@backstage/plugin-techdocs 0.12.10 → 0.12.14
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 +57 -0
- package/config.d.ts +11 -0
- package/dist/index.d.ts +11 -15
- package/dist/index.esm.js +77 -19
- package/dist/index.esm.js.map +1 -1
- package/package.json +16 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# @backstage/plugin-techdocs
|
|
2
2
|
|
|
3
|
+
## 0.12.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5333451def: Cleaned up API exports
|
|
8
|
+
- 1628ca3f49: Fix an issue where the TechDocs sidebar is hidden when the Backstage sidebar is pinned at smaller screen sizes
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- @backstage/config@0.1.12
|
|
11
|
+
- @backstage/integration@0.7.1
|
|
12
|
+
- @backstage/core-components@0.8.4
|
|
13
|
+
- @backstage/core-plugin-api@0.5.0
|
|
14
|
+
- @backstage/plugin-catalog-react@0.6.11
|
|
15
|
+
- @backstage/errors@0.2.0
|
|
16
|
+
- @backstage/catalog-model@0.9.9
|
|
17
|
+
- @backstage/integration-react@0.1.18
|
|
18
|
+
- @backstage/plugin-catalog@0.7.8
|
|
19
|
+
- @backstage/plugin-search@0.5.5
|
|
20
|
+
|
|
21
|
+
## 0.12.13
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- fe9de6c25b: Adds support for opening internal Techdocs links in a new tab with CTRL+Click or CMD+Click
|
|
26
|
+
- 4ce51ab0f1: Internal refactor of the `react-use` imports to use `react-use/lib/*` instead.
|
|
27
|
+
- e0271456d8: Updated Techdocs footer navigation to dynamically resize to the width of the dom, resolving an issue where a pinned sidebar causes navigation to go off of the screen
|
|
28
|
+
- Updated dependencies
|
|
29
|
+
- @backstage/plugin-search@0.5.4
|
|
30
|
+
- @backstage/core-plugin-api@0.4.1
|
|
31
|
+
- @backstage/plugin-catalog-react@0.6.10
|
|
32
|
+
- @backstage/core-components@0.8.3
|
|
33
|
+
- @backstage/plugin-catalog@0.7.7
|
|
34
|
+
|
|
35
|
+
## 0.12.12
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- aa8f764a3e: Add the techdocs.sanitizer.allowedIframeHosts config.
|
|
40
|
+
This config allows all iframes which have the host of the attribute src in the 'allowedIframehosts' list to be displayed in the documentation.
|
|
41
|
+
- Updated dependencies
|
|
42
|
+
- @backstage/plugin-search@0.5.3
|
|
43
|
+
- @backstage/plugin-catalog@0.7.6
|
|
44
|
+
- @backstage/plugin-catalog-react@0.6.9
|
|
45
|
+
- @backstage/integration@0.7.0
|
|
46
|
+
- @backstage/integration-react@0.1.17
|
|
47
|
+
|
|
48
|
+
## 0.12.11
|
|
49
|
+
|
|
50
|
+
### Patch Changes
|
|
51
|
+
|
|
52
|
+
- Updated dependencies
|
|
53
|
+
- @backstage/plugin-search@0.5.2
|
|
54
|
+
- @backstage/core-plugin-api@0.4.0
|
|
55
|
+
- @backstage/plugin-catalog-react@0.6.8
|
|
56
|
+
- @backstage/core-components@0.8.2
|
|
57
|
+
- @backstage/plugin-catalog@0.7.5
|
|
58
|
+
- @backstage/integration-react@0.1.16
|
|
59
|
+
|
|
3
60
|
## 0.12.10
|
|
4
61
|
|
|
5
62
|
### Patch Changes
|
package/config.d.ts
CHANGED
|
@@ -39,5 +39,16 @@ export interface Config {
|
|
|
39
39
|
* @deprecated
|
|
40
40
|
*/
|
|
41
41
|
requestUrl?: string;
|
|
42
|
+
|
|
43
|
+
sanitizer?: {
|
|
44
|
+
/**
|
|
45
|
+
* Allows iframe tag only for listed hosts
|
|
46
|
+
* Example:
|
|
47
|
+
* allowedIframeHosts: ["example.com"]
|
|
48
|
+
* this will allow all iframes with the host `example.com` in the src attribute
|
|
49
|
+
* @visibility frontend
|
|
50
|
+
*/
|
|
51
|
+
allowedIframeHosts?: string[];
|
|
52
|
+
};
|
|
42
53
|
};
|
|
43
54
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -37,9 +37,7 @@ interface TechDocsApi {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* API to talk to techdocs-backend
|
|
41
|
-
*
|
|
42
|
-
* @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API
|
|
40
|
+
* API to talk to `techdocs-backend`.
|
|
43
41
|
*/
|
|
44
42
|
declare class TechDocsClient implements TechDocsApi {
|
|
45
43
|
configApi: Config;
|
|
@@ -58,7 +56,7 @@ declare class TechDocsClient implements TechDocsApi {
|
|
|
58
56
|
* static files. It includes necessary data about the docs site. This method requests techdocs-backend
|
|
59
57
|
* which retrieves the TechDocs metadata.
|
|
60
58
|
*
|
|
61
|
-
* @param
|
|
59
|
+
* @param entityId - Object containing entity data like name, namespace, etc.
|
|
62
60
|
*/
|
|
63
61
|
getTechDocsMetadata(entityId: EntityName): Promise<TechDocsMetadata>;
|
|
64
62
|
/**
|
|
@@ -67,14 +65,12 @@ declare class TechDocsClient implements TechDocsApi {
|
|
|
67
65
|
* This method requests techdocs-backend which uses the catalog APIs to respond with filtered
|
|
68
66
|
* information required here.
|
|
69
67
|
*
|
|
70
|
-
* @param
|
|
68
|
+
* @param entityId - Object containing entity data like name, namespace, etc.
|
|
71
69
|
*/
|
|
72
70
|
getEntityMetadata(entityId: EntityName): Promise<TechDocsEntityMetadata>;
|
|
73
71
|
}
|
|
74
72
|
/**
|
|
75
73
|
* API which talks to TechDocs storage to fetch files to render.
|
|
76
|
-
*
|
|
77
|
-
* @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API
|
|
78
74
|
*/
|
|
79
75
|
declare class TechDocsStorageClient implements TechDocsStorageApi {
|
|
80
76
|
configApi: Config;
|
|
@@ -91,19 +87,19 @@ declare class TechDocsStorageClient implements TechDocsStorageApi {
|
|
|
91
87
|
/**
|
|
92
88
|
* Fetch HTML content as text for an individual docs page in an entity's docs site.
|
|
93
89
|
*
|
|
94
|
-
* @param
|
|
95
|
-
* @param
|
|
96
|
-
* @returns
|
|
97
|
-
* @throws
|
|
90
|
+
* @param entityId - Object containing entity data like name, namespace, etc.
|
|
91
|
+
* @param path - The unique path to an individual docs page e.g. overview/what-is-new
|
|
92
|
+
* @returns HTML content of the docs page as string
|
|
93
|
+
* @throws Throws error when the page is not found.
|
|
98
94
|
*/
|
|
99
95
|
getEntityDocs(entityId: EntityName, path: string): Promise<string>;
|
|
100
96
|
/**
|
|
101
97
|
* Check if docs are on the latest version and trigger rebuild if not
|
|
102
98
|
*
|
|
103
|
-
* @param
|
|
104
|
-
* @param
|
|
105
|
-
* @returns
|
|
106
|
-
* @throws
|
|
99
|
+
* @param entityId - Object containing entity data like name, namespace, etc.
|
|
100
|
+
* @param logHandler - Callback to receive log messages from the build process
|
|
101
|
+
* @returns Whether documents are currently synchronized to newest version
|
|
102
|
+
* @throws Throws error on error from sync endpoint in Techdocs Backend
|
|
107
103
|
*/
|
|
108
104
|
syncEntityDocs(entityId: EntityName, logHandler?: (line: string) => void): Promise<SyncResult>;
|
|
109
105
|
getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): Promise<string>;
|
package/dist/index.esm.js
CHANGED
|
@@ -3,11 +3,11 @@ import { ResponseError, NotFoundError } from '@backstage/errors';
|
|
|
3
3
|
import { EventSourcePolyfill } from 'event-source-polyfill';
|
|
4
4
|
import React, { useEffect, useState, useReducer, useRef, useMemo, createContext, useContext, useCallback } from 'react';
|
|
5
5
|
import { makeStyles, ListItemText, ListItem, Divider, Card, CardMedia, CardContent, CardActions, Grid, TextField, InputAdornment, IconButton, CircularProgress, createStyles, Button as Button$1, Drawer, Typography, useTheme } from '@material-ui/core';
|
|
6
|
-
import { Link, SubvalueCell, Table, EmptyState, Button, WarningPanel, CodeSnippet, PageWithHeader, Content, ContentHeader, SupportButton, ItemCardGrid, ItemCardHeader, Progress, LogViewer, ErrorPage, HeaderLabel, Header, Page, HeaderTabs, MissingAnnotationEmptyState } from '@backstage/core-components';
|
|
6
|
+
import { Link, SubvalueCell, Table, EmptyState, Button, WarningPanel, CodeSnippet, PageWithHeader, Content, ContentHeader, SupportButton, ItemCardGrid, ItemCardHeader, Progress, LogViewer, ErrorPage, SidebarPinStateContext, HeaderLabel, Header, Page, HeaderTabs, MissingAnnotationEmptyState } from '@backstage/core-components';
|
|
7
7
|
import TextTruncate from 'react-text-truncate';
|
|
8
8
|
import { FilteredEntityLayout, FilterContainer, EntityListContainer } from '@backstage/plugin-catalog';
|
|
9
9
|
import { favoriteEntityIcon, favoriteEntityTooltip, EntityRefLinks, getEntityRelations, formatEntityRefTitle, useEntityListProvider, useStarredEntities, CATALOG_FILTER_EXISTS, EntityListProvider, UserListPicker, EntityOwnerPicker, EntityTagPicker, EntityRefLink, catalogApiRef, useOwnUser, isOwnerOf, useEntity } from '@backstage/plugin-catalog-react';
|
|
10
|
-
import
|
|
10
|
+
import useCopyToClipboard from 'react-use/lib/useCopyToClipboard';
|
|
11
11
|
import { capitalize } from 'lodash';
|
|
12
12
|
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
|
|
13
13
|
import ShareIcon from '@material-ui/icons/Share';
|
|
@@ -22,17 +22,18 @@ import { SearchContextProvider, useSearch } from '@backstage/plugin-search';
|
|
|
22
22
|
import SearchIcon from '@material-ui/icons/Search';
|
|
23
23
|
import Autocomplete from '@material-ui/lab/Autocomplete';
|
|
24
24
|
import { useNavigate, useOutlet } from 'react-router';
|
|
25
|
+
import useDebounce from 'react-use/lib/useDebounce';
|
|
25
26
|
import { Alert } from '@material-ui/lab';
|
|
26
27
|
import Close from '@material-ui/icons/Close';
|
|
28
|
+
import useAsync from 'react-use/lib/useAsync';
|
|
29
|
+
import useAsyncRetry from 'react-use/lib/useAsyncRetry';
|
|
27
30
|
import CodeIcon from '@material-ui/icons/Code';
|
|
28
31
|
|
|
29
32
|
const techdocsStorageApiRef = createApiRef({
|
|
30
|
-
id: "plugin.techdocs.storageservice"
|
|
31
|
-
description: "Used to make requests towards the techdocs storage"
|
|
33
|
+
id: "plugin.techdocs.storageservice"
|
|
32
34
|
});
|
|
33
35
|
const techdocsApiRef = createApiRef({
|
|
34
|
-
id: "plugin.techdocs.service"
|
|
35
|
-
description: "Used to make requests towards techdocs API"
|
|
36
|
+
id: "plugin.techdocs.service"
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
class TechDocsClient {
|
|
@@ -753,11 +754,37 @@ const safeLinksHook = (node) => {
|
|
|
753
754
|
}
|
|
754
755
|
return node;
|
|
755
756
|
};
|
|
756
|
-
const
|
|
757
|
+
const filterIframeHook = (allowedIframeHosts) => (node) => {
|
|
758
|
+
if (node.nodeName === "IFRAME") {
|
|
759
|
+
const src = node.getAttribute("src");
|
|
760
|
+
if (!src) {
|
|
761
|
+
node.remove();
|
|
762
|
+
return node;
|
|
763
|
+
}
|
|
764
|
+
try {
|
|
765
|
+
const srcUrl = new URL(src);
|
|
766
|
+
const isMatch = allowedIframeHosts.some((host) => srcUrl.host === host);
|
|
767
|
+
if (!isMatch) {
|
|
768
|
+
node.remove();
|
|
769
|
+
}
|
|
770
|
+
} catch (error) {
|
|
771
|
+
console.warn(`Invalid iframe src, ${error}`);
|
|
772
|
+
node.remove();
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return node;
|
|
776
|
+
};
|
|
777
|
+
const sanitizeDOM = (config) => {
|
|
778
|
+
const allowedIframeHosts = (config == null ? void 0 : config.getOptionalStringArray("allowedIframeHosts")) || [];
|
|
757
779
|
return (dom) => {
|
|
758
780
|
DOMPurify.addHook("afterSanitizeAttributes", safeLinksHook);
|
|
781
|
+
const addTags = ["link"];
|
|
782
|
+
if (allowedIframeHosts.length > 0) {
|
|
783
|
+
DOMPurify.addHook("beforeSanitizeElements", filterIframeHook(allowedIframeHosts));
|
|
784
|
+
addTags.push("iframe");
|
|
785
|
+
}
|
|
759
786
|
return DOMPurify.sanitize(dom.innerHTML, {
|
|
760
|
-
ADD_TAGS:
|
|
787
|
+
ADD_TAGS: addTags,
|
|
761
788
|
FORBID_TAGS: ["style"],
|
|
762
789
|
WHOLE_DOCUMENT: true,
|
|
763
790
|
RETURN_DOM: true
|
|
@@ -1240,10 +1267,12 @@ const useTechDocsReaderDom = (entityRef) => {
|
|
|
1240
1267
|
const theme = useTheme();
|
|
1241
1268
|
const techdocsStorageApi = useApi(techdocsStorageApiRef);
|
|
1242
1269
|
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
|
|
1270
|
+
const techdocsSanitizer = useApi(configApiRef);
|
|
1243
1271
|
const { namespace = "", kind = "", name = "" } = entityRef;
|
|
1244
1272
|
const { state, path, content: rawPage } = useTechDocsReader();
|
|
1245
1273
|
const [sidebars, setSidebars] = useState();
|
|
1246
1274
|
const [dom, setDom] = useState(null);
|
|
1275
|
+
const { isPinned } = useContext(SidebarPinStateContext);
|
|
1247
1276
|
const updateSidebarPosition = useCallback(() => {
|
|
1248
1277
|
if (!dom || !sidebars)
|
|
1249
1278
|
return;
|
|
@@ -1262,8 +1291,23 @@ const useTechDocsReaderDom = (entityRef) => {
|
|
|
1262
1291
|
window.removeEventListener("resize", updateSidebarPosition);
|
|
1263
1292
|
};
|
|
1264
1293
|
}, [updateSidebarPosition, state]);
|
|
1294
|
+
const updateFooterWidth = useCallback(() => {
|
|
1295
|
+
if (!dom)
|
|
1296
|
+
return;
|
|
1297
|
+
const footer = dom.querySelector(".md-footer");
|
|
1298
|
+
if (footer) {
|
|
1299
|
+
footer.style.width = `${dom.getBoundingClientRect().width}px`;
|
|
1300
|
+
}
|
|
1301
|
+
}, [dom]);
|
|
1302
|
+
useEffect(() => {
|
|
1303
|
+
updateFooterWidth();
|
|
1304
|
+
window.addEventListener("resize", updateFooterWidth);
|
|
1305
|
+
return () => {
|
|
1306
|
+
window.removeEventListener("resize", updateFooterWidth);
|
|
1307
|
+
};
|
|
1308
|
+
});
|
|
1265
1309
|
const preRender = useCallback((rawContent, contentPath) => transform(rawContent, [
|
|
1266
|
-
sanitizeDOM(),
|
|
1310
|
+
sanitizeDOM(techdocsSanitizer.getOptionalConfig("techdocs.sanitizer")),
|
|
1267
1311
|
addBaseUrl({
|
|
1268
1312
|
techdocsStorageApi,
|
|
1269
1313
|
entityId: {
|
|
@@ -1291,7 +1335,7 @@ const useTechDocsReaderDom = (entityRef) => {
|
|
|
1291
1335
|
.md-sidebar { position: fixed; bottom: 100px; width: 20rem; }
|
|
1292
1336
|
.md-sidebar--secondary { right: 2rem; }
|
|
1293
1337
|
.md-content { margin-bottom: 50px }
|
|
1294
|
-
.md-footer { position: fixed; bottom: 0px;
|
|
1338
|
+
.md-footer { position: fixed; bottom: 0px; }
|
|
1295
1339
|
.md-footer-nav__link { width: 20rem;}
|
|
1296
1340
|
.md-content { margin-left: 20rem; max-width: calc(100% - 20rem * 2 - 3rem); }
|
|
1297
1341
|
.md-typeset { font-size: 1rem; }
|
|
@@ -1327,13 +1371,16 @@ const useTechDocsReaderDom = (entityRef) => {
|
|
|
1327
1371
|
transition: none !important
|
|
1328
1372
|
}
|
|
1329
1373
|
.md-sidebar--secondary { display: none; }
|
|
1330
|
-
.md-sidebar--primary { left: 72px; width: 10rem }
|
|
1374
|
+
.md-sidebar--primary { left: ${isPinned ? "242px" : "72px"}; width: 10rem }
|
|
1331
1375
|
.md-content { margin-left: 10rem; max-width: calc(100% - 10rem); }
|
|
1332
1376
|
.md-content__inner { font-size: 0.9rem }
|
|
1333
1377
|
.md-footer {
|
|
1334
1378
|
position: static;
|
|
1335
|
-
|
|
1336
|
-
|
|
1379
|
+
padding-left: 10rem;
|
|
1380
|
+
}
|
|
1381
|
+
.md-footer-nav__link {
|
|
1382
|
+
/* footer links begin to overlap at small sizes without setting width */
|
|
1383
|
+
width: 50%;
|
|
1337
1384
|
}
|
|
1338
1385
|
.md-nav--primary .md-nav__title {
|
|
1339
1386
|
white-space: normal;
|
|
@@ -1398,27 +1445,38 @@ const useTechDocsReaderDom = (entityRef) => {
|
|
|
1398
1445
|
namespace,
|
|
1399
1446
|
scmIntegrationsApi,
|
|
1400
1447
|
techdocsStorageApi,
|
|
1448
|
+
techdocsSanitizer,
|
|
1401
1449
|
theme.palette.action.disabledBackground,
|
|
1402
1450
|
theme.palette.background.default,
|
|
1403
1451
|
theme.palette.background.paper,
|
|
1404
1452
|
theme.palette.primary.main,
|
|
1405
1453
|
theme.palette.success.main,
|
|
1406
1454
|
theme.palette.text.primary,
|
|
1407
|
-
theme.typography.fontFamily
|
|
1455
|
+
theme.typography.fontFamily,
|
|
1456
|
+
isPinned
|
|
1408
1457
|
]);
|
|
1409
1458
|
const postRender = useCallback(async (transformedElement) => transform(transformedElement, [
|
|
1410
1459
|
scrollIntoAnchor(),
|
|
1411
1460
|
addLinkClickListener({
|
|
1412
1461
|
baseUrl: window.location.origin,
|
|
1413
|
-
onClick: (
|
|
1462
|
+
onClick: (event, url) => {
|
|
1414
1463
|
var _a, _b;
|
|
1464
|
+
const modifierActive = event.ctrlKey || event.metaKey;
|
|
1415
1465
|
const parsedUrl = new URL(url);
|
|
1416
1466
|
if (parsedUrl.hash) {
|
|
1417
|
-
|
|
1418
|
-
|
|
1467
|
+
if (modifierActive) {
|
|
1468
|
+
window.open(`${parsedUrl.pathname}${parsedUrl.hash}`, "_blank");
|
|
1469
|
+
} else {
|
|
1470
|
+
navigate(`${parsedUrl.pathname}${parsedUrl.hash}`);
|
|
1471
|
+
(_a = transformedElement == null ? void 0 : transformedElement.querySelector(`#${parsedUrl.hash.slice(1)}`)) == null ? void 0 : _a.scrollIntoView();
|
|
1472
|
+
}
|
|
1419
1473
|
} else {
|
|
1420
|
-
|
|
1421
|
-
|
|
1474
|
+
if (modifierActive) {
|
|
1475
|
+
window.open(parsedUrl.pathname, "_blank");
|
|
1476
|
+
} else {
|
|
1477
|
+
navigate(parsedUrl.pathname);
|
|
1478
|
+
(_b = transformedElement == null ? void 0 : transformedElement.querySelector(".md-content__inner")) == null ? void 0 : _b.scrollIntoView();
|
|
1479
|
+
}
|
|
1422
1480
|
}
|
|
1423
1481
|
}
|
|
1424
1482
|
}),
|
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../src/api.ts","../src/client.ts","../src/components/DocsResultListItem/DocsResultListItem.tsx","../src/routes.ts","../src/home/components/actions.tsx","../src/home/components/columns.tsx","../src/helpers.ts","../src/home/components/DocsTable.tsx","../src/home/components/EntityListDocsTable.tsx","../src/home/components/TechDocsPageWrapper.tsx","../src/home/components/TechDocsPicker.tsx","../src/home/components/DefaultTechDocsHome.tsx","../src/home/components/DocsCardGrid.tsx","../src/home/components/EntityListDocsGrid.tsx","../src/plugin.ts","../src/reader/transformers/addBaseUrl.ts","../src/reader/transformers/addGitFeedbackLink.ts","../src/reader/transformers/rewriteDocLinks.ts","../src/reader/transformers/addLinkClickListener.ts","../src/reader/transformers/removeMkdocsHeader.ts","../src/reader/transformers/simplifyMkdocsFooter.ts","../src/reader/transformers/onCssReady.ts","../src/reader/transformers/sanitizeDOM.ts","../src/reader/transformers/injectCss.ts","../src/reader/transformers/scrollIntoAnchor.ts","../src/reader/transformers/transformer.ts","../src/reader/components/TechDocsSearch.tsx","../src/reader/components/TechDocsBuildLogs.tsx","../src/reader/components/TechDocsNotFound.tsx","../src/reader/components/TechDocsStateIndicator.tsx","../src/reader/components/useReaderState.ts","../src/reader/components/Reader.tsx","../src/reader/components/TechDocsPageHeader.tsx","../src/reader/components/LegacyTechDocsPage.tsx","../src/reader/components/TechDocsPage.tsx","../src/home/components/TechDocsCustomHome.tsx","../src/home/components/LegacyTechDocsHome.tsx","../src/home/components/TechDocsIndexPage.tsx","../src/EntityPageDocs.tsx","../src/Router.tsx"],"sourcesContent":["/*\n * Copyright 2020 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 { EntityName } from '@backstage/catalog-model';\nimport { TechDocsEntityMetadata, TechDocsMetadata } from './types';\nimport { createApiRef } from '@backstage/core-plugin-api';\n\nexport const techdocsStorageApiRef = createApiRef<TechDocsStorageApi>({\n id: 'plugin.techdocs.storageservice',\n description: 'Used to make requests towards the techdocs storage',\n});\n\nexport const techdocsApiRef = createApiRef<TechDocsApi>({\n id: 'plugin.techdocs.service',\n description: 'Used to make requests towards techdocs API',\n});\n\nexport type SyncResult = 'cached' | 'updated';\n\nexport interface TechDocsStorageApi {\n getApiOrigin(): Promise<string>;\n getStorageUrl(): Promise<string>;\n getBuilder(): Promise<string>;\n getEntityDocs(entityId: EntityName, path: string): Promise<string>;\n syncEntityDocs(\n entityId: EntityName,\n logHandler?: (line: string) => void,\n ): Promise<SyncResult>;\n getBaseUrl(\n oldBaseUrl: string,\n entityId: EntityName,\n path: string,\n ): Promise<string>;\n}\n\nexport interface TechDocsApi {\n getApiOrigin(): Promise<string>;\n getTechDocsMetadata(entityId: EntityName): Promise<TechDocsMetadata>;\n getEntityMetadata(entityId: EntityName): Promise<TechDocsEntityMetadata>;\n}\n","/*\n * Copyright 2020 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 { EntityName } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';\nimport { NotFoundError, ResponseError } from '@backstage/errors';\nimport { EventSourcePolyfill } from 'event-source-polyfill';\nimport { SyncResult, TechDocsApi, TechDocsStorageApi } from './api';\nimport { TechDocsEntityMetadata, TechDocsMetadata } from './types';\n\n/**\n * API to talk to techdocs-backend.\n *\n * @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API\n */\nexport class TechDocsClient implements TechDocsApi {\n public configApi: Config;\n public discoveryApi: DiscoveryApi;\n public identityApi: IdentityApi;\n\n constructor({\n configApi,\n discoveryApi,\n identityApi,\n }: {\n configApi: Config;\n discoveryApi: DiscoveryApi;\n identityApi: IdentityApi;\n }) {\n this.configApi = configApi;\n this.discoveryApi = discoveryApi;\n this.identityApi = identityApi;\n }\n\n async getApiOrigin(): Promise<string> {\n return (\n this.configApi.getOptionalString('techdocs.requestUrl') ??\n (await this.discoveryApi.getBaseUrl('techdocs'))\n );\n }\n\n /**\n * Retrieve TechDocs metadata.\n *\n * When docs are built, we generate a techdocs_metadata.json and store it along with the generated\n * static files. It includes necessary data about the docs site. This method requests techdocs-backend\n * which retrieves the TechDocs metadata.\n *\n * @param {EntityName} entityId Object containing entity data like name, namespace, etc.\n */\n async getTechDocsMetadata(entityId: EntityName): Promise<TechDocsMetadata> {\n const { kind, namespace, name } = entityId;\n\n const apiOrigin = await this.getApiOrigin();\n const requestUrl = `${apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`;\n const token = await this.identityApi.getIdToken();\n\n const request = await fetch(`${requestUrl}`, {\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n });\n\n if (!request.ok) {\n throw await ResponseError.fromResponse(request);\n }\n\n return await request.json();\n }\n\n /**\n * Retrieve metadata about an entity.\n *\n * This method requests techdocs-backend which uses the catalog APIs to respond with filtered\n * information required here.\n *\n * @param {EntityName} entityId Object containing entity data like name, namespace, etc.\n */\n async getEntityMetadata(\n entityId: EntityName,\n ): Promise<TechDocsEntityMetadata> {\n const { kind, namespace, name } = entityId;\n\n const apiOrigin = await this.getApiOrigin();\n const requestUrl = `${apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`;\n const token = await this.identityApi.getIdToken();\n\n const request = await fetch(`${requestUrl}`, {\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n });\n\n if (!request.ok) {\n throw await ResponseError.fromResponse(request);\n }\n\n return await request.json();\n }\n}\n\n/**\n * API which talks to TechDocs storage to fetch files to render.\n *\n * @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API\n */\nexport class TechDocsStorageClient implements TechDocsStorageApi {\n public configApi: Config;\n public discoveryApi: DiscoveryApi;\n public identityApi: IdentityApi;\n\n constructor({\n configApi,\n discoveryApi,\n identityApi,\n }: {\n configApi: Config;\n discoveryApi: DiscoveryApi;\n identityApi: IdentityApi;\n }) {\n this.configApi = configApi;\n this.discoveryApi = discoveryApi;\n this.identityApi = identityApi;\n }\n\n async getApiOrigin(): Promise<string> {\n return (\n this.configApi.getOptionalString('techdocs.requestUrl') ??\n (await this.discoveryApi.getBaseUrl('techdocs'))\n );\n }\n\n async getStorageUrl(): Promise<string> {\n return (\n this.configApi.getOptionalString('techdocs.storageUrl') ??\n `${await this.discoveryApi.getBaseUrl('techdocs')}/static/docs`\n );\n }\n\n async getBuilder(): Promise<string> {\n return this.configApi.getString('techdocs.builder');\n }\n\n /**\n * Fetch HTML content as text for an individual docs page in an entity's docs site.\n *\n * @param {EntityName} entityId Object containing entity data like name, namespace, etc.\n * @param {string} path The unique path to an individual docs page e.g. overview/what-is-new\n * @returns {string} HTML content of the docs page as string\n * @throws {Error} Throws error when the page is not found.\n */\n async getEntityDocs(entityId: EntityName, path: string): Promise<string> {\n const { kind, namespace, name } = entityId;\n\n const storageUrl = await this.getStorageUrl();\n const url = `${storageUrl}/${namespace}/${kind}/${name}/${path}`;\n const token = await this.identityApi.getIdToken();\n\n const request = await fetch(\n `${url.endsWith('/') ? url : `${url}/`}index.html`,\n {\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n },\n );\n\n let errorMessage = '';\n switch (request.status) {\n case 404:\n errorMessage = 'Page not found. ';\n // path is empty for the home page of an entity's docs site\n if (!path) {\n errorMessage +=\n 'This could be because there is no index.md file in the root of the docs directory of this repository.';\n }\n throw new NotFoundError(errorMessage);\n case 500:\n errorMessage =\n 'Could not generate documentation or an error in the TechDocs backend. ';\n throw new Error(errorMessage);\n default:\n // Do nothing\n break;\n }\n\n return request.text();\n }\n\n /**\n * Check if docs are on the latest version and trigger rebuild if not\n *\n * @param {EntityName} entityId Object containing entity data like name, namespace, etc.\n * @param {Function} logHandler Callback to receive log messages from the build process\n * @returns {SyncResult} Whether documents are currently synchronized to newest version\n * @throws {Error} Throws error on error from sync endpoint in Techdocs Backend\n */\n async syncEntityDocs(\n entityId: EntityName,\n logHandler: (line: string) => void = () => {},\n ): Promise<SyncResult> {\n const { kind, namespace, name } = entityId;\n\n const apiOrigin = await this.getApiOrigin();\n const url = `${apiOrigin}/sync/${namespace}/${kind}/${name}`;\n const token = await this.identityApi.getIdToken();\n\n return new Promise((resolve, reject) => {\n // Polyfill is used to add support for custom headers and auth\n const source = new EventSourcePolyfill(url, {\n withCredentials: true,\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n });\n\n source.addEventListener('log', (e: any) => {\n if (e.data) {\n logHandler(JSON.parse(e.data));\n }\n });\n\n source.addEventListener('finish', (e: any) => {\n let updated: boolean = false;\n\n if (e.data) {\n ({ updated } = JSON.parse(e.data));\n }\n\n resolve(updated ? 'updated' : 'cached');\n });\n\n source.onerror = (e: any) => {\n source.close();\n\n switch (e.status) {\n // the endpoint returned a 404 status\n case 404:\n reject(new NotFoundError(e.message));\n return;\n\n // also handles the event-stream close. the reject is ignored if the Promise was already\n // resolved by a finish event.\n default:\n reject(new Error(e.data));\n return;\n }\n };\n });\n }\n\n async getBaseUrl(\n oldBaseUrl: string,\n entityId: EntityName,\n path: string,\n ): Promise<string> {\n const { kind, namespace, name } = entityId;\n\n const apiOrigin = await this.getApiOrigin();\n const newBaseUrl = `${apiOrigin}/static/docs/${namespace}/${kind}/${name}/${path}`;\n\n return new URL(\n oldBaseUrl,\n newBaseUrl.endsWith('/') ? newBaseUrl : `${newBaseUrl}/`,\n ).toString();\n }\n}\n","/*\n * Copyright 2021 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, { PropsWithChildren } from 'react';\nimport { Divider, ListItem, ListItemText, makeStyles } from '@material-ui/core';\nimport { Link } from '@backstage/core-components';\nimport TextTruncate from 'react-text-truncate';\n\nconst useStyles = makeStyles({\n flexContainer: {\n flexWrap: 'wrap',\n },\n itemText: {\n width: '100%',\n marginBottom: '1rem',\n },\n});\n\nexport const DocsResultListItem = ({\n result,\n lineClamp = 5,\n asListItem = true,\n asLink = true,\n title,\n}: {\n result: any;\n lineClamp?: number;\n asListItem?: boolean;\n asLink?: boolean;\n title?: string;\n}) => {\n const classes = useStyles();\n const TextItem = () => (\n <ListItemText\n className={classes.itemText}\n primaryTypographyProps={{ variant: 'h6' }}\n primary={\n title\n ? title\n : `${result.title} | ${result.entityTitle ?? result.name} docs`\n }\n secondary={\n <TextTruncate\n line={lineClamp}\n truncateText=\"…\"\n text={result.text}\n element=\"span\"\n />\n }\n />\n );\n\n const LinkWrapper = ({ children }: PropsWithChildren<{}>) =>\n asLink ? <Link to={result.location}>{children}</Link> : <>{children}</>;\n\n const ListItemWrapper = ({ children }: PropsWithChildren<{}>) =>\n asListItem ? (\n <>\n <ListItem alignItems=\"flex-start\" className={classes.flexContainer}>\n {children}\n </ListItem>\n <Divider component=\"li\" />\n </>\n ) : (\n <>{children}</>\n );\n\n return (\n <LinkWrapper>\n <ListItemWrapper>\n <TextItem />\n </ListItemWrapper>\n </LinkWrapper>\n );\n};\n","/*\n * Copyright 2020 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 { createRouteRef } from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'techdocs:index-page',\n});\n\nexport const rootDocsRouteRef = createRouteRef({\n id: 'techdocs:reader-page',\n params: ['namespace', 'kind', 'name'],\n});\n\nexport const rootCatalogDocsRouteRef = createRouteRef({\n id: 'techdocs:catalog-reader-view',\n});\n","/*\n * Copyright 2021 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 from 'react';\nimport ShareIcon from '@material-ui/icons/Share';\nimport {\n favoriteEntityIcon,\n favoriteEntityTooltip,\n} from '@backstage/plugin-catalog-react';\nimport { DocsTableRow } from './types';\n\nexport function createCopyDocsUrlAction(copyToClipboard: Function) {\n return (row: DocsTableRow) => {\n return {\n icon: () => <ShareIcon fontSize=\"small\" />,\n tooltip: 'Click to copy documentation link to clipboard',\n onClick: () =>\n copyToClipboard(`${window.location.origin}${row.resolved.docsUrl}`),\n };\n };\n}\n\nexport function createStarEntityAction(\n isStarredEntity: Function,\n toggleStarredEntity: Function,\n) {\n return ({ entity }: DocsTableRow) => {\n const isStarred = isStarredEntity(entity);\n return {\n cellStyle: { paddingLeft: '1em' },\n icon: () => favoriteEntityIcon(isStarred),\n tooltip: favoriteEntityTooltip(isStarred),\n onClick: () => toggleStarredEntity(entity),\n };\n };\n}\n","/*\n * Copyright 2021 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 from 'react';\nimport { Link, SubvalueCell, TableColumn } from '@backstage/core-components';\nimport { EntityRefLinks } from '@backstage/plugin-catalog-react';\nimport { Entity } from '@backstage/catalog-model';\nimport { DocsTableRow } from './types';\n\nfunction customTitle(entity: Entity): string {\n return entity.metadata.title || entity.metadata.name;\n}\n\nexport function createNameColumn(): TableColumn<DocsTableRow> {\n return {\n title: 'Document',\n field: 'entity.metadata.name',\n highlight: true,\n render: (row: DocsTableRow) => (\n <SubvalueCell\n value={<Link to={row.resolved.docsUrl}>{customTitle(row.entity)}</Link>}\n subvalue={row.entity.metadata.description}\n />\n ),\n };\n}\n\nexport function createOwnerColumn(): TableColumn<DocsTableRow> {\n return {\n title: 'Owner',\n field: 'resolved.ownedByRelationsTitle',\n render: ({ resolved }) => (\n <EntityRefLinks\n entityRefs={resolved.ownedByRelations}\n defaultKind=\"group\"\n />\n ),\n };\n}\n\nexport function createTypeColumn(): TableColumn<DocsTableRow> {\n return {\n title: 'Type',\n field: 'entity.spec.type',\n };\n}\n","/*\n * Copyright 2021 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 { Config } from '@backstage/config';\n\n// Lower-case entity triplets by default, but allow override.\nexport function toLowerMaybe(str: string, config: Config) {\n return config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n )\n ? str\n : str.toLocaleLowerCase('en-US');\n}\n","/*\n * Copyright 2021 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 from 'react';\nimport { useCopyToClipboard } from 'react-use';\n\nimport { useRouteRef, useApi, configApiRef } from '@backstage/core-plugin-api';\nimport { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';\nimport {\n formatEntityRefTitle,\n getEntityRelations,\n} from '@backstage/plugin-catalog-react';\nimport { rootDocsRouteRef } from '../../routes';\nimport {\n Button,\n EmptyState,\n Table,\n TableColumn,\n TableProps,\n} from '@backstage/core-components';\nimport * as actionFactories from './actions';\nimport * as columnFactories from './columns';\nimport { DocsTableRow } from './types';\nimport { toLowerMaybe } from '../../helpers';\n\nexport const DocsTable = ({\n entities,\n title,\n loading,\n columns,\n actions,\n}: {\n entities: Entity[] | undefined;\n title?: string | undefined;\n loading?: boolean | undefined;\n columns?: TableColumn<DocsTableRow>[];\n actions?: TableProps<DocsTableRow>['actions'];\n}) => {\n const [, copyToClipboard] = useCopyToClipboard();\n const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);\n const config = useApi(configApiRef);\n if (!entities) return null;\n\n const documents = entities.map(entity => {\n const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY);\n return {\n entity,\n resolved: {\n docsUrl: getRouteToReaderPageFor({\n namespace: toLowerMaybe(\n entity.metadata.namespace ?? 'default',\n config,\n ),\n kind: toLowerMaybe(entity.kind, config),\n name: toLowerMaybe(entity.metadata.name, config),\n }),\n ownedByRelations,\n ownedByRelationsTitle: ownedByRelations\n .map(r => formatEntityRefTitle(r, { defaultKind: 'group' }))\n .join(', '),\n },\n };\n });\n\n const defaultColumns: TableColumn<DocsTableRow>[] = [\n columnFactories.createNameColumn(),\n columnFactories.createOwnerColumn(),\n columnFactories.createTypeColumn(),\n ];\n\n const defaultActions: TableProps<DocsTableRow>['actions'] = [\n actionFactories.createCopyDocsUrlAction(copyToClipboard),\n ];\n\n return (\n <>\n {loading || (documents && documents.length > 0) ? (\n <Table<DocsTableRow>\n isLoading={loading}\n options={{\n paging: true,\n pageSize: 20,\n search: true,\n actionsColumnIndex: -1,\n }}\n data={documents}\n columns={columns || defaultColumns}\n actions={actions || defaultActions}\n title={\n title\n ? `${title} (${documents.length})`\n : `All (${documents.length})`\n }\n />\n ) : (\n <EmptyState\n missing=\"data\"\n title=\"No documents to show\"\n description=\"Create your own document. Check out our Getting Started Information\"\n action={\n <Button\n color=\"primary\"\n to=\"https://backstage.io/docs/features/techdocs/getting-started\"\n variant=\"contained\"\n >\n DOCS\n </Button>\n }\n />\n )}\n </>\n );\n};\n","/*\n * Copyright 2021 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 from 'react';\nimport { useCopyToClipboard } from 'react-use';\nimport { capitalize } from 'lodash';\nimport {\n CodeSnippet,\n TableColumn,\n TableProps,\n WarningPanel,\n} from '@backstage/core-components';\nimport {\n useEntityListProvider,\n useStarredEntities,\n} from '@backstage/plugin-catalog-react';\nimport { DocsTable } from './DocsTable';\nimport * as actionFactories from './actions';\nimport * as columnFactories from './columns';\nimport { DocsTableRow } from './types';\n\nexport const EntityListDocsTable = ({\n columns,\n actions,\n}: {\n columns?: TableColumn<DocsTableRow>[];\n actions?: TableProps<DocsTableRow>['actions'];\n}) => {\n const { loading, error, entities, filters } = useEntityListProvider();\n const { isStarredEntity, toggleStarredEntity } = useStarredEntities();\n const [, copyToClipboard] = useCopyToClipboard();\n\n const title = capitalize(filters.user?.value ?? 'all');\n\n const defaultActions = [\n actionFactories.createCopyDocsUrlAction(copyToClipboard),\n actionFactories.createStarEntityAction(\n isStarredEntity,\n toggleStarredEntity,\n ),\n ];\n\n if (error) {\n return (\n <WarningPanel\n severity=\"error\"\n title=\"Could not load available documentation.\"\n >\n <CodeSnippet language=\"text\" text={error.toString()} />\n </WarningPanel>\n );\n }\n\n return (\n <DocsTable\n title={title}\n entities={entities}\n loading={loading}\n actions={actions || defaultActions}\n columns={columns}\n />\n );\n};\n\nEntityListDocsTable.columns = columnFactories;\nEntityListDocsTable.actions = actionFactories;\n","/*\n * Copyright 2021 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 from 'react';\n\nimport { PageWithHeader } from '@backstage/core-components';\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\n\ntype Props = {\n children?: React.ReactNode;\n};\n\nexport const TechDocsPageWrapper = ({ children }: Props) => {\n const configApi = useApi(configApiRef);\n const generatedSubtitle = `Documentation available in ${\n configApi.getOptionalString('organization.name') ?? 'Backstage'\n }`;\n\n return (\n <PageWithHeader\n title=\"Documentation\"\n subtitle={generatedSubtitle}\n themeId=\"documentation\"\n >\n {children}\n </PageWithHeader>\n );\n};\n","/*\n * Copyright 2021 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 { useEffect } from 'react';\nimport {\n CATALOG_FILTER_EXISTS,\n DefaultEntityFilters,\n EntityFilter,\n useEntityListProvider,\n} from '@backstage/plugin-catalog-react';\n\nclass TechDocsFilter implements EntityFilter {\n getCatalogFilters(): Record<string, string | symbol | (string | symbol)[]> {\n return {\n 'metadata.annotations.backstage.io/techdocs-ref': CATALOG_FILTER_EXISTS,\n };\n }\n}\n\ntype CustomFilters = DefaultEntityFilters & {\n techdocs?: TechDocsFilter;\n};\n\nexport const TechDocsPicker = () => {\n const { updateFilters } = useEntityListProvider<CustomFilters>();\n\n useEffect(() => {\n updateFilters({\n techdocs: new TechDocsFilter(),\n });\n }, [updateFilters]);\n\n return null;\n};\n","/*\n * Copyright 2021 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 from 'react';\nimport {\n Content,\n ContentHeader,\n SupportButton,\n TableColumn,\n TableProps,\n} from '@backstage/core-components';\nimport {\n EntityListContainer,\n FilterContainer,\n FilteredEntityLayout,\n} from '@backstage/plugin-catalog';\nimport {\n EntityListProvider,\n EntityOwnerPicker,\n EntityTagPicker,\n UserListFilterKind,\n UserListPicker,\n} from '@backstage/plugin-catalog-react';\nimport { EntityListDocsTable } from './EntityListDocsTable';\nimport { TechDocsPageWrapper } from './TechDocsPageWrapper';\nimport { TechDocsPicker } from './TechDocsPicker';\nimport { DocsTableRow } from './types';\n\nexport const DefaultTechDocsHome = ({\n initialFilter = 'all',\n columns,\n actions,\n}: {\n initialFilter?: UserListFilterKind;\n columns?: TableColumn<DocsTableRow>[];\n actions?: TableProps<DocsTableRow>['actions'];\n}) => {\n return (\n <TechDocsPageWrapper>\n <Content>\n <ContentHeader title=\"\">\n <SupportButton>\n Discover documentation in your ecosystem.\n </SupportButton>\n </ContentHeader>\n <EntityListProvider>\n <FilteredEntityLayout>\n <FilterContainer>\n <TechDocsPicker />\n <UserListPicker initialFilter={initialFilter} />\n <EntityOwnerPicker />\n <EntityTagPicker />\n </FilterContainer>\n <EntityListContainer>\n <EntityListDocsTable actions={actions} columns={columns} />\n </EntityListContainer>\n </FilteredEntityLayout>\n </EntityListProvider>\n </Content>\n </TechDocsPageWrapper>\n );\n};\n","/*\n * Copyright 2021 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 from 'react';\n\nimport { Entity } from '@backstage/catalog-model';\nimport { useApi, useRouteRef, configApiRef } from '@backstage/core-plugin-api';\nimport { Card, CardActions, CardContent, CardMedia } from '@material-ui/core';\nimport { rootDocsRouteRef } from '../../routes';\n\nimport {\n Button,\n ItemCardGrid,\n ItemCardHeader,\n} from '@backstage/core-components';\nimport { toLowerMaybe } from '../../helpers';\n\nexport const DocsCardGrid = ({\n entities,\n}: {\n entities: Entity[] | undefined;\n}) => {\n const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);\n const config = useApi(configApiRef);\n if (!entities) return null;\n return (\n <ItemCardGrid data-testid=\"docs-explore\">\n {!entities?.length\n ? null\n : entities.map((entity, index: number) => (\n <Card key={index}>\n <CardMedia>\n <ItemCardHeader\n title={entity.metadata.title ?? entity.metadata.name}\n />\n </CardMedia>\n <CardContent>{entity.metadata.description}</CardContent>\n <CardActions>\n <Button\n to={getRouteToReaderPageFor({\n namespace: toLowerMaybe(\n entity.metadata.namespace ?? 'default',\n config,\n ),\n kind: toLowerMaybe(entity.kind, config),\n name: toLowerMaybe(entity.metadata.name, config),\n })}\n color=\"primary\"\n data-testid=\"read_docs\"\n >\n Read Docs\n </Button>\n </CardActions>\n </Card>\n ))}\n </ItemCardGrid>\n );\n};\n","/*\n * Copyright 2021 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 {\n CodeSnippet,\n Progress,\n WarningPanel,\n} from '@backstage/core-components';\nimport { useEntityListProvider } from '@backstage/plugin-catalog-react';\nimport React from 'react';\nimport { DocsCardGrid } from './DocsCardGrid';\n\nexport const EntityListDocsGrid = () => {\n const { loading, error, entities } = useEntityListProvider();\n\n if (error) {\n return (\n <WarningPanel\n severity=\"error\"\n title=\"Could not load available documentation.\"\n >\n <CodeSnippet language=\"text\" text={error.toString()} />\n </WarningPanel>\n );\n }\n\n if (loading || !entities) {\n return <Progress />;\n }\n\n entities.sort((a, b) =>\n (a.metadata.title ?? a.metadata.name).localeCompare(\n b.metadata.title ?? b.metadata.name,\n ),\n );\n\n return <DocsCardGrid entities={entities} />;\n};\n","/*\n * Copyright 2020 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 { techdocsApiRef, techdocsStorageApiRef } from './api';\nimport { TechDocsClient, TechDocsStorageClient } from './client';\nimport {\n rootDocsRouteRef,\n rootRouteRef,\n rootCatalogDocsRouteRef,\n} from './routes';\nimport {\n configApiRef,\n createApiFactory,\n createComponentExtension,\n createPlugin,\n createRoutableExtension,\n discoveryApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\n\nexport const techdocsPlugin = createPlugin({\n id: 'techdocs',\n apis: [\n createApiFactory({\n api: techdocsStorageApiRef,\n deps: {\n configApi: configApiRef,\n discoveryApi: discoveryApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ configApi, discoveryApi, identityApi }) =>\n new TechDocsStorageClient({\n configApi,\n discoveryApi,\n identityApi,\n }),\n }),\n createApiFactory({\n api: techdocsApiRef,\n deps: {\n configApi: configApiRef,\n discoveryApi: discoveryApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ configApi, discoveryApi, identityApi }) =>\n new TechDocsClient({\n configApi,\n discoveryApi,\n identityApi,\n }),\n }),\n ],\n routes: {\n root: rootRouteRef,\n docRoot: rootDocsRouteRef,\n entityContent: rootCatalogDocsRouteRef,\n },\n});\n\nexport const TechdocsPage = techdocsPlugin.provide(\n createRoutableExtension({\n name: 'TechdocsPage',\n component: () => import('./Router').then(m => m.Router),\n mountPoint: rootRouteRef,\n }),\n);\n\nexport const EntityTechdocsContent = techdocsPlugin.provide(\n createRoutableExtension({\n name: 'EntityTechdocsContent',\n component: () => import('./Router').then(m => m.EmbeddedDocsRouter),\n mountPoint: rootCatalogDocsRouteRef,\n }),\n);\n\n// takes a list of entities and renders documentation cards\nexport const DocsCardGrid = techdocsPlugin.provide(\n createComponentExtension({\n name: 'DocsCardGrid',\n component: {\n lazy: () =>\n import('./home/components/DocsCardGrid').then(m => m.DocsCardGrid),\n },\n }),\n);\n\n// takes a list of entities and renders table listing documentation\nexport const DocsTable = techdocsPlugin.provide(\n createComponentExtension({\n name: 'DocsTable',\n component: {\n lazy: () => import('./home/components/DocsTable').then(m => m.DocsTable),\n },\n }),\n);\n\n// takes a custom tabs config object and renders a documentation landing page\nexport const TechDocsCustomHome = techdocsPlugin.provide(\n createRoutableExtension({\n name: 'TechDocsCustomHome',\n component: () =>\n import('./home/components/TechDocsCustomHome').then(\n m => m.TechDocsCustomHome,\n ),\n mountPoint: rootRouteRef,\n }),\n);\n\nexport const TechDocsIndexPage = techdocsPlugin.provide(\n createRoutableExtension({\n name: 'TechDocsIndexPage',\n component: () =>\n import('./home/components/TechDocsIndexPage').then(\n m => m.TechDocsIndexPage,\n ),\n mountPoint: rootRouteRef,\n }),\n);\n\nexport const TechDocsReaderPage = techdocsPlugin.provide(\n createRoutableExtension({\n name: 'TechDocsReaderPage',\n component: () =>\n import('./reader/components/TechDocsPage').then(m => m.TechDocsPage),\n mountPoint: rootDocsRouteRef,\n }),\n);\n","/*\n * Copyright 2020 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 { EntityName } from '@backstage/catalog-model';\nimport { TechDocsStorageApi } from '../../api';\nimport type { Transformer } from './transformer';\n\ntype AddBaseUrlOptions = {\n techdocsStorageApi: TechDocsStorageApi;\n entityId: EntityName;\n path: string;\n};\n\n/**\n * TechDocs backend serves SVGs with text/plain content-type for security. This\n * helper determines if an SVG is being loaded from the backend, and thus needs\n * inlining to be displayed properly.\n */\nconst isSvgNeedingInlining = (\n attrName: string,\n attrVal: string,\n apiOrigin: string,\n) => {\n const isSrcToSvg = attrName === 'src' && attrVal.endsWith('.svg');\n const isRelativeUrl = !attrVal.match(/^([a-z]*:)?\\/\\//i);\n const pointsToOurBackend = attrVal.startsWith(apiOrigin);\n return isSrcToSvg && (isRelativeUrl || pointsToOurBackend);\n};\n\nexport const addBaseUrl = ({\n techdocsStorageApi,\n entityId,\n path,\n}: AddBaseUrlOptions): Transformer => {\n return async dom => {\n const apiOrigin = await techdocsStorageApi.getApiOrigin();\n\n const updateDom = async <T extends Element>(\n list: HTMLCollectionOf<T> | NodeListOf<T>,\n attributeName: string,\n ) => {\n for (const elem of list) {\n if (elem.hasAttribute(attributeName)) {\n const elemAttribute = elem.getAttribute(attributeName);\n if (!elemAttribute) return;\n\n // Special handling for SVG images.\n const newValue = await techdocsStorageApi.getBaseUrl(\n elemAttribute,\n entityId,\n path,\n );\n\n if (isSvgNeedingInlining(attributeName, elemAttribute, apiOrigin)) {\n try {\n const svg = await fetch(newValue, { credentials: 'include' });\n const svgContent = await svg.text();\n elem.setAttribute(\n attributeName,\n `data:image/svg+xml;base64,${btoa(svgContent)}`,\n );\n } catch (e) {\n elem.setAttribute('alt', `Error: ${elemAttribute}`);\n }\n } else {\n elem.setAttribute(attributeName, newValue);\n }\n }\n }\n };\n\n await Promise.all([\n updateDom<HTMLImageElement>(dom.querySelectorAll('img'), 'src'),\n updateDom<HTMLScriptElement>(dom.querySelectorAll('script'), 'src'),\n updateDom<HTMLLinkElement>(dom.querySelectorAll('link'), 'href'),\n updateDom<HTMLAnchorElement>(dom.querySelectorAll('a[download]'), 'href'),\n ]);\n\n return dom;\n };\n};\n","/*\n * Copyright 2021 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 type { Transformer } from './index';\nimport {\n replaceGitHubUrlType,\n ScmIntegrationRegistry,\n} from '@backstage/integration';\nimport FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport parseGitUrl from 'git-url-parse';\n\n// requires repo\nexport const addGitFeedbackLink = (\n scmIntegrationsApi: ScmIntegrationRegistry,\n): Transformer => {\n return dom => {\n // attempting to use selectors that are more likely to be static as MkDocs updates over time\n const sourceAnchor = dom.querySelector(\n '[title=\"Edit this page\"]',\n ) as HTMLAnchorElement;\n\n // don't show if edit link not available in raw page\n if (!sourceAnchor || !sourceAnchor.href) {\n return dom;\n }\n\n const sourceURL = new URL(sourceAnchor.href);\n const integration = scmIntegrationsApi.byUrl(sourceURL);\n\n // don't show if can't identify edit link hostname as a gitlab/github hosting\n if (integration?.type !== 'github' && integration?.type !== 'gitlab') {\n return dom;\n }\n\n // topmost h1 only contains title for whole page\n const title = (dom.querySelector('article>h1') as HTMLElement).childNodes[0]\n .textContent;\n const issueTitle = encodeURIComponent(`Documentation Feedback: ${title}`);\n const issueDesc = encodeURIComponent(\n `Page source:\\n${sourceAnchor.href}\\n\\nFeedback:`,\n );\n\n // Convert GitHub edit url to blob type so it can be parsed by git-url-parse correctly\n const gitUrl =\n integration?.type === 'github'\n ? replaceGitHubUrlType(sourceURL.href, 'blob')\n : sourceURL.href;\n const gitInfo = parseGitUrl(gitUrl);\n const repoPath = `/${gitInfo.organization}/${gitInfo.name}`;\n\n const feedbackLink = sourceAnchor.cloneNode() as HTMLAnchorElement;\n switch (integration?.type) {\n case 'gitlab':\n feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?issue[title]=${issueTitle}&issue[description]=${issueDesc}`;\n break;\n case 'github':\n feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?title=${issueTitle}&body=${issueDesc}`;\n break;\n default:\n return dom;\n }\n ReactDOM.render(React.createElement(FeedbackOutlinedIcon), feedbackLink);\n feedbackLink.style.paddingLeft = '5px';\n feedbackLink.title = 'Leave feedback for this page';\n feedbackLink.id = 'git-feedback-link';\n sourceAnchor?.insertAdjacentElement('beforebegin', feedbackLink);\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\nexport const rewriteDocLinks = (): Transformer => {\n return dom => {\n const updateDom = <T extends Element>(\n list: Array<T>,\n attributeName: string,\n ): void => {\n Array.from(list)\n .filter(elem => elem.hasAttribute(attributeName))\n .forEach((elem: T) => {\n const elemAttribute = elem.getAttribute(attributeName);\n if (elemAttribute) {\n // if link is external, add target to open in a new window or tab\n if (elemAttribute.match(/^https?:\\/\\//i)) {\n elem.setAttribute('target', '_blank');\n }\n\n try {\n const normalizedWindowLocation = normalizeUrl(\n window.location.href,\n );\n elem.setAttribute(\n attributeName,\n new URL(elemAttribute, normalizedWindowLocation).toString(),\n );\n } catch (_e) {\n // Non-parseable links should be re-written as plain text.\n elem.replaceWith(elem.textContent || elemAttribute);\n }\n }\n });\n };\n\n updateDom(Array.from(dom.getElementsByTagName('a')), 'href');\n\n return dom;\n };\n};\n\n/** Make sure that the input url always ends with a '/' */\nexport function normalizeUrl(input: string): string {\n const url = new URL(input);\n\n if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) {\n url.pathname += '/';\n }\n\n return url.toString();\n}\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\ntype AddLinkClickListenerOptions = {\n baseUrl: string;\n onClick: (e: MouseEvent, newUrl: string) => void;\n};\n\nexport const addLinkClickListener = ({\n baseUrl,\n onClick,\n}: AddLinkClickListenerOptions): Transformer => {\n return dom => {\n Array.from(dom.getElementsByTagName('a')).forEach(elem => {\n elem.addEventListener('click', (e: MouseEvent) => {\n const target = elem as HTMLAnchorElement;\n const href = target.getAttribute('href');\n\n if (!href) return;\n if (href.startsWith(baseUrl)) {\n e.preventDefault();\n onClick(e, href);\n }\n });\n });\n\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\nexport const removeMkdocsHeader = (): Transformer => {\n return dom => {\n // Remove the header\n dom.querySelector('.md-header')?.remove();\n\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\nexport const simplifyMkdocsFooter = (): Transformer => {\n return dom => {\n // Remove mkdocs copyright\n dom.querySelector('.md-footer-copyright')?.remove();\n\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\ntype OnCssReadyOptions = {\n docStorageUrl: string;\n onLoading: (dom: Element) => void;\n onLoaded: (dom: Element) => void;\n};\n\nexport const onCssReady = ({\n docStorageUrl,\n onLoading,\n onLoaded,\n}: OnCssReadyOptions): Transformer => {\n return dom => {\n const cssPages = Array.from(\n dom.querySelectorAll('head > link[rel=\"stylesheet\"]'),\n ).filter(elem => elem.getAttribute('href')?.startsWith(docStorageUrl));\n\n let count = cssPages.length;\n\n if (count > 0) {\n onLoading(dom);\n }\n\n cssPages.forEach(cssPage =>\n cssPage.addEventListener('load', () => {\n count -= 1;\n\n if (count === 0) {\n onLoaded(dom);\n }\n }),\n );\n\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 TECHDOCS_CSS = /main\\.[A-Fa-f0-9]{8}\\.min\\.css$/;\nconst GOOGLE_FONTS = /^https:\\/\\/fonts\\.googleapis\\.com/;\nconst GSTATIC_FONTS = /^https:\\/\\/fonts\\.gstatic\\.com/;\n\nexport const safeLinksHook = (node: Element) => {\n if (node.nodeName && node.nodeName === 'LINK') {\n const href = node.getAttribute('href') || '';\n if (href.match(TECHDOCS_CSS)) {\n node.setAttribute('rel', 'stylesheet');\n }\n if (href.match(GOOGLE_FONTS)) {\n node.setAttribute('rel', 'stylesheet');\n }\n if (href.match(GSTATIC_FONTS)) {\n node.setAttribute('rel', 'preconnect');\n }\n }\n return node;\n};\n\nimport DOMPurify from 'dompurify';\nimport type { Transformer } from './transformer';\n\nexport const sanitizeDOM = (): Transformer => {\n return dom => {\n DOMPurify.addHook('afterSanitizeAttributes', safeLinksHook);\n return DOMPurify.sanitize(dom.innerHTML, {\n ADD_TAGS: ['link'],\n FORBID_TAGS: ['style'],\n WHOLE_DOCUMENT: true,\n RETURN_DOM: true,\n });\n };\n};\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\ntype InjectCssOptions = {\n css: string;\n};\n\nexport const injectCss = ({ css }: InjectCssOptions): Transformer => {\n return dom => {\n dom\n .getElementsByTagName('head')[0]\n .insertAdjacentHTML('beforeend', `<style>${css}</style>`);\n\n return dom;\n };\n};\n","/*\n * Copyright 2021 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 type { Transformer } from './transformer';\n\nexport const scrollIntoAnchor = (): Transformer => {\n return dom => {\n setTimeout(() => {\n // Scroll to the desired anchor on initial navigation\n if (window.location.hash) {\n const hash = window.location.hash.slice(1);\n dom?.querySelector(`#${hash}`)?.scrollIntoView();\n }\n }, 200);\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 type Transformer = (dom: Element) => Element | Promise<Element>;\n\nexport const transform = async (\n html: string | Element,\n transformers: Transformer[],\n): Promise<Element> => {\n let dom: Element;\n\n if (typeof html === 'string') {\n dom = new DOMParser().parseFromString(html, 'text/html').documentElement;\n } else if (html instanceof Element) {\n dom = html;\n } else {\n throw new Error('dom is not a recognized type');\n }\n\n for (const transformer of transformers) {\n dom = await transformer(dom);\n }\n\n return dom;\n};\n","/*\n * Copyright 2021 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 { EntityName } from '@backstage/catalog-model';\nimport { SearchContextProvider, useSearch } from '@backstage/plugin-search';\nimport {\n CircularProgress,\n Grid,\n IconButton,\n InputAdornment,\n TextField,\n} from '@material-ui/core';\nimport SearchIcon from '@material-ui/icons/Search';\nimport Autocomplete from '@material-ui/lab/Autocomplete';\nimport React, { ChangeEvent, useEffect, useState } from 'react';\nimport { useNavigate } from 'react-router';\nimport { useDebounce } from 'react-use';\nimport { DocsResultListItem } from '../../components/DocsResultListItem';\n\ntype TechDocsSearchProps = {\n entityId: EntityName;\n debounceTime?: number;\n};\n\ntype TechDocsDoc = {\n namespace: string;\n kind: string;\n name: string;\n path: string;\n location: string;\n title: string;\n};\n\ntype TechDocsSearchResult = {\n type: string;\n document: TechDocsDoc;\n};\n\nconst TechDocsSearchBar = ({\n entityId,\n debounceTime = 150,\n}: TechDocsSearchProps) => {\n const [open, setOpen] = useState(false);\n const navigate = useNavigate();\n const {\n term,\n setTerm,\n result: { loading, value: searchVal },\n } = useSearch();\n const [options, setOptions] = useState<any[]>([]);\n useEffect(() => {\n let mounted = true;\n\n if (mounted && searchVal) {\n // TODO: Change this into getting only subset of search results from the BE in the first place\n // once pagination is implemented for search engines\n // See: https://github.com/backstage/backstage/issues/6062\n const searchResults = searchVal.results.slice(0, 10);\n setOptions(searchResults);\n }\n return () => {\n mounted = false;\n };\n }, [loading, searchVal]);\n\n const [value, setValue] = useState<string>(term);\n\n useDebounce(() => setTerm(value), debounceTime, [value]);\n\n const handleQuery = (e: ChangeEvent<HTMLInputElement>) => {\n if (!open) {\n setOpen(true);\n }\n setValue(e.target.value);\n };\n\n const handleSelection = (_: any, selection: TechDocsSearchResult | null) => {\n if (selection?.document) {\n const { location } = selection.document;\n navigate(location);\n }\n };\n\n return (\n <Grid item xs={12}>\n <Autocomplete\n data-testid=\"techdocs-search-bar\"\n size=\"small\"\n open={open}\n getOptionLabel={() => ''}\n filterOptions={x => {\n return x; // This is needed to get renderOption to be called after options change. Bug in material-ui?\n }}\n onClose={() => {\n setOpen(false);\n }}\n onFocus={() => {\n setOpen(true);\n }}\n onChange={handleSelection}\n blurOnSelect\n noOptionsText=\"No results found\"\n value={null}\n options={options}\n renderOption={({ document }) => (\n <DocsResultListItem\n result={document}\n lineClamp={3}\n asListItem={false}\n asLink={false}\n title={document.title}\n />\n )}\n loading={loading}\n renderInput={params => (\n <TextField\n {...params}\n data-testid=\"techdocs-search-bar-input\"\n variant=\"outlined\"\n fullWidth\n placeholder={`Search ${entityId.name} docs`}\n value={value}\n onChange={handleQuery}\n InputProps={{\n ...params.InputProps,\n startAdornment: (\n <InputAdornment position=\"start\">\n <IconButton aria-label=\"Query\" disabled>\n <SearchIcon />\n </IconButton>\n </InputAdornment>\n ),\n endAdornment: (\n <React.Fragment>\n {loading ? (\n <CircularProgress color=\"inherit\" size={20} />\n ) : null}\n {params.InputProps.endAdornment}\n </React.Fragment>\n ),\n }}\n />\n )}\n />\n </Grid>\n );\n};\n\nexport const TechDocsSearch = (props: TechDocsSearchProps) => {\n const initialState = {\n term: '',\n types: ['techdocs'],\n pageCursor: '',\n filters: props.entityId,\n };\n return (\n <SearchContextProvider initialState={initialState}>\n <TechDocsSearchBar {...props} />\n </SearchContextProvider>\n );\n};\n","/*\n * Copyright 2021 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 { LogViewer } from '@backstage/core-components';\nimport {\n Button,\n createStyles,\n Drawer,\n Grid,\n IconButton,\n makeStyles,\n Theme,\n Typography,\n} from '@material-ui/core';\nimport Close from '@material-ui/icons/Close';\nimport React, { useState } from 'react';\n\nconst useDrawerStyles = makeStyles((theme: Theme) =>\n createStyles({\n paper: {\n width: '100%',\n [theme.breakpoints.up('sm')]: {\n width: '75%',\n },\n [theme.breakpoints.up('md')]: {\n width: '50%',\n },\n padding: theme.spacing(2.5),\n },\n root: {\n height: '100%',\n overflow: 'hidden',\n },\n logs: {\n background: theme.palette.background.default,\n },\n }),\n);\n\nexport const TechDocsBuildLogsDrawerContent = ({\n buildLog,\n onClose,\n}: {\n buildLog: string[];\n onClose: () => void;\n}) => {\n const classes = useDrawerStyles();\n const logText =\n buildLog.length === 0 ? 'Waiting for logs...' : buildLog.join('\\n');\n return (\n <Grid\n container\n direction=\"column\"\n className={classes.root}\n spacing={0}\n wrap=\"nowrap\"\n >\n <Grid\n item\n container\n justifyContent=\"space-between\"\n alignItems=\"center\"\n spacing={0}\n wrap=\"nowrap\"\n >\n <Typography variant=\"h5\">Build Details</Typography>\n <IconButton\n key=\"dismiss\"\n title=\"Close the drawer\"\n onClick={onClose}\n color=\"inherit\"\n >\n <Close />\n </IconButton>\n </Grid>\n <LogViewer text={logText} classes={{ root: classes.logs }} />\n </Grid>\n );\n};\n\nexport const TechDocsBuildLogs = ({ buildLog }: { buildLog: string[] }) => {\n const classes = useDrawerStyles();\n const [open, setOpen] = useState(false);\n\n return (\n <>\n <Button color=\"inherit\" onClick={() => setOpen(true)}>\n Show Build Logs\n </Button>\n <Drawer\n classes={{ paper: classes.paper }}\n anchor=\"right\"\n open={open}\n onClose={() => setOpen(false)}\n >\n <TechDocsBuildLogsDrawerContent\n buildLog={buildLog}\n onClose={() => setOpen(false)}\n />\n </Drawer>\n </>\n );\n};\n","/*\n * Copyright 2020 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 from 'react';\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\nimport { ErrorPage } from '@backstage/core-components';\n\ntype Props = {\n errorMessage?: string;\n};\n\nexport const TechDocsNotFound = ({ errorMessage }: Props) => {\n const techdocsBuilder =\n useApi(configApiRef).getOptionalString('techdocs.builder');\n\n let additionalInfo = '';\n if (techdocsBuilder !== 'local') {\n additionalInfo =\n \"Note that techdocs.builder is not set to 'local' in your config, which means this Backstage app will not \" +\n \"generate docs if they are not found. Make sure the project's docs are generated and published by some external \" +\n \"process (e.g. CI/CD pipeline). Or change techdocs.builder to 'local' to generate docs from this Backstage \" +\n 'instance.';\n }\n\n return (\n <ErrorPage\n status=\"404\"\n statusMessage={errorMessage || 'Documentation not found'}\n additionalInfo={additionalInfo}\n />\n );\n};\n","/*\n * Copyright 2021 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 from 'react';\nimport { Progress } from '@backstage/core-components';\nimport { CircularProgress, Button, makeStyles } from '@material-ui/core';\nimport { Alert } from '@material-ui/lab';\n\nimport { TechDocsBuildLogs } from './TechDocsBuildLogs';\nimport { TechDocsNotFound } from './TechDocsNotFound';\nimport { useTechDocsReader } from './Reader';\n\nconst useStyles = makeStyles(() => ({\n message: {\n // `word-break: break-word` is deprecated, but gives legacy support to browsers not supporting `overflow-wrap` yet\n // https://developer.mozilla.org/en-US/docs/Web/CSS/word-break\n wordBreak: 'break-word',\n overflowWrap: 'anywhere',\n },\n}));\n\n/**\n * Note: this component is currently being exported so that we can rapidly\n * iterate on alternative <Reader /> implementations that extend core\n * functionality. There is no guarantee that this component will continue to be\n * exported by the package in the future!\n *\n * todo: Make public or stop exporting (ctrl+f \"altReaderExperiments\")\n * @internal\n */\nexport const TechDocsStateIndicator = () => {\n let StateAlert: JSX.Element | null = null;\n const classes = useStyles();\n\n const {\n state,\n contentReload,\n contentErrorMessage,\n syncErrorMessage,\n buildLog,\n } = useTechDocsReader();\n\n const ReaderProgress = state === 'CHECKING' ? <Progress /> : null;\n\n if (state === 'INITIAL_BUILD') {\n StateAlert = (\n <Alert\n variant=\"outlined\"\n severity=\"info\"\n icon={<CircularProgress size=\"24px\" />}\n action={<TechDocsBuildLogs buildLog={buildLog} />}\n >\n Documentation is accessed for the first time and is being prepared. The\n subsequent loads are much faster.\n </Alert>\n );\n }\n\n if (state === 'CONTENT_STALE_REFRESHING') {\n StateAlert = (\n <Alert\n variant=\"outlined\"\n severity=\"info\"\n icon={<CircularProgress size=\"24px\" />}\n action={<TechDocsBuildLogs buildLog={buildLog} />}\n >\n A newer version of this documentation is being prepared and will be\n available shortly.\n </Alert>\n );\n }\n\n if (state === 'CONTENT_STALE_READY') {\n StateAlert = (\n <Alert\n variant=\"outlined\"\n severity=\"success\"\n action={\n <Button color=\"inherit\" onClick={() => contentReload()}>\n Refresh\n </Button>\n }\n >\n A newer version of this documentation is now available, please refresh\n to view.\n </Alert>\n );\n }\n\n if (state === 'CONTENT_STALE_ERROR') {\n StateAlert = (\n <Alert\n variant=\"outlined\"\n severity=\"error\"\n action={<TechDocsBuildLogs buildLog={buildLog} />}\n classes={{ message: classes.message }}\n >\n Building a newer version of this documentation failed.{' '}\n {syncErrorMessage}\n </Alert>\n );\n }\n\n if (state === 'CONTENT_NOT_FOUND') {\n StateAlert = (\n <>\n {syncErrorMessage && (\n <Alert\n variant=\"outlined\"\n severity=\"error\"\n action={<TechDocsBuildLogs buildLog={buildLog} />}\n classes={{ message: classes.message }}\n >\n Building a newer version of this documentation failed.{' '}\n {syncErrorMessage}\n </Alert>\n )}\n <TechDocsNotFound errorMessage={contentErrorMessage} />\n </>\n );\n }\n\n return (\n <>\n {ReaderProgress}\n {StateAlert}\n </>\n );\n};\n","/*\n * Copyright 2021 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 { useApi } from '@backstage/core-plugin-api';\nimport { useMemo, useReducer, useRef } from 'react';\nimport { useAsync, useAsyncRetry } from 'react-use';\nimport { techdocsStorageApiRef } from '../../api';\n\n/**\n * A state representation that is used to configure the UI of <Reader />\n */\ntype ContentStateTypes =\n /** There is nothing to display but a loading indicator */\n | 'CHECKING'\n\n /** There is no content yet -> present a full screen loading page */\n | 'INITIAL_BUILD'\n\n /** There is content, but the backend is about to update it */\n | 'CONTENT_STALE_REFRESHING'\n\n /** There is content, but after a reload, the content will be different */\n | 'CONTENT_STALE_READY'\n\n /** There is content, the backend tried to update it, but failed */\n | 'CONTENT_STALE_ERROR'\n\n /** There is nothing to see but a \"not found\" page. Is also shown on page load errors */\n | 'CONTENT_NOT_FOUND'\n\n /** There is only the latest and greatest content */\n | 'CONTENT_FRESH';\n\n/**\n * Calculate the state that should be reported to the display component.\n */\nexport function calculateDisplayState({\n contentLoading,\n content,\n activeSyncState,\n}: Pick<\n ReducerState,\n 'contentLoading' | 'content' | 'activeSyncState'\n>): ContentStateTypes {\n // we have nothing to display yet\n if (contentLoading) {\n return 'CHECKING';\n }\n\n // the build is ready, but it triggered a content reload and the content variable is not trusted\n if (activeSyncState === 'BUILD_READY_RELOAD') {\n return 'CHECKING';\n }\n\n // there is no content, but the sync process is still evaluating\n if (!content && activeSyncState === 'CHECKING') {\n return 'CHECKING';\n }\n\n // there is no content yet so we assume that we are building it for the first time\n if (!content && activeSyncState === 'BUILDING') {\n return 'INITIAL_BUILD';\n }\n\n // if there is still no content after building, it might just not exist\n if (!content) {\n return 'CONTENT_NOT_FOUND';\n }\n\n // we are still building, but we already show stale content\n if (activeSyncState === 'BUILDING') {\n return 'CONTENT_STALE_REFRESHING';\n }\n\n // the build is ready, but the content is still stale\n if (activeSyncState === 'BUILD_READY') {\n return 'CONTENT_STALE_READY';\n }\n\n // the build failed, but the content is still stale\n if (activeSyncState === 'ERROR') {\n return 'CONTENT_STALE_ERROR';\n }\n\n // seems like the content is up-to-date (or we don't know yet and the sync process is still evaluating in the background)\n return 'CONTENT_FRESH';\n}\n\n/**\n * The state of the synchronization task. It checks whether the docs are\n * up-to-date. If they aren't, it triggers a build.\n */\ntype SyncStates =\n /** Checking if it should be synced */\n | 'CHECKING'\n\n /** Building the documentation */\n | 'BUILDING'\n\n /** Finished building the documentation */\n | 'BUILD_READY'\n\n /**\n * Finished building the documentation and triggered a content reload.\n * This state is left toward UP_TO_DATE when the content loading has finished.\n */\n | 'BUILD_READY_RELOAD'\n\n /** No need for a sync. The content was already up-to-date. */\n | 'UP_TO_DATE'\n\n /** An error occurred */\n | 'ERROR';\n\ntype ReducerActions =\n | {\n type: 'sync';\n state: SyncStates;\n syncError?: Error;\n }\n | { type: 'contentLoading' }\n | {\n type: 'content';\n path?: string;\n content?: string;\n contentError?: Error;\n }\n | { type: 'buildLog'; log: string };\n\ntype ReducerState = {\n /**\n * The path of the current page\n */\n path: string;\n\n /**\n * The current sync state\n */\n activeSyncState: SyncStates;\n\n /**\n * If true, the content is downloading from the storage.\n */\n contentLoading: boolean;\n /**\n * The content that has been downloaded and should be displayed.\n */\n content?: string;\n\n contentError?: Error;\n syncError?: Error;\n\n /**\n * A list of log messages that were emitted by the build process.\n */\n buildLog: string[];\n};\n\nexport function reducer(\n oldState: ReducerState,\n action: ReducerActions,\n): ReducerState {\n const newState = { ...oldState };\n\n switch (action.type) {\n case 'sync':\n // reset the build log when a new check starts\n if (action.state === 'CHECKING') {\n newState.buildLog = [];\n }\n\n newState.activeSyncState = action.state;\n newState.syncError = action.syncError;\n break;\n\n case 'contentLoading':\n newState.contentLoading = true;\n\n // only reset errors but keep the old content until it is replaced by the 'content' action\n newState.contentError = undefined;\n break;\n\n case 'content':\n // only override the path if it is part of the action\n if (typeof action.path === 'string') {\n newState.path = action.path;\n }\n\n newState.contentLoading = false;\n newState.content = action.content;\n newState.contentError = action.contentError;\n break;\n\n case 'buildLog':\n newState.buildLog = newState.buildLog.concat(action.log);\n break;\n\n default:\n throw new Error();\n }\n\n // a content update loads fresh content so the build is updated to being up-to-date\n if (\n ['BUILD_READY', 'BUILD_READY_RELOAD'].includes(newState.activeSyncState) &&\n ['contentLoading', 'content'].includes(action.type)\n ) {\n newState.activeSyncState = 'UP_TO_DATE';\n newState.buildLog = [];\n }\n\n return newState;\n}\n\nexport function useReaderState(\n kind: string,\n namespace: string,\n name: string,\n path: string,\n): {\n state: ContentStateTypes;\n path: string;\n contentReload: () => void;\n content?: string;\n contentErrorMessage?: string;\n syncErrorMessage?: string;\n buildLog: string[];\n} {\n const [state, dispatch] = useReducer(reducer, {\n activeSyncState: 'CHECKING',\n path,\n contentLoading: true,\n buildLog: [],\n });\n\n const techdocsStorageApi = useApi(techdocsStorageApiRef);\n\n // try to load the content. the function will fire events and we don't care for the return values\n const { retry: contentReload } = useAsyncRetry(async () => {\n dispatch({ type: 'contentLoading' });\n\n try {\n const entityDocs = await techdocsStorageApi.getEntityDocs(\n { kind, namespace, name },\n path,\n );\n\n // update content and path at the same time\n dispatch({ type: 'content', content: entityDocs, path });\n\n return entityDocs;\n } catch (e) {\n dispatch({ type: 'content', contentError: e, path });\n }\n\n return undefined;\n }, [techdocsStorageApi, kind, namespace, name, path]);\n\n // create a ref that holds the latest content. This provides a useAsync hook\n // with the latest content without restarting the useAsync hook.\n const contentRef = useRef<{ content?: string; reload: () => void }>({\n content: undefined,\n reload: () => {},\n });\n contentRef.current = { content: state.content, reload: contentReload };\n\n // try to derive the state. the function will fire events and we don't care for the return values\n useAsync(async () => {\n dispatch({ type: 'sync', state: 'CHECKING' });\n\n // should only switch to BUILDING if the request takes more than 1 seconds\n const buildingTimeout = setTimeout(() => {\n dispatch({ type: 'sync', state: 'BUILDING' });\n }, 1000);\n\n try {\n const result = await techdocsStorageApi.syncEntityDocs(\n {\n kind,\n namespace,\n name,\n },\n log => {\n dispatch({ type: 'buildLog', log });\n },\n );\n\n switch (result) {\n case 'updated':\n // if there was no content prior to building, retry the loading\n if (!contentRef.current.content) {\n contentRef.current.reload();\n dispatch({ type: 'sync', state: 'BUILD_READY_RELOAD' });\n } else {\n dispatch({ type: 'sync', state: 'BUILD_READY' });\n }\n break;\n case 'cached':\n dispatch({ type: 'sync', state: 'UP_TO_DATE' });\n break;\n\n default:\n dispatch({\n type: 'sync',\n state: 'ERROR',\n syncError: new Error('Unexpected return state'),\n });\n break;\n }\n } catch (e) {\n dispatch({ type: 'sync', state: 'ERROR', syncError: e });\n } finally {\n // Cancel the timer that sets the state \"BUILDING\"\n clearTimeout(buildingTimeout);\n }\n }, [kind, name, namespace, techdocsStorageApi, dispatch, contentRef]);\n\n const displayState = useMemo(\n () =>\n calculateDisplayState({\n activeSyncState: state.activeSyncState,\n contentLoading: state.contentLoading,\n content: state.content,\n }),\n [state.activeSyncState, state.content, state.contentLoading],\n );\n\n return {\n state: displayState,\n contentReload,\n path: state.path,\n content: state.content,\n contentErrorMessage: state.contentError?.toString(),\n syncErrorMessage: state.syncError?.toString(),\n buildLog: state.buildLog,\n };\n}\n","/*\n * Copyright 2020 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, {\n PropsWithChildren,\n ComponentType,\n createContext,\n useContext,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { Grid, makeStyles, useTheme } from '@material-ui/core';\n\nimport { EntityName } from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { scmIntegrationsApiRef } from '@backstage/integration-react';\nimport { BackstageTheme } from '@backstage/theme';\n\nimport { techdocsStorageApiRef } from '../../api';\n\nimport {\n addBaseUrl,\n addGitFeedbackLink,\n addLinkClickListener,\n injectCss,\n onCssReady,\n removeMkdocsHeader,\n rewriteDocLinks,\n sanitizeDOM,\n simplifyMkdocsFooter,\n scrollIntoAnchor,\n transform as transformer,\n} from '../transformers';\n\nimport { TechDocsSearch } from './TechDocsSearch';\nimport { TechDocsStateIndicator } from './TechDocsStateIndicator';\nimport { useReaderState } from './useReaderState';\n\ntype Props = {\n entityRef: EntityName;\n withSearch?: boolean;\n onReady?: () => void;\n};\n\nconst useStyles = makeStyles<BackstageTheme>(theme => ({\n searchBar: {\n marginLeft: '20rem',\n maxWidth: 'calc(100% - 20rem * 2 - 3rem)',\n marginTop: theme.spacing(1),\n '@media screen and (max-width: 76.1875em)': {\n marginLeft: '10rem',\n maxWidth: 'calc(100% - 10rem)',\n },\n },\n}));\n\ntype TechDocsReaderValue = ReturnType<typeof useReaderState>;\n\nconst TechDocsReaderContext = createContext<TechDocsReaderValue>(\n {} as TechDocsReaderValue,\n);\n\nconst TechDocsReaderProvider = ({\n children,\n entityRef,\n}: PropsWithChildren<{ entityRef: EntityName }>) => {\n const { '*': path } = useParams();\n const { kind, namespace, name } = entityRef;\n const value = useReaderState(kind, namespace, name, path);\n return (\n <TechDocsReaderContext.Provider value={value}>\n {children}\n </TechDocsReaderContext.Provider>\n );\n};\n\n/**\n * Note: this HOC is currently being exported so that we can rapidly\n * iterate on alternative <Reader /> implementations that extend core\n * functionality. There is no guarantee that this HOC will continue to be\n * exported by the package in the future!\n *\n * todo: Make public or stop exporting (ctrl+f \"altReaderExperiments\")\n * @internal\n */\nexport const withTechDocsReaderProvider =\n <T extends {}>(Component: ComponentType<T>, entityRef: EntityName) =>\n (props: T) =>\n (\n <TechDocsReaderProvider entityRef={entityRef}>\n <Component {...props} />\n </TechDocsReaderProvider>\n );\n\n/**\n * Note: this hook is currently being exported so that we can rapidly\n * iterate on alternative <Reader /> implementations that extend core\n * functionality. There is no guarantee that this hook will continue to be\n * exported by the package in the future!\n *\n * todo: Make public or stop exporting (ctrl+f \"altReaderExperiments\")\n * @internal\n */\nexport const useTechDocsReader = () => useContext(TechDocsReaderContext);\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 *\n * Note: this hook is currently being exported so that we can rapidly iterate\n * on alternative <Reader /> implementations that extend core functionality.\n * There is no guarantee that this hook will continue to be exported by the\n * package in the future!\n *\n * todo: Make public or stop exporting (see others: \"altReaderExperiments\")\n * @internal\n */\nexport const useTechDocsReaderDom = (entityRef: EntityName): Element | null => {\n const navigate = useNavigate();\n const theme = useTheme<BackstageTheme>();\n const techdocsStorageApi = useApi(techdocsStorageApiRef);\n const scmIntegrationsApi = useApi(scmIntegrationsApiRef);\n const { namespace = '', kind = '', name = '' } = entityRef;\n const { state, path, content: rawPage } = useTechDocsReader();\n\n const [sidebars, setSidebars] = useState<HTMLElement[]>();\n const [dom, setDom] = useState<HTMLElement | null>(null);\n\n const updateSidebarPosition = useCallback(() => {\n if (!dom || !sidebars) return;\n // set sidebar height so they don't initially render in wrong position\n const mdTabs = dom.querySelector('.md-container > .md-tabs');\n sidebars.forEach(sidebar => {\n const newTop = Math.max(dom.getBoundingClientRect().top, 0);\n sidebar.style.top = mdTabs\n ? `${newTop + mdTabs.getBoundingClientRect().height}px`\n : `${newTop}px`;\n });\n }, [dom, sidebars]);\n\n useEffect(() => {\n updateSidebarPosition();\n window.addEventListener('scroll', updateSidebarPosition, true);\n window.addEventListener('resize', updateSidebarPosition);\n return () => {\n window.removeEventListener('scroll', updateSidebarPosition, true);\n window.removeEventListener('resize', updateSidebarPosition);\n };\n // an update to \"state\" might lead to an updated UI so we include it as a trigger\n }, [updateSidebarPosition, state]);\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 sanitizeDOM(),\n addBaseUrl({\n techdocsStorageApi,\n entityId: {\n kind,\n name,\n namespace,\n },\n path: contentPath,\n }),\n rewriteDocLinks(),\n removeMkdocsHeader(),\n simplifyMkdocsFooter(),\n addGitFeedbackLink(scmIntegrationsApi),\n injectCss({\n css: `\n body {\n font-family: ${theme.typography.fontFamily};\n --md-text-color: ${theme.palette.text.primary};\n --md-text-link-color: ${theme.palette.primary.main};\n\n --md-code-fg-color: ${theme.palette.text.primary};\n --md-code-bg-color: ${theme.palette.background.paper};\n }\n .md-main__inner { margin-top: 0; }\n .md-sidebar { position: fixed; bottom: 100px; width: 20rem; }\n .md-sidebar--secondary { right: 2rem; }\n .md-content { margin-bottom: 50px }\n .md-footer { position: fixed; bottom: 0px; width: 100vw; }\n .md-footer-nav__link { width: 20rem;}\n .md-content { margin-left: 20rem; max-width: calc(100% - 20rem * 2 - 3rem); }\n .md-typeset { font-size: 1rem; }\n .md-typeset h1, .md-typeset h2, .md-typeset h3 { font-weight: bold; }\n .md-nav { font-size: 1rem; }\n .md-grid { max-width: 90vw; margin: 0 }\n .md-typeset table:not([class]) {\n font-size: 1rem;\n border: 1px solid ${theme.palette.text.primary};\n border-bottom: none;\n border-collapse: collapse;\n }\n .md-typeset table:not([class]) td, .md-typeset table:not([class]) th {\n border-bottom: 1px solid ${theme.palette.text.primary};\n }\n .md-typeset table:not([class]) th { font-weight: bold; }\n .md-typeset .admonition, .md-typeset details {\n font-size: 1rem;\n }\n \n /* style the checkmarks of the task list */\n .md-typeset .task-list-control .task-list-indicator::before {\n background-color: ${theme.palette.action.disabledBackground};\n }\n .md-typeset .task-list-control [type=\"checkbox\"]:checked + .task-list-indicator:before {\n background-color: ${theme.palette.success.main};\n }\n /**/\n\n @media screen and (max-width: 76.1875em) {\n .md-nav {\n background-color: ${theme.palette.background.default};\n transition: none !important\n }\n .md-sidebar--secondary { display: none; }\n .md-sidebar--primary { left: 72px; width: 10rem }\n .md-content { margin-left: 10rem; max-width: calc(100% - 10rem); }\n .md-content__inner { font-size: 0.9rem }\n .md-footer {\n position: static;\n margin-left: 10rem;\n width: calc(100% - 10rem);\n }\n .md-nav--primary .md-nav__title {\n white-space: normal;\n height: auto;\n line-height: 1rem;\n cursor: auto;\n }\n .md-nav--primary > .md-nav__title [for=\"none\"] {\n padding-top: 0;\n }\n }\n `,\n }),\n injectCss({\n // Disable CSS animations on link colors as they lead to issues in dark\n // mode. The dark mode color theme is applied later and theirfore there\n // is always an animation from light to dark mode when navigation\n // between pages.\n css: `\n .md-nav__link, .md-typeset a, .md-typeset a::before, .md-typeset .headerlink {\n transition: none;\n }\n `,\n }),\n injectCss({\n // Properly style code blocks.\n css: `\n .md-typeset pre > code::-webkit-scrollbar-thumb {\n background-color: hsla(0, 0%, 0%, 0.32);\n }\n .md-typeset pre > code::-webkit-scrollbar-thumb:hover {\n background-color: hsla(0, 0%, 0%, 0.87);\n }\n `,\n }),\n injectCss({\n // Admonitions and others are using SVG masks to define icons. These\n // masks are defined as CSS variables.\n // As the MkDocs output is rendered in shadow DOM, the CSS variable\n // definitions on the root selector are not applied. Instead, the have\n // to be applied on :host.\n // As there is no way to transform the served main*.css yet (for\n // example in the backend), we have to copy from main*.css and modify\n // them.\n css: `\n :host {\n --md-admonition-icon--note: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z\"/></svg>');\n --md-admonition-icon--abstract: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M4 5h16v2H4V5m0 4h16v2H4V9m0 4h16v2H4v-2m0 4h10v2H4v-2z\"/></svg>');\n --md-admonition-icon--info: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 002 12a10 10 0 0010 10 10 10 0 0010-10A10 10 0 0012 2z\"/></svg>');\n --md-admonition-icon--tip: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M17.55 11.2c-.23-.3-.5-.56-.76-.82-.65-.6-1.4-1.03-2.03-1.66C13.3 7.26 13 4.85 13.91 3c-.91.23-1.75.75-2.45 1.32-2.54 2.08-3.54 5.75-2.34 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.22.1-.46.04-.64-.12a.83.83 0 01-.15-.17c-1.1-1.43-1.28-3.48-.53-5.12C5.89 10 5 12.3 5.14 14.47c.04.5.1 1 .27 1.5.14.6.4 1.2.72 1.73 1.04 1.73 2.87 2.97 4.84 3.22 2.1.27 4.35-.12 5.96-1.6 1.8-1.66 2.45-4.32 1.5-6.6l-.13-.26c-.2-.46-.47-.87-.8-1.25l.05-.01m-3.1 6.3c-.28.24-.73.5-1.08.6-1.1.4-2.2-.16-2.87-.82 1.19-.28 1.89-1.16 2.09-2.05.17-.8-.14-1.46-.27-2.23-.12-.74-.1-1.37.18-2.06.17.38.37.76.6 1.06.76 1 1.95 1.44 2.2 2.8.04.14.06.28.06.43.03.82-.32 1.72-.92 2.27h.01z\"/></svg>');\n --md-admonition-icon--success: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2m-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>');\n --md-admonition-icon--question: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M15.07 11.25l-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 00-2-2 2 2 0 00-2 2H8a4 4 0 014-4 4 4 0 014 4 3.2 3.2 0 01-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 002 12a10 10 0 0010 10 10 10 0 0010-10c0-5.53-4.5-10-10-10z\"/></svg>');\n --md-admonition-icon--warning: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M13 14h-2v-4h2m0 8h-2v-2h2M1 21h22L12 2 1 21z\"/></svg>');\n --md-admonition-icon--failure: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M12 2c5.53 0 10 4.47 10 10s-4.47 10-10 10S2 17.53 2 12 6.47 2 12 2m3.59 5L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41 15.59 7z\"/></svg>');\n --md-admonition-icon--danger: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M11.5 20l4.86-9.73H13V4l-5 9.73h3.5V20M12 2c2.75 0 5.1 1 7.05 2.95C21 6.9 22 9.25 22 12s-1 5.1-2.95 7.05C17.1 21 14.75 22 12 22s-5.1-1-7.05-2.95C3 17.1 2 14.75 2 12s1-5.1 2.95-7.05C6.9 3 9.25 2 12 2z\"/></svg>');\n --md-admonition-icon--bug: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 00-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 00-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z\"/></svg>');\n --md-admonition-icon--example: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 01.75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z\"/></svg>');\n --md-admonition-icon--quote: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z\"/></svg>');\n }\n :host {\n --md-footnotes-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.42L5.83 13H21V7h-2z\"/></svg>');\n }\n :host {\n --md-details-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M8.59 16.58L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42z\"/></svg>');\n }\n :host {\n --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z\"/></svg>');\n --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>');\n }\n `,\n }),\n ]),\n [\n kind,\n name,\n namespace,\n scmIntegrationsApi,\n techdocsStorageApi,\n theme.palette.action.disabledBackground,\n theme.palette.background.default,\n theme.palette.background.paper,\n theme.palette.primary.main,\n theme.palette.success.main,\n theme.palette.text.primary,\n theme.typography.fontFamily,\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 scrollIntoAnchor(),\n addLinkClickListener({\n baseUrl: window.location.origin,\n onClick: (_: MouseEvent, url: string) => {\n const parsedUrl = new URL(url);\n // hash exists when anchor is clicked on secondary sidebar\n if (parsedUrl.hash) {\n navigate(`${parsedUrl.pathname}${parsedUrl.hash}`);\n // Scroll to hash if it's on the current page\n transformedElement\n ?.querySelector(`#${parsedUrl.hash.slice(1)}`)\n ?.scrollIntoView();\n } else {\n navigate(parsedUrl.pathname);\n // Scroll to top of reader if primary sidebar link is clicked\n transformedElement\n ?.querySelector('.md-content__inner')\n ?.scrollIntoView();\n }\n },\n }),\n onCssReady({\n docStorageUrl: await techdocsStorageApi.getApiOrigin(),\n onLoading: (renderedElement: Element) => {\n (renderedElement as HTMLElement).style.setProperty('opacity', '0');\n },\n onLoaded: (renderedElement: Element) => {\n (renderedElement as HTMLElement).style.removeProperty('opacity');\n // disable MkDocs drawer toggling ('for' attribute => checkbox mechanism)\n renderedElement\n .querySelector('.md-nav__title')\n ?.removeAttribute('for');\n setSidebars(\n Array.from(renderedElement.querySelectorAll('.md-sidebar')),\n );\n },\n }),\n ]),\n [navigate, techdocsStorageApi],\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\nconst TheReader = ({\n entityRef,\n onReady = () => {},\n withSearch = true,\n}: Props) => {\n const classes = useStyles();\n const dom = useTechDocsReaderDom(entityRef);\n const shadowDomRef = useRef<HTMLDivElement>(null);\n\n const onReadyRef = useRef<() => void>(onReady);\n useEffect(() => {\n onReadyRef.current = onReady;\n }, [onReady]);\n\n useEffect(() => {\n if (!dom || !shadowDomRef.current) return;\n const shadowDiv = shadowDomRef.current;\n const shadowRoot =\n shadowDiv.shadowRoot || shadowDiv.attachShadow({ mode: 'open' });\n Array.from(shadowRoot.children).forEach(child =>\n shadowRoot.removeChild(child),\n );\n shadowRoot.appendChild(dom);\n onReadyRef.current();\n\n // this hook must ONLY be triggered by a changed dom\n }, [dom]);\n\n return (\n <>\n <TechDocsStateIndicator />\n {withSearch && shadowDomRef?.current?.shadowRoot?.innerHTML && (\n <Grid container className={classes.searchBar}>\n <TechDocsSearch entityId={entityRef} />\n </Grid>\n )}\n <div data-testid=\"techdocs-content-shadowroot\" ref={shadowDomRef} />\n </>\n );\n};\n\nexport const Reader = ({\n entityRef,\n onReady = () => {},\n withSearch = true,\n}: Props) => (\n <TechDocsReaderProvider entityRef={entityRef}>\n <TheReader\n entityRef={entityRef}\n onReady={onReady}\n withSearch={withSearch}\n />\n </TechDocsReaderProvider>\n);\n","/*\n * Copyright 2020 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 { EntityName, RELATION_OWNED_BY } from '@backstage/catalog-model';\nimport { Header, HeaderLabel } from '@backstage/core-components';\nimport { useRouteRef } from '@backstage/core-plugin-api';\nimport {\n EntityRefLink,\n EntityRefLinks,\n getEntityRelations,\n} from '@backstage/plugin-catalog-react';\nimport CodeIcon from '@material-ui/icons/Code';\nimport React from 'react';\nimport { rootRouteRef } from '../../routes';\nimport { TechDocsEntityMetadata, TechDocsMetadata } from '../../types';\n\nexport type TechDocsPageHeaderProps = {\n entityRef: EntityName;\n entityMetadata?: TechDocsEntityMetadata;\n techDocsMetadata?: TechDocsMetadata;\n};\n\nexport const TechDocsPageHeader = ({\n entityRef,\n entityMetadata,\n techDocsMetadata,\n}: TechDocsPageHeaderProps) => {\n const { name } = entityRef;\n\n const { site_name: siteName, site_description: siteDescription } =\n techDocsMetadata || {};\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=\"Component\"\n value={\n <EntityRefLink\n color=\"inherit\"\n entityRef={entityRef}\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 ? <HeaderLabel label=\"Lifecycle\" value={lifecycle} /> : null}\n {locationMetadata &&\n locationMetadata.type !== 'dir' &&\n locationMetadata.type !== 'file' ? (\n <HeaderLabel\n label=\"\"\n value={\n <a\n href={locationMetadata.target}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <CodeIcon style={{ marginTop: '-25px', fill: '#fff' }} />\n </a>\n }\n />\n ) : null}\n </>\n );\n\n return (\n <Header\n title={siteName ? siteName : '.'}\n pageTitleOverride={siteName || name}\n subtitle={\n siteDescription && siteDescription !== 'None' ? siteDescription : ''\n }\n type=\"Docs\"\n typeLink={docsRootLink}\n >\n {labels}\n </Header>\n );\n};\n","/*\n * Copyright 2020 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, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useAsync } from 'react-use';\nimport { techdocsApiRef } from '../../api';\nimport { TechDocsNotFound } from './TechDocsNotFound';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { Page, Content } from '@backstage/core-components';\nimport { Reader } from './Reader';\nimport { TechDocsPageHeader } from './TechDocsPageHeader';\n\nexport const LegacyTechDocsPage = () => {\n const [documentReady, setDocumentReady] = useState<boolean>(false);\n const { namespace, kind, name } = useParams();\n\n const techdocsApi = useApi(techdocsApiRef);\n\n const { value: techdocsMetadataValue } = useAsync(() => {\n if (documentReady) {\n return techdocsApi.getTechDocsMetadata({ kind, namespace, name });\n }\n\n return Promise.resolve(undefined);\n }, [kind, namespace, name, techdocsApi, documentReady]);\n\n const { value: entityMetadataValue, error: entityMetadataError } =\n useAsync(() => {\n return techdocsApi.getEntityMetadata({ kind, namespace, name });\n }, [kind, namespace, name, techdocsApi]);\n\n const onReady = useCallback(() => {\n setDocumentReady(true);\n }, [setDocumentReady]);\n\n if (entityMetadataError) {\n return <TechDocsNotFound errorMessage={entityMetadataError.message} />;\n }\n\n return (\n <Page themeId=\"documentation\">\n <TechDocsPageHeader\n techDocsMetadata={techdocsMetadataValue}\n entityMetadata={entityMetadataValue}\n entityRef={{\n kind,\n namespace,\n name,\n }}\n />\n <Content data-testid=\"techdocs-content\">\n <Reader\n onReady={onReady}\n entityRef={{\n kind,\n namespace,\n name,\n }}\n />\n </Content>\n </Page>\n );\n};\n","/*\n * Copyright 2020 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, useState } from 'react';\nimport { useOutlet } from 'react-router';\nimport { useParams } from 'react-router-dom';\nimport { useAsync } from 'react-use';\nimport { techdocsApiRef } from '../../api';\nimport { TechDocsNotFound } from './TechDocsNotFound';\nimport { LegacyTechDocsPage } from './LegacyTechDocsPage';\nimport { TechDocsEntityMetadata, TechDocsMetadata } from '../../types';\nimport { EntityName } from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { Page } from '@backstage/core-components';\n\nexport type TechDocsPageRenderFunction = ({\n techdocsMetadataValue,\n entityMetadataValue,\n entityRef,\n}: {\n techdocsMetadataValue?: TechDocsMetadata | undefined;\n entityMetadataValue?: TechDocsEntityMetadata | undefined;\n entityRef: EntityName;\n onReady: () => void;\n}) => JSX.Element;\n\nexport type TechDocsPageProps = {\n children?: TechDocsPageRenderFunction | React.ReactNode;\n};\n\nexport const TechDocsPage = ({ children }: TechDocsPageProps) => {\n const outlet = useOutlet();\n\n const [documentReady, setDocumentReady] = useState<boolean>(false);\n const { namespace, kind, name } = useParams();\n\n const techdocsApi = useApi(techdocsApiRef);\n\n const { value: techdocsMetadataValue } = useAsync(() => {\n if (documentReady) {\n return techdocsApi.getTechDocsMetadata({ kind, namespace, name });\n }\n\n return Promise.resolve(undefined);\n }, [kind, namespace, name, techdocsApi, documentReady]);\n\n const { value: entityMetadataValue, error: entityMetadataError } =\n useAsync(() => {\n return techdocsApi.getEntityMetadata({ kind, namespace, name });\n }, [kind, namespace, name, techdocsApi]);\n\n const onReady = useCallback(() => {\n setDocumentReady(true);\n }, [setDocumentReady]);\n\n if (entityMetadataError) {\n return <TechDocsNotFound errorMessage={entityMetadataError.message} />;\n }\n\n if (!children) return outlet || <LegacyTechDocsPage />;\n\n return (\n <Page themeId=\"documentation\">\n {children instanceof Function\n ? children({\n techdocsMetadataValue,\n entityMetadataValue,\n entityRef: { kind, namespace, name },\n onReady,\n })\n : children}\n </Page>\n );\n};\n","/*\n * Copyright 2021 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, { useState } from 'react';\nimport { useAsync } from 'react-use';\nimport { makeStyles } from '@material-ui/core';\nimport { CSSProperties } from '@material-ui/styles';\nimport {\n CATALOG_FILTER_EXISTS,\n catalogApiRef,\n CatalogApi,\n isOwnerOf,\n useOwnUser,\n} from '@backstage/plugin-catalog-react';\nimport { Entity } from '@backstage/catalog-model';\nimport { DocsTable } from './DocsTable';\nimport { DocsCardGrid } from './DocsCardGrid';\nimport { TechDocsPageWrapper } from './TechDocsPageWrapper';\n\nimport {\n CodeSnippet,\n Content,\n HeaderTabs,\n Progress,\n WarningPanel,\n SupportButton,\n ContentHeader,\n} from '@backstage/core-components';\n\nimport { useApi } from '@backstage/core-plugin-api';\n\nconst panels = {\n DocsTable: DocsTable,\n DocsCardGrid: DocsCardGrid,\n};\n\nexport type PanelType = 'DocsCardGrid' | 'DocsTable';\n\nexport interface PanelConfig {\n title: string;\n description: string;\n panelType: PanelType;\n panelCSS?: CSSProperties;\n filterPredicate: ((entity: Entity) => boolean) | string;\n}\n\nexport interface TabConfig {\n label: string;\n panels: PanelConfig[];\n}\n\nexport type TabsConfig = TabConfig[];\n\nconst CustomPanel = ({\n config,\n entities,\n index,\n}: {\n config: PanelConfig;\n entities: Entity[];\n index: number;\n}) => {\n const useStyles = makeStyles({\n panelContainer: {\n marginBottom: '2rem',\n ...(config.panelCSS ? config.panelCSS : {}),\n },\n });\n const classes = useStyles();\n const { value: user } = useOwnUser();\n\n const Panel = panels[config.panelType];\n\n const shownEntities = entities.filter(entity => {\n if (config.filterPredicate === 'ownedByUser') {\n if (!user) {\n return false;\n }\n return isOwnerOf(user, entity);\n }\n\n return (\n typeof config.filterPredicate === 'function' &&\n config.filterPredicate(entity)\n );\n });\n\n return (\n <>\n <ContentHeader title={config.title} description={config.description}>\n {index === 0 ? (\n <SupportButton>\n Discover documentation in your ecosystem.\n </SupportButton>\n ) : null}\n </ContentHeader>\n <div className={classes.panelContainer}>\n <Panel data-testid=\"techdocs-custom-panel\" entities={shownEntities} />\n </div>\n </>\n );\n};\n\nexport const TechDocsCustomHome = ({\n tabsConfig,\n}: {\n tabsConfig: TabsConfig;\n}) => {\n const [selectedTab, setSelectedTab] = useState<number>(0);\n const catalogApi: CatalogApi = useApi(catalogApiRef);\n\n const {\n value: entities,\n loading,\n error,\n } = useAsync(async () => {\n const response = await catalogApi.getEntities({\n filter: {\n 'metadata.annotations.backstage.io/techdocs-ref': CATALOG_FILTER_EXISTS,\n },\n fields: [\n 'apiVersion',\n 'kind',\n 'metadata',\n 'relations',\n 'spec.owner',\n 'spec.type',\n ],\n });\n return response.items.filter((entity: Entity) => {\n return !!entity.metadata.annotations?.['backstage.io/techdocs-ref'];\n });\n });\n\n const currentTabConfig = tabsConfig[selectedTab];\n\n if (loading) {\n return (\n <TechDocsPageWrapper>\n <Content>\n <Progress />\n </Content>\n </TechDocsPageWrapper>\n );\n }\n\n if (error) {\n return (\n <TechDocsPageWrapper>\n <Content>\n <WarningPanel\n severity=\"error\"\n title=\"Could not load available documentation.\"\n >\n <CodeSnippet language=\"text\" text={error.toString()} />\n </WarningPanel>\n </Content>\n </TechDocsPageWrapper>\n );\n }\n\n return (\n <TechDocsPageWrapper>\n <HeaderTabs\n selectedIndex={selectedTab}\n onChange={index => setSelectedTab(index)}\n tabs={tabsConfig.map(({ label }, index) => ({\n id: index.toString(),\n label,\n }))}\n />\n <Content data-testid=\"techdocs-content\">\n {currentTabConfig.panels.map((config, index) => (\n <CustomPanel\n key={index}\n config={config}\n entities={!!entities ? entities : []}\n index={index}\n />\n ))}\n </Content>\n </TechDocsPageWrapper>\n );\n};\n","/*\n * Copyright 2021 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 from 'react';\nimport { PanelType, TechDocsCustomHome } from './TechDocsCustomHome';\n\nexport const LegacyTechDocsHome = () => {\n const tabsConfig = [\n {\n label: 'Overview',\n panels: [\n {\n title: 'Overview',\n description:\n 'Explore your internal technical ecosystem through documentation.',\n panelType: 'DocsCardGrid' as PanelType,\n filterPredicate: () => true,\n },\n // uncomment this if you would like to have a secondary panel with owned documents\n // {\n // title: 'Owned',\n // description: 'Explore your owned internal documentation.',\n // panelType: 'DocsCardGrid' as PanelType,\n // filterPredicate: 'ownedByUser',\n // },\n ],\n },\n {\n label: 'Owned Documents',\n panels: [\n {\n title: 'Owned documents',\n description: 'Access your documentation.',\n panelType: 'DocsTable' as PanelType,\n // ownedByUser filters out entities owned by signed in user\n filterPredicate: 'ownedByUser',\n },\n ],\n },\n ];\n return <TechDocsCustomHome tabsConfig={tabsConfig} />;\n};\n","/*\n * Copyright 2021 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 from 'react';\nimport { useOutlet } from 'react-router';\nimport { LegacyTechDocsHome } from './LegacyTechDocsHome';\n\nexport const TechDocsIndexPage = () => {\n const outlet = useOutlet();\n\n return outlet || <LegacyTechDocsHome />;\n};\n","/*\n * Copyright 2020 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 from 'react';\nimport { Entity } from '@backstage/catalog-model';\nimport { Reader } from './reader';\nimport { toLowerMaybe } from './helpers';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\n\nexport const EntityPageDocs = ({ entity }: { entity: Entity }) => {\n const config = useApi(configApiRef);\n return (\n <Reader\n withSearch={false}\n entityRef={{\n namespace: toLowerMaybe(entity.metadata.namespace ?? 'default', config),\n kind: toLowerMaybe(entity.kind, config),\n name: toLowerMaybe(entity.metadata.name, config),\n }}\n />\n );\n};\n","/*\n * Copyright 2020 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 from 'react';\nimport { Entity } from '@backstage/catalog-model';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { Route, Routes } from 'react-router-dom';\nimport { TechDocsIndexPage } from './home/components/TechDocsIndexPage';\nimport { TechDocsPage as TechDocsReaderPage } from './reader/components/TechDocsPage';\nimport { EntityPageDocs } from './EntityPageDocs';\nimport { MissingAnnotationEmptyState } from '@backstage/core-components';\n\nconst TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref';\n\nexport const isTechDocsAvailable = (entity: Entity) =>\n Boolean(entity?.metadata?.annotations?.[TECHDOCS_ANNOTATION]);\n\nexport const Router = () => {\n return (\n <Routes>\n <Route path=\"/\" element={<TechDocsIndexPage />} />\n <Route\n path=\"/:namespace/:kind/:name/*\"\n element={<TechDocsReaderPage />}\n />\n </Routes>\n );\n};\n\ntype Props = {\n /** @deprecated The entity is now grabbed from context instead */\n entity?: Entity;\n};\n\nexport const EmbeddedDocsRouter = (_props: Props) => {\n const { entity } = useEntity();\n\n const projectId = entity.metadata.annotations?.[TECHDOCS_ANNOTATION];\n\n if (!projectId) {\n return <MissingAnnotationEmptyState annotation={TECHDOCS_ANNOTATION} />;\n }\n\n return (\n <Routes>\n <Route path=\"/*\" element={<EntityPageDocs entity={entity} />} />\n </Routes>\n );\n};\n"],"names":["useStyles","DocsTable","columnFactories.createNameColumn","columnFactories.createOwnerColumn","columnFactories.createTypeColumn","actionFactories.createCopyDocsUrlAction","actionFactories.createStarEntityAction","DocsCardGrid","TechDocsCustomHome","TechDocsIndexPage","Button","useNavigate","transformer","TechDocsReaderPage"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;MAoBa,wBAAwB,aAAiC;AAAA,EACpE,IAAI;AAAA,EACJ,aAAa;AAAA;MAGF,iBAAiB,aAA0B;AAAA,EACtD,IAAI;AAAA,EACJ,aAAa;AAAA;;qBCEoC;AAAA,EAKjD,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,KAKC;AACD,SAAK,YAAY;AACjB,SAAK,eAAe;AACpB,SAAK,cAAc;AAAA;AAAA,QAGf,eAAgC;AAhDxC;AAiDI,WACE,WAAK,UAAU,kBAAkB,2BAAjC,YACC,MAAM,KAAK,aAAa,WAAW;AAAA;AAAA,QAalC,oBAAoB,UAAiD;AACzE,UAAM,EAAE,MAAM,WAAW,SAAS;AAElC,UAAM,YAAY,MAAM,KAAK;AAC7B,UAAM,aAAa,GAAG,+BAA+B,aAAa,QAAQ;AAC1E,UAAM,QAAQ,MAAM,KAAK,YAAY;AAErC,UAAM,UAAU,MAAM,MAAM,GAAG,cAAc;AAAA,MAC3C,SAAS,QAAQ,EAAE,eAAe,UAAU,YAAY;AAAA;AAG1D,QAAI,CAAC,QAAQ,IAAI;AACf,YAAM,MAAM,cAAc,aAAa;AAAA;AAGzC,WAAO,MAAM,QAAQ;AAAA;AAAA,QAWjB,kBACJ,UACiC;AACjC,UAAM,EAAE,MAAM,WAAW,SAAS;AAElC,UAAM,YAAY,MAAM,KAAK;AAC7B,UAAM,aAAa,GAAG,6BAA6B,aAAa,QAAQ;AACxE,UAAM,QAAQ,MAAM,KAAK,YAAY;AAErC,UAAM,UAAU,MAAM,MAAM,GAAG,cAAc;AAAA,MAC3C,SAAS,QAAQ,EAAE,eAAe,UAAU,YAAY;AAAA;AAG1D,QAAI,CAAC,QAAQ,IAAI;AACf,YAAM,MAAM,cAAc,aAAa;AAAA;AAGzC,WAAO,MAAM,QAAQ;AAAA;AAAA;4BASwC;AAAA,EAK/D,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,KAKC;AACD,SAAK,YAAY;AACjB,SAAK,eAAe;AACpB,SAAK,cAAc;AAAA;AAAA,QAGf,eAAgC;AAvIxC;AAwII,WACE,WAAK,UAAU,kBAAkB,2BAAjC,YACC,MAAM,KAAK,aAAa,WAAW;AAAA;AAAA,QAIlC,gBAAiC;AA9IzC;AA+II,WACE,WAAK,UAAU,kBAAkB,2BAAjC,YACA,GAAG,MAAM,KAAK,aAAa,WAAW;AAAA;AAAA,QAIpC,aAA8B;AAClC,WAAO,KAAK,UAAU,UAAU;AAAA;AAAA,QAW5B,cAAc,UAAsB,MAA+B;AACvE,UAAM,EAAE,MAAM,WAAW,SAAS;AAElC,UAAM,aAAa,MAAM,KAAK;AAC9B,UAAM,MAAM,GAAG,cAAc,aAAa,QAAQ,QAAQ;AAC1D,UAAM,QAAQ,MAAM,KAAK,YAAY;AAErC,UAAM,UAAU,MAAM,MACpB,GAAG,IAAI,SAAS,OAAO,MAAM,GAAG,oBAChC;AAAA,MACE,SAAS,QAAQ,EAAE,eAAe,UAAU,YAAY;AAAA;AAI5D,QAAI,eAAe;AACnB,YAAQ,QAAQ;AAAA,WACT;AACH,uBAAe;AAEf,YAAI,CAAC,MAAM;AACT,0BACE;AAAA;AAEJ,cAAM,IAAI,cAAc;AAAA,WACrB;AACH,uBACE;AACF,cAAM,IAAI,MAAM;AAGhB;AAGJ,WAAO,QAAQ;AAAA;AAAA,QAWX,eACJ,UACA,aAAqC,MAAM;AAAA,KACtB;AACrB,UAAM,EAAE,MAAM,WAAW,SAAS;AAElC,UAAM,YAAY,MAAM,KAAK;AAC7B,UAAM,MAAM,GAAG,kBAAkB,aAAa,QAAQ;AACtD,UAAM,QAAQ,MAAM,KAAK,YAAY;AAErC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AAEtC,YAAM,SAAS,IAAI,oBAAoB,KAAK;AAAA,QAC1C,iBAAiB;AAAA,QACjB,SAAS,QAAQ,EAAE,eAAe,UAAU,YAAY;AAAA;AAG1D,aAAO,iBAAiB,OAAO,CAAC,MAAW;AACzC,YAAI,EAAE,MAAM;AACV,qBAAW,KAAK,MAAM,EAAE;AAAA;AAAA;AAI5B,aAAO,iBAAiB,UAAU,CAAC,MAAW;AAC5C,YAAI,UAAmB;AAEvB,YAAI,EAAE,MAAM;AACV,UAAC,GAAE,YAAY,KAAK,MAAM,EAAE;AAAA;AAG9B,gBAAQ,UAAU,YAAY;AAAA;AAGhC,aAAO,UAAU,CAAC,MAAW;AAC3B,eAAO;AAEP,gBAAQ,EAAE;AAAA,eAEH;AACH,mBAAO,IAAI,cAAc,EAAE;AAC3B;AAAA;AAKA,mBAAO,IAAI,MAAM,EAAE;AACnB;AAAA;AAAA;AAAA;AAAA;AAAA,QAMJ,WACJ,YACA,UACA,MACiB;AACjB,UAAM,EAAE,MAAM,WAAW,SAAS;AAElC,UAAM,YAAY,MAAM,KAAK;AAC7B,UAAM,aAAa,GAAG,yBAAyB,aAAa,QAAQ,QAAQ;AAE5E,WAAO,IAAI,IACT,YACA,WAAW,SAAS,OAAO,aAAa,GAAG,eAC3C;AAAA;AAAA;;ACzPN,MAAMA,cAAY,WAAW;AAAA,EAC3B,eAAe;AAAA,IACb,UAAU;AAAA;AAAA,EAEZ,UAAU;AAAA,IACR,OAAO;AAAA,IACP,cAAc;AAAA;AAAA;MAIL,qBAAqB,CAAC;AAAA,EACjC;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,SAAS;AAAA,EACT;AAAA,MAOI;AACJ,QAAM,UAAUA;AAChB,QAAM,WAAW,MAAG;AA7CtB;AA8CI,+CAAC,cAAD;AAAA,MACE,WAAW,QAAQ;AAAA,MACnB,wBAAwB,EAAE,SAAS;AAAA,MACnC,SACE,QACI,QACA,GAAG,OAAO,WAAW,aAAO,gBAAP,YAAsB,OAAO;AAAA,MAExD,+CACG,cAAD;AAAA,QACE,MAAM;AAAA,QACN,cAAa;AAAA,QACb,MAAM,OAAO;AAAA,QACb,SAAQ;AAAA;AAAA;AAAA;AAMhB,QAAM,cAAc,CAAC,EAAE,eACrB,6CAAU,MAAD;AAAA,IAAM,IAAI,OAAO;AAAA,KAAW,sEAAsB;AAE7D,QAAM,kBAAkB,CAAC,EAAE,eACzB,2GAEK,UAAD;AAAA,IAAU,YAAW;AAAA,IAAa,WAAW,QAAQ;AAAA,KAClD,+CAEF,SAAD;AAAA,IAAS,WAAU;AAAA,kEAGlB;AAGP,6CACG,aAAD,0CACG,iBAAD,0CACG,UAAD;AAAA;;MCjEK,eAAe,eAAe;AAAA,EACzC,IAAI;AAAA;MAGO,mBAAmB,eAAe;AAAA,EAC7C,IAAI;AAAA,EACJ,QAAQ,CAAC,aAAa,QAAQ;AAAA;MAGnB,0BAA0B,eAAe;AAAA,EACpD,IAAI;AAAA;;iCCJkC,iBAA2B;AACjE,SAAO,CAAC,QAAsB;AAC5B,WAAO;AAAA,MACL,MAAM,0CAAO,WAAD;AAAA,QAAW,UAAS;AAAA;AAAA,MAChC,SAAS;AAAA,MACT,SAAS,MACP,gBAAgB,GAAG,OAAO,SAAS,SAAS,IAAI,SAAS;AAAA;AAAA;AAAA;gCAM/D,iBACA,qBACA;AACA,SAAO,CAAC,EAAE,aAA2B;AACnC,UAAM,YAAY,gBAAgB;AAClC,WAAO;AAAA,MACL,WAAW,EAAE,aAAa;AAAA,MAC1B,MAAM,MAAM,mBAAmB;AAAA,MAC/B,SAAS,sBAAsB;AAAA,MAC/B,SAAS,MAAM,oBAAoB;AAAA;AAAA;AAAA;;;;;;;;ACvBzC,qBAAqB,QAAwB;AAC3C,SAAO,OAAO,SAAS,SAAS,OAAO,SAAS;AAAA;4BAGY;AAC5D,SAAO;AAAA,IACL,OAAO;AAAA,IACP,OAAO;AAAA,IACP,WAAW;AAAA,IACX,QAAQ,CAAC,4CACN,cAAD;AAAA,MACE,2CAAQ,MAAD;AAAA,QAAM,IAAI,IAAI,SAAS;AAAA,SAAU,YAAY,IAAI;AAAA,MACxD,UAAU,IAAI,OAAO,SAAS;AAAA;AAAA;AAAA;6BAMyB;AAC7D,SAAO;AAAA,IACL,OAAO;AAAA,IACP,OAAO;AAAA,IACP,QAAQ,CAAC,EAAE,mDACR,gBAAD;AAAA,MACE,YAAY,SAAS;AAAA,MACrB,aAAY;AAAA;AAAA;AAAA;4BAM0C;AAC5D,SAAO;AAAA,IACL,OAAO;AAAA,IACP,OAAO;AAAA;AAAA;;;;;;;;;sBCrCkB,KAAa,QAAgB;AACxD,SAAO,OAAO,mBACZ,iDAEE,MACA,IAAI,kBAAkB;AAAA;;MCcfC,cAAY,CAAC;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,MAOI;AACJ,QAAM,GAAG,mBAAmB;AAC5B,QAAM,0BAA0B,YAAY;AAC5C,QAAM,SAAS,OAAO;AACtB,MAAI,CAAC;AAAU,WAAO;AAEtB,QAAM,YAAY,SAAS,IAAI,YAAU;AAxD3C;AAyDI,UAAM,mBAAmB,mBAAmB,QAAQ;AACpD,WAAO;AAAA,MACL;AAAA,MACA,UAAU;AAAA,QACR,SAAS,wBAAwB;AAAA,UAC/B,WAAW,aACT,aAAO,SAAS,cAAhB,YAA6B,WAC7B;AAAA,UAEF,MAAM,aAAa,OAAO,MAAM;AAAA,UAChC,MAAM,aAAa,OAAO,SAAS,MAAM;AAAA;AAAA,QAE3C;AAAA,QACA,uBAAuB,iBACpB,IAAI,OAAK,qBAAqB,GAAG,EAAE,aAAa,YAChD,KAAK;AAAA;AAAA;AAAA;AAKd,QAAM,iBAA8C;AAAA,IAClDC;AAAgB,IAChBC;AAAgB,IAChBC;AAAgB;AAGlB,QAAM,iBAAsD;AAAA,IAC1DC,wBAAwC;AAAA;AAG1C,mEAEK,WAAY,aAAa,UAAU,SAAS,wCAC1C,OAAD;AAAA,IACE,WAAW;AAAA,IACX,SAAS;AAAA,MACP,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,oBAAoB;AAAA;AAAA,IAEtB,MAAM;AAAA,IACN,SAAS,WAAW;AAAA,IACpB,SAAS,WAAW;AAAA,IACpB,OACE,QACI,GAAG,UAAU,UAAU,YACvB,QAAQ,UAAU;AAAA,2CAIzB,YAAD;AAAA,IACE,SAAQ;AAAA,IACR,OAAM;AAAA,IACN,aAAY;AAAA,IACZ,4CACG,QAAD;AAAA,MACE,OAAM;AAAA,MACN,IAAG;AAAA,MACH,SAAQ;AAAA,OACT;AAAA;AAAA;;;;;;;MCnFA,sBAAsB,CAAC;AAAA,EAClC;AAAA,EACA;AAAA,MAII;AAxCN;AAyCE,QAAM,EAAE,SAAS,OAAO,UAAU,YAAY;AAC9C,QAAM,EAAE,iBAAiB,wBAAwB;AACjD,QAAM,GAAG,mBAAmB;AAE5B,QAAM,QAAQ,WAAW,oBAAQ,SAAR,mBAAc,UAAd,YAAuB;AAEhD,QAAM,iBAAiB;AAAA,IACrBA,wBAAwC;AAAA,IACxCC,uBACE,iBACA;AAAA;AAIJ,MAAI,OAAO;AACT,+CACG,cAAD;AAAA,MACE,UAAS;AAAA,MACT,OAAM;AAAA,2CAEL,aAAD;AAAA,MAAa,UAAS;AAAA,MAAO,MAAM,MAAM;AAAA;AAAA;AAK/C,6CACGL,aAAD;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,WAAW;AAAA,IACpB;AAAA;AAAA;AAKN,oBAAoB,UAAU;AAC9B,oBAAoB,UAAU;;MCrDjB,sBAAsB,CAAC,EAAE,eAAsB;AAzB5D;AA0BE,QAAM,YAAY,OAAO;AACzB,QAAM,oBAAoB,8BACxB,gBAAU,kBAAkB,yBAA5B,YAAoD;AAGtD,6CACG,gBAAD;AAAA,IACE,OAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAQ;AAAA,KAEP;AAAA;;ACbP,qBAA6C;AAAA,EAC3C,oBAA2E;AACzE,WAAO;AAAA,MACL,kDAAkD;AAAA;AAAA;AAAA;MAS3C,iBAAiB,MAAM;AAClC,QAAM,EAAE,kBAAkB;AAE1B,YAAU,MAAM;AACd,kBAAc;AAAA,MACZ,UAAU,IAAI;AAAA;AAAA,KAEf,CAAC;AAEJ,SAAO;AAAA;;MCJI,sBAAsB,CAAC;AAAA,EAClC,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,MAKI;AACJ,6CACG,qBAAD,0CACG,SAAD,0CACG,eAAD;AAAA,IAAe,OAAM;AAAA,yCAClB,eAAD,MAAe,mFAIhB,oBAAD,0CACG,sBAAD,0CACG,iBAAD,0CACG,gBAAD,2CACC,gBAAD;AAAA,IAAgB;AAAA,0CACf,mBAAD,2CACC,iBAAD,4CAED,qBAAD,0CACG,qBAAD;AAAA,IAAqB;AAAA,IAAkB;AAAA;AAAA;;MCrCxCM,iBAAe,CAAC;AAAA,EAC3B;AAAA,MAGI;AACJ,QAAM,0BAA0B,YAAY;AAC5C,QAAM,SAAS,OAAO;AACtB,MAAI,CAAC;AAAU,WAAO;AACtB,6CACG,cAAD;AAAA,IAAc,eAAY;AAAA,KACvB,uCAAW,UACR,OACA,SAAS,IAAI,CAAC,QAAQ,UAAe;AA1C/C;AA2CY,+CAAC,MAAD;AAAA,MAAM,KAAK;AAAA,2CACR,WAAD,0CACG,gBAAD;AAAA,MACE,OAAO,aAAO,SAAS,UAAhB,YAAyB,OAAO,SAAS;AAAA,6CAGnD,aAAD,MAAc,OAAO,SAAS,kDAC7B,aAAD,0CACG,QAAD;AAAA,MACE,IAAI,wBAAwB;AAAA,QAC1B,WAAW,aACT,aAAO,SAAS,cAAhB,YAA6B,WAC7B;AAAA,QAEF,MAAM,aAAa,OAAO,MAAM;AAAA,QAChC,MAAM,aAAa,OAAO,SAAS,MAAM;AAAA;AAAA,MAE3C,OAAM;AAAA,MACN,eAAY;AAAA,OACb;AAAA;AAAA;;;;;;;MCtCJ,qBAAqB,MAAM;AACtC,QAAM,EAAE,SAAS,OAAO,aAAa;AAErC,MAAI,OAAO;AACT,+CACG,cAAD;AAAA,MACE,UAAS;AAAA,MACT,OAAM;AAAA,2CAEL,aAAD;AAAA,MAAa,UAAS;AAAA,MAAO,MAAM,MAAM;AAAA;AAAA;AAK/C,MAAI,WAAW,CAAC,UAAU;AACxB,+CAAQ,UAAD;AAAA;AAGT,WAAS,KAAK,CAAC,GAAG,MAAG;AA1CvB;AA2CK,oBAAE,SAAS,UAAX,YAAoB,EAAE,SAAS,MAAM,cACpC,QAAE,SAAS,UAAX,YAAoB,EAAE,SAAS;AAAA;AAInC,6CAAQA,gBAAD;AAAA,IAAc;AAAA;AAAA;;MCfV,iBAAiB,aAAa;AAAA,EACzC,IAAI;AAAA,EACJ,MAAM;AAAA,IACJ,iBAAiB;AAAA,MACf,KAAK;AAAA,MACL,MAAM;AAAA,QACJ,WAAW;AAAA,QACX,cAAc;AAAA,QACd,aAAa;AAAA;AAAA,MAEf,SAAS,CAAC,EAAE,WAAW,cAAc,kBACnC,IAAI,sBAAsB;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA;AAAA;AAAA,IAGN,iBAAiB;AAAA,MACf,KAAK;AAAA,MACL,MAAM;AAAA,QACJ,WAAW;AAAA,QACX,cAAc;AAAA,QACd,aAAa;AAAA;AAAA,MAEf,SAAS,CAAC,EAAE,WAAW,cAAc,kBACnC,IAAI,eAAe;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA;AAAA;AAAA;AAAA,EAIR,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe;AAAA;AAAA;MAIN,eAAe,eAAe,QACzC,wBAAwB;AAAA,EACtB,MAAM;AAAA,EACN,WAAW,MAAM,yDAAmB,KAAK,OAAK,EAAE;AAAA,EAChD,YAAY;AAAA;MAIH,wBAAwB,eAAe,QAClD,wBAAwB;AAAA,EACtB,MAAM;AAAA,EACN,WAAW,MAAM,yDAAmB,KAAK,OAAK,EAAE;AAAA,EAChD,YAAY;AAAA;MAKH,eAAe,eAAe,QACzC,yBAAyB;AAAA,EACvB,MAAM;AAAA,EACN,WAAW;AAAA,IACT,MAAM,MACJ,+DAAyC,KAAK,OAAK,EAAE;AAAA;AAAA;MAMhD,YAAY,eAAe,QACtC,yBAAyB;AAAA,EACvB,MAAM;AAAA,EACN,WAAW;AAAA,IACT,MAAM,MAAM,4DAAsC,KAAK,OAAK,EAAE;AAAA;AAAA;MAMvDC,uBAAqB,eAAe,QAC/C,wBAAwB;AAAA,EACtB,MAAM;AAAA,EACN,WAAW,MACT,qEAA+C,KAC7C,OAAK,EAAE;AAAA,EAEX,YAAY;AAAA;MAIHC,sBAAoB,eAAe,QAC9C,wBAAwB;AAAA,EACtB,MAAM;AAAA,EACN,WAAW,MACT,oEAA8C,KAC5C,OAAK,EAAE;AAAA,EAEX,YAAY;AAAA;MAIH,qBAAqB,eAAe,QAC/C,wBAAwB;AAAA,EACtB,MAAM;AAAA,EACN,WAAW,MACT,+DAA2C,KAAK,OAAK,EAAE;AAAA,EACzD,YAAY;AAAA;;AC3GhB,MAAM,uBAAuB,CAC3B,UACA,SACA,cACG;AACH,QAAM,aAAa,aAAa,SAAS,QAAQ,SAAS;AAC1D,QAAM,gBAAgB,CAAC,QAAQ,MAAM;AACrC,QAAM,qBAAqB,QAAQ,WAAW;AAC9C,SAAO,gCAAgC;AAAA;MAG5B,aAAa,CAAC;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,MACoC;AACpC,SAAO,OAAM,QAAO;AAClB,UAAM,YAAY,MAAM,mBAAmB;AAE3C,UAAM,YAAY,OAChB,MACA,kBACG;AACH,iBAAW,QAAQ,MAAM;AACvB,YAAI,KAAK,aAAa,gBAAgB;AACpC,gBAAM,gBAAgB,KAAK,aAAa;AACxC,cAAI,CAAC;AAAe;AAGpB,gBAAM,WAAW,MAAM,mBAAmB,WACxC,eACA,UACA;AAGF,cAAI,qBAAqB,eAAe,eAAe,YAAY;AACjE,gBAAI;AACF,oBAAM,MAAM,MAAM,MAAM,UAAU,EAAE,aAAa;AACjD,oBAAM,aAAa,MAAM,IAAI;AAC7B,mBAAK,aACH,eACA,6BAA6B,KAAK;AAAA,qBAE7B,GAAP;AACA,mBAAK,aAAa,OAAO,UAAU;AAAA;AAAA,iBAEhC;AACL,iBAAK,aAAa,eAAe;AAAA;AAAA;AAAA;AAAA;AAMzC,UAAM,QAAQ,IAAI;AAAA,MAChB,UAA4B,IAAI,iBAAiB,QAAQ;AAAA,MACzD,UAA6B,IAAI,iBAAiB,WAAW;AAAA,MAC7D,UAA2B,IAAI,iBAAiB,SAAS;AAAA,MACzD,UAA6B,IAAI,iBAAiB,gBAAgB;AAAA;AAGpE,WAAO;AAAA;AAAA;;MC/DE,qBAAqB,CAChC,uBACgB;AAChB,SAAO,SAAO;AAEZ,UAAM,eAAe,IAAI,cACvB;AAIF,QAAI,CAAC,gBAAgB,CAAC,aAAa,MAAM;AACvC,aAAO;AAAA;AAGT,UAAM,YAAY,IAAI,IAAI,aAAa;AACvC,UAAM,cAAc,mBAAmB,MAAM;AAG7C,QAAI,4CAAa,UAAS,YAAY,4CAAa,UAAS,UAAU;AACpE,aAAO;AAAA;AAIT,UAAM,QAAS,IAAI,cAAc,cAA8B,WAAW,GACvE;AACH,UAAM,aAAa,mBAAmB,2BAA2B;AACjE,UAAM,YAAY,mBAChB;AAAA,EAAiB,aAAa;AAAA;AAAA;AAIhC,UAAM,SACJ,4CAAa,UAAS,WAClB,qBAAqB,UAAU,MAAM,UACrC,UAAU;AAChB,UAAM,UAAU,YAAY;AAC5B,UAAM,WAAW,IAAI,QAAQ,gBAAgB,QAAQ;AAErD,UAAM,eAAe,aAAa;AAClC,YAAQ,2CAAa;AAAA,WACd;AACH,qBAAa,OAAO,GAAG,UAAU,SAAS,oCAAoC,iCAAiC;AAC/G;AAAA,WACG;AACH,qBAAa,OAAO,GAAG,UAAU,SAAS,6BAA6B,mBAAmB;AAC1F;AAAA;AAEA,eAAO;AAAA;AAEX,aAAS,OAAO,MAAM,cAAc,uBAAuB;AAC3D,iBAAa,MAAM,cAAc;AACjC,iBAAa,QAAQ;AACrB,iBAAa,KAAK;AAClB,iDAAc,sBAAsB,eAAe;AACnD,WAAO;AAAA;AAAA;;MC/DE,kBAAkB,MAAmB;AAChD,SAAO,SAAO;AACZ,UAAM,YAAY,CAChB,MACA,kBACS;AACT,YAAM,KAAK,MACR,OAAO,UAAQ,KAAK,aAAa,gBACjC,QAAQ,CAAC,SAAY;AACpB,cAAM,gBAAgB,KAAK,aAAa;AACxC,YAAI,eAAe;AAEjB,cAAI,cAAc,MAAM,kBAAkB;AACxC,iBAAK,aAAa,UAAU;AAAA;AAG9B,cAAI;AACF,kBAAM,2BAA2B,aAC/B,OAAO,SAAS;AAElB,iBAAK,aACH,eACA,IAAI,IAAI,eAAe,0BAA0B;AAAA,mBAE5C,IAAP;AAEA,iBAAK,YAAY,KAAK,eAAe;AAAA;AAAA;AAAA;AAAA;AAM/C,cAAU,MAAM,KAAK,IAAI,qBAAqB,OAAO;AAErD,WAAO;AAAA;AAAA;sBAKkB,OAAuB;AAClD,QAAM,MAAM,IAAI,IAAI;AAEpB,MAAI,CAAC,IAAI,SAAS,SAAS,QAAQ,CAAC,IAAI,SAAS,SAAS,UAAU;AAClE,QAAI,YAAY;AAAA;AAGlB,SAAO,IAAI;AAAA;;MCzCA,uBAAuB,CAAC;AAAA,EACnC;AAAA,EACA;AAAA,MAC8C;AAC9C,SAAO,SAAO;AACZ,UAAM,KAAK,IAAI,qBAAqB,MAAM,QAAQ,UAAQ;AACxD,WAAK,iBAAiB,SAAS,CAAC,MAAkB;AAChD,cAAM,SAAS;AACf,cAAM,OAAO,OAAO,aAAa;AAEjC,YAAI,CAAC;AAAM;AACX,YAAI,KAAK,WAAW,UAAU;AAC5B,YAAE;AACF,kBAAQ,GAAG;AAAA;AAAA;AAAA;AAKjB,WAAO;AAAA;AAAA;;MCvBE,qBAAqB,MAAmB;AACnD,SAAO,SAAO;AAnBhB;AAqBI,cAAI,cAAc,kBAAlB,mBAAiC;AAEjC,WAAO;AAAA;AAAA;;MCLE,uBAAuB,MAAmB;AACrD,SAAO,SAAO;AAnBhB;AAqBI,cAAI,cAAc,4BAAlB,mBAA2C;AAE3C,WAAO;AAAA;AAAA;;MCCE,aAAa,CAAC;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,MACoC;AACpC,SAAO,SAAO;AACZ,UAAM,WAAW,MAAM,KACrB,IAAI,iBAAiB,kCACrB,OAAO,UAAK;AAhClB;AAgCqB,wBAAK,aAAa,YAAlB,mBAA2B,WAAW;AAAA;AAEvD,QAAI,QAAQ,SAAS;AAErB,QAAI,QAAQ,GAAG;AACb,gBAAU;AAAA;AAGZ,aAAS,QAAQ,aACf,QAAQ,iBAAiB,QAAQ,MAAM;AACrC,eAAS;AAET,UAAI,UAAU,GAAG;AACf,iBAAS;AAAA;AAAA;AAKf,WAAO;AAAA;AAAA;;AClCX,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,gBAAgB;MAET,gBAAgB,CAAC,SAAkB;AAC9C,MAAI,KAAK,YAAY,KAAK,aAAa,QAAQ;AAC7C,UAAM,OAAO,KAAK,aAAa,WAAW;AAC1C,QAAI,KAAK,MAAM,eAAe;AAC5B,WAAK,aAAa,OAAO;AAAA;AAE3B,QAAI,KAAK,MAAM,eAAe;AAC5B,WAAK,aAAa,OAAO;AAAA;AAE3B,QAAI,KAAK,MAAM,gBAAgB;AAC7B,WAAK,aAAa,OAAO;AAAA;AAAA;AAG7B,SAAO;AAAA;MAMI,cAAc,MAAmB;AAC5C,SAAO,SAAO;AACZ,cAAU,QAAQ,2BAA2B;AAC7C,WAAO,UAAU,SAAS,IAAI,WAAW;AAAA,MACvC,UAAU,CAAC;AAAA,MACX,aAAa,CAAC;AAAA,MACd,gBAAgB;AAAA,MAChB,YAAY;AAAA;AAAA;AAAA;;MCxBL,YAAY,CAAC,EAAE,UAAyC;AACnE,SAAO,SAAO;AACZ,QACG,qBAAqB,QAAQ,GAC7B,mBAAmB,aAAa,UAAU;AAE7C,WAAO;AAAA;AAAA;;MCVE,mBAAmB,MAAmB;AACjD,SAAO,SAAO;AACZ,eAAW,MAAM;AApBrB;AAsBM,UAAI,OAAO,SAAS,MAAM;AACxB,cAAM,OAAO,OAAO,SAAS,KAAK,MAAM;AACxC,yCAAK,cAAc,IAAI,YAAvB,mBAAgC;AAAA;AAAA,OAEjC;AACH,WAAO;AAAA;AAAA;;MCTE,YAAY,OACvB,MACA,iBACqB;AACrB,MAAI;AAEJ,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,IAAI,YAAY,gBAAgB,MAAM,aAAa;AAAA,aAChD,gBAAgB,SAAS;AAClC,UAAM;AAAA,SACD;AACL,UAAM,IAAI,MAAM;AAAA;AAGlB,aAAW,eAAe,cAAc;AACtC,UAAM,MAAM,YAAY;AAAA;AAG1B,SAAO;AAAA;;ACeT,MAAM,oBAAoB,CAAC;AAAA,EACzB;AAAA,EACA,eAAe;AAAA,MACU;AACzB,QAAM,CAAC,MAAM,WAAW,SAAS;AACjC,QAAM,WAAW;AACjB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,QAAQ,EAAE,SAAS,OAAO;AAAA,MACxB;AACJ,QAAM,CAAC,SAAS,cAAc,SAAgB;AAC9C,YAAU,MAAM;AACd,QAAI,UAAU;AAEd,QAAI,WAAW,WAAW;AAIxB,YAAM,gBAAgB,UAAU,QAAQ,MAAM,GAAG;AACjD,iBAAW;AAAA;AAEb,WAAO,MAAM;AACX,gBAAU;AAAA;AAAA,KAEX,CAAC,SAAS;AAEb,QAAM,CAAC,OAAO,YAAY,SAAiB;AAE3C,cAAY,MAAM,QAAQ,QAAQ,cAAc,CAAC;AAEjD,QAAM,cAAc,CAAC,MAAqC;AACxD,QAAI,CAAC,MAAM;AACT,cAAQ;AAAA;AAEV,aAAS,EAAE,OAAO;AAAA;AAGpB,QAAM,kBAAkB,CAAC,GAAQ,cAA2C;AAC1E,QAAI,uCAAW,UAAU;AACvB,YAAM,EAAE,aAAa,UAAU;AAC/B,eAAS;AAAA;AAAA;AAIb,6CACG,MAAD;AAAA,IAAM,MAAI;AAAA,IAAC,IAAI;AAAA,yCACZ,cAAD;AAAA,IACE,eAAY;AAAA,IACZ,MAAK;AAAA,IACL;AAAA,IACA,gBAAgB,MAAM;AAAA,IACtB,eAAe,OAAK;AAClB,aAAO;AAAA;AAAA,IAET,SAAS,MAAM;AACb,cAAQ;AAAA;AAAA,IAEV,SAAS,MAAM;AACb,cAAQ;AAAA;AAAA,IAEV,UAAU;AAAA,IACV,cAAY;AAAA,IACZ,eAAc;AAAA,IACd,OAAO;AAAA,IACP;AAAA,IACA,cAAc,CAAC,EAAE,mDACd,oBAAD;AAAA,MACE,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,OAAO,SAAS;AAAA;AAAA,IAGpB;AAAA,IACA,aAAa,gDACV,WAAD;AAAA,SACM;AAAA,MACJ,eAAY;AAAA,MACZ,SAAQ;AAAA,MACR,WAAS;AAAA,MACT,aAAa,UAAU,SAAS;AAAA,MAChC;AAAA,MACA,UAAU;AAAA,MACV,YAAY;AAAA,WACP,OAAO;AAAA,QACV,oDACG,gBAAD;AAAA,UAAgB,UAAS;AAAA,+CACtB,YAAD;AAAA,UAAY,cAAW;AAAA,UAAQ,UAAQ;AAAA,+CACpC,YAAD;AAAA,QAIN,kDACG,MAAM,UAAP,MACG,8CACE,kBAAD;AAAA,UAAkB,OAAM;AAAA,UAAU,MAAM;AAAA,aACtC,MACH,OAAO,WAAW;AAAA;AAAA;AAAA;AAAA;MAWxB,iBAAiB,CAAC,UAA+B;AAC5D,QAAM,eAAe;AAAA,IACnB,MAAM;AAAA,IACN,OAAO,CAAC;AAAA,IACR,YAAY;AAAA,IACZ,SAAS,MAAM;AAAA;AAEjB,6CACG,uBAAD;AAAA,IAAuB;AAAA,yCACpB,mBAAD;AAAA,OAAuB;AAAA;AAAA;;AC5I7B,MAAM,kBAAkB,WAAW,CAAC,UAClC,aAAa;AAAA,EACX,OAAO;AAAA,IACL,OAAO;AAAA,KACN,MAAM,YAAY,GAAG,QAAQ;AAAA,MAC5B,OAAO;AAAA;AAAA,KAER,MAAM,YAAY,GAAG,QAAQ;AAAA,MAC5B,OAAO;AAAA;AAAA,IAET,SAAS,MAAM,QAAQ;AAAA;AAAA,EAEzB,MAAM;AAAA,IACJ,QAAQ;AAAA,IACR,UAAU;AAAA;AAAA,EAEZ,MAAM;AAAA,IACJ,YAAY,MAAM,QAAQ,WAAW;AAAA;AAAA;MAK9B,iCAAiC,CAAC;AAAA,EAC7C;AAAA,EACA;AAAA,MAII;AACJ,QAAM,UAAU;AAChB,QAAM,UACJ,SAAS,WAAW,IAAI,wBAAwB,SAAS,KAAK;AAChE,6CACG,MAAD;AAAA,IACE,WAAS;AAAA,IACT,WAAU;AAAA,IACV,WAAW,QAAQ;AAAA,IACnB,SAAS;AAAA,IACT,MAAK;AAAA,yCAEJ,MAAD;AAAA,IACE,MAAI;AAAA,IACJ,WAAS;AAAA,IACT,gBAAe;AAAA,IACf,YAAW;AAAA,IACX,SAAS;AAAA,IACT,MAAK;AAAA,yCAEJ,YAAD;AAAA,IAAY,SAAQ;AAAA,KAAK,sDACxB,YAAD;AAAA,IACE,KAAI;AAAA,IACJ,OAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAM;AAAA,yCAEL,OAAD,6CAGH,WAAD;AAAA,IAAW,MAAM;AAAA,IAAS,SAAS,EAAE,MAAM,QAAQ;AAAA;AAAA;MAK5C,oBAAoB,CAAC,EAAE,eAAuC;AACzE,QAAM,UAAU;AAChB,QAAM,CAAC,MAAM,WAAW,SAAS;AAEjC,uGAEKC,UAAD;AAAA,IAAQ,OAAM;AAAA,IAAU,SAAS,MAAM,QAAQ;AAAA,KAAO,wDAGrD,QAAD;AAAA,IACE,SAAS,EAAE,OAAO,QAAQ;AAAA,IAC1B,QAAO;AAAA,IACP;AAAA,IACA,SAAS,MAAM,QAAQ;AAAA,yCAEtB,gCAAD;AAAA,IACE;AAAA,IACA,SAAS,MAAM,QAAQ;AAAA;AAAA;;MCtFpB,mBAAmB,CAAC,EAAE,mBAA0B;AAC3D,QAAM,kBACJ,OAAO,cAAc,kBAAkB;AAEzC,MAAI,iBAAiB;AACrB,MAAI,oBAAoB,SAAS;AAC/B,qBACE;AAAA;AAMJ,6CACG,WAAD;AAAA,IACE,QAAO;AAAA,IACP,eAAe,gBAAgB;AAAA,IAC/B;AAAA;AAAA;;AChBN,MAAMV,cAAY,WAAW;AAAO,EAClC,SAAS;AAAA,IAGP,WAAW;AAAA,IACX,cAAc;AAAA;AAAA;MAaL,yBAAyB,MAAM;AAC1C,MAAI,aAAiC;AACrC,QAAM,UAAUA;AAEhB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,MACE;AAEJ,QAAM,iBAAiB,UAAU,iDAAc,UAAD,QAAe;AAE7D,MAAI,UAAU,iBAAiB;AAC7B,qDACG,OAAD;AAAA,MACE,SAAQ;AAAA,MACR,UAAS;AAAA,MACT,0CAAO,kBAAD;AAAA,QAAkB,MAAK;AAAA;AAAA,MAC7B,4CAAS,mBAAD;AAAA,QAAmB;AAAA;AAAA,OAC5B;AAAA;AAOL,MAAI,UAAU,4BAA4B;AACxC,qDACG,OAAD;AAAA,MACE,SAAQ;AAAA,MACR,UAAS;AAAA,MACT,0CAAO,kBAAD;AAAA,QAAkB,MAAK;AAAA;AAAA,MAC7B,4CAAS,mBAAD;AAAA,QAAmB;AAAA;AAAA,OAC5B;AAAA;AAOL,MAAI,UAAU,uBAAuB;AACnC,qDACG,OAAD;AAAA,MACE,SAAQ;AAAA,MACR,UAAS;AAAA,MACT,4CACGU,UAAD;AAAA,QAAQ,OAAM;AAAA,QAAU,SAAS,MAAM;AAAA,SAAiB;AAAA,OAI3D;AAAA;AAOL,MAAI,UAAU,uBAAuB;AACnC,qDACG,OAAD;AAAA,MACE,SAAQ;AAAA,MACR,UAAS;AAAA,MACT,4CAAS,mBAAD;AAAA,QAAmB;AAAA;AAAA,MAC3B,SAAS,EAAE,SAAS,QAAQ;AAAA,OAC7B,0DACwD,KACtD;AAAA;AAKP,MAAI,UAAU,qBAAqB;AACjC,2EAEK,wDACE,OAAD;AAAA,MACE,SAAQ;AAAA,MACR,UAAS;AAAA,MACT,4CAAS,mBAAD;AAAA,QAAmB;AAAA;AAAA,MAC3B,SAAS,EAAE,SAAS,QAAQ;AAAA,OAC7B,0DACwD,KACtD,uDAGJ,kBAAD;AAAA,MAAkB,cAAc;AAAA;AAAA;AAKtC,mEAEK,gBACA;AAAA;;+BCzF+B;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,GAIoB;AAEpB,MAAI,gBAAgB;AAClB,WAAO;AAAA;AAIT,MAAI,oBAAoB,sBAAsB;AAC5C,WAAO;AAAA;AAIT,MAAI,CAAC,WAAW,oBAAoB,YAAY;AAC9C,WAAO;AAAA;AAIT,MAAI,CAAC,WAAW,oBAAoB,YAAY;AAC9C,WAAO;AAAA;AAIT,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA;AAIT,MAAI,oBAAoB,YAAY;AAClC,WAAO;AAAA;AAIT,MAAI,oBAAoB,eAAe;AACrC,WAAO;AAAA;AAIT,MAAI,oBAAoB,SAAS;AAC/B,WAAO;AAAA;AAIT,SAAO;AAAA;iBA0EP,UACA,QACc;AACd,QAAM,WAAW,KAAK;AAEtB,UAAQ,OAAO;AAAA,SACR;AAEH,UAAI,OAAO,UAAU,YAAY;AAC/B,iBAAS,WAAW;AAAA;AAGtB,eAAS,kBAAkB,OAAO;AAClC,eAAS,YAAY,OAAO;AAC5B;AAAA,SAEG;AACH,eAAS,iBAAiB;AAG1B,eAAS,eAAe;AACxB;AAAA,SAEG;AAEH,UAAI,OAAO,OAAO,SAAS,UAAU;AACnC,iBAAS,OAAO,OAAO;AAAA;AAGzB,eAAS,iBAAiB;AAC1B,eAAS,UAAU,OAAO;AAC1B,eAAS,eAAe,OAAO;AAC/B;AAAA,SAEG;AACH,eAAS,WAAW,SAAS,SAAS,OAAO,OAAO;AACpD;AAAA;AAGA,YAAM,IAAI;AAAA;AAId,MACE,CAAC,eAAe,sBAAsB,SAAS,SAAS,oBACxD,CAAC,kBAAkB,WAAW,SAAS,OAAO,OAC9C;AACA,aAAS,kBAAkB;AAC3B,aAAS,WAAW;AAAA;AAGtB,SAAO;AAAA;wBAIP,MACA,WACA,MACA,MASA;AA/OF;AAgPE,QAAM,CAAC,OAAO,YAAY,WAAW,SAAS;AAAA,IAC5C,iBAAiB;AAAA,IACjB;AAAA,IACA,gBAAgB;AAAA,IAChB,UAAU;AAAA;AAGZ,QAAM,qBAAqB,OAAO;AAGlC,QAAM,EAAE,OAAO,kBAAkB,cAAc,YAAY;AACzD,aAAS,EAAE,MAAM;AAEjB,QAAI;AACF,YAAM,aAAa,MAAM,mBAAmB,cAC1C,EAAE,MAAM,WAAW,QACnB;AAIF,eAAS,EAAE,MAAM,WAAW,SAAS,YAAY;AAEjD,aAAO;AAAA,aACA,GAAP;AACA,eAAS,EAAE,MAAM,WAAW,cAAc,GAAG;AAAA;AAG/C,WAAO;AAAA,KACN,CAAC,oBAAoB,MAAM,WAAW,MAAM;AAI/C,QAAM,aAAa,OAAiD;AAAA,IAClE,SAAS;AAAA,IACT,QAAQ,MAAM;AAAA;AAAA;AAEhB,aAAW,UAAU,EAAE,SAAS,MAAM,SAAS,QAAQ;AAGvD,WAAS,YAAY;AACnB,aAAS,EAAE,MAAM,QAAQ,OAAO;AAGhC,UAAM,kBAAkB,WAAW,MAAM;AACvC,eAAS,EAAE,MAAM,QAAQ,OAAO;AAAA,OAC/B;AAEH,QAAI;AACF,YAAM,SAAS,MAAM,mBAAmB,eACtC;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,SAEF,SAAO;AACL,iBAAS,EAAE,MAAM,YAAY;AAAA;AAIjC,cAAQ;AAAA,aACD;AAEH,cAAI,CAAC,WAAW,QAAQ,SAAS;AAC/B,uBAAW,QAAQ;AACnB,qBAAS,EAAE,MAAM,QAAQ,OAAO;AAAA,iBAC3B;AACL,qBAAS,EAAE,MAAM,QAAQ,OAAO;AAAA;AAElC;AAAA,aACG;AACH,mBAAS,EAAE,MAAM,QAAQ,OAAO;AAChC;AAAA;AAGA,mBAAS;AAAA,YACP,MAAM;AAAA,YACN,OAAO;AAAA,YACP,WAAW,IAAI,MAAM;AAAA;AAEvB;AAAA;AAAA,aAEG,GAAP;AACA,eAAS,EAAE,MAAM,QAAQ,OAAO,SAAS,WAAW;AAAA,cACpD;AAEA,mBAAa;AAAA;AAAA,KAEd,CAAC,MAAM,MAAM,WAAW,oBAAoB,UAAU;AAEzD,QAAM,eAAe,QACnB,MACE,sBAAsB;AAAA,IACpB,iBAAiB,MAAM;AAAA,IACvB,gBAAgB,MAAM;AAAA,IACtB,SAAS,MAAM;AAAA,MAEnB,CAAC,MAAM,iBAAiB,MAAM,SAAS,MAAM;AAG/C,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,SAAS,MAAM;AAAA,IACf,qBAAqB,YAAM,iBAAN,mBAAoB;AAAA,IACzC,kBAAkB,YAAM,cAAN,mBAAiB;AAAA,IACnC,UAAU,MAAM;AAAA;AAAA;;AC9RpB,MAAM,YAAY,WAA2B;AAAU,EACrD,WAAW;AAAA,IACT,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,WAAW,MAAM,QAAQ;AAAA,IACzB,4CAA4C;AAAA,MAC1C,YAAY;AAAA,MACZ,UAAU;AAAA;AAAA;AAAA;AAOhB,MAAM,wBAAwB,cAC5B;AAGF,MAAM,yBAAyB,CAAC;AAAA,EAC9B;AAAA,EACA;AAAA,MACkD;AAClD,QAAM,EAAE,KAAK,SAAS;AACtB,QAAM,EAAE,MAAM,WAAW,SAAS;AAClC,QAAM,QAAQ,eAAe,MAAM,WAAW,MAAM;AACpD,6CACG,sBAAsB,UAAvB;AAAA,IAAgC;AAAA,KAC7B;AAAA;MAcM,6BACX,CAAe,WAA6B,cAC5C,CAAC,8CAEI,wBAAD;AAAA,EAAwB;AAAA,uCACrB,WAAD;AAAA,KAAe;AAAA;MAaV,oBAAoB,MAAM,WAAW;MAerC,uBAAuB,CAAC,cAA0C;AAC7E,QAAM,WAAWC;AACjB,QAAM,QAAQ;AACd,QAAM,qBAAqB,OAAO;AAClC,QAAM,qBAAqB,OAAO;AAClC,QAAM,EAAE,YAAY,IAAI,OAAO,IAAI,OAAO,OAAO;AACjD,QAAM,EAAE,OAAO,MAAM,SAAS,YAAY;AAE1C,QAAM,CAAC,UAAU,eAAe;AAChC,QAAM,CAAC,KAAK,UAAU,SAA6B;AAEnD,QAAM,wBAAwB,YAAY,MAAM;AAC9C,QAAI,CAAC,OAAO,CAAC;AAAU;AAEvB,UAAM,SAAS,IAAI,cAAc;AACjC,aAAS,QAAQ,aAAW;AAC1B,YAAM,SAAS,KAAK,IAAI,IAAI,wBAAwB,KAAK;AACzD,cAAQ,MAAM,MAAM,SAChB,GAAG,SAAS,OAAO,wBAAwB,aAC3C,GAAG;AAAA;AAAA,KAER,CAAC,KAAK;AAET,YAAU,MAAM;AACd;AACA,WAAO,iBAAiB,UAAU,uBAAuB;AACzD,WAAO,iBAAiB,UAAU;AAClC,WAAO,MAAM;AACX,aAAO,oBAAoB,UAAU,uBAAuB;AAC5D,aAAO,oBAAoB,UAAU;AAAA;AAAA,KAGtC,CAAC,uBAAuB;AAG3B,QAAM,YAAY,YAChB,CAAC,YAAoB,gBACnBC,UAAY,YAAY;AAAA,IACtB;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA,UAAU;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA;AAAA,MAEF,MAAM;AAAA;AAAA,IAER;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB,UAAU;AAAA,MACR,KAAK;AAAA;AAAA,2BAEY,MAAM,WAAW;AAAA,+BACb,MAAM,QAAQ,KAAK;AAAA,oCACd,MAAM,QAAQ,QAAQ;AAAA;AAAA,kCAExB,MAAM,QAAQ,KAAK;AAAA,kCACnB,MAAM,QAAQ,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAe3B,MAAM,QAAQ,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,uCAKZ,MAAM,QAAQ,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAS1B,MAAM,QAAQ,OAAO;AAAA;AAAA;AAAA,gCAGrB,MAAM,QAAQ,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kCAMpB,MAAM,QAAQ,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAwBnD,UAAU;AAAA,MAKR,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMP,UAAU;AAAA,MAER,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASP,UAAU;AAAA,MASR,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MA4BX;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,QAAQ,OAAO;AAAA,IACrB,MAAM,QAAQ,WAAW;AAAA,IACzB,MAAM,QAAQ,WAAW;AAAA,IACzB,MAAM,QAAQ,QAAQ;AAAA,IACtB,MAAM,QAAQ,QAAQ;AAAA,IACtB,MAAM,QAAQ,KAAK;AAAA,IACnB,MAAM,WAAW;AAAA;AAKrB,QAAM,aAAa,YACjB,OAAO,uBACLA,UAAY,oBAAoB;AAAA,IAC9B;AAAA,IACA,qBAAqB;AAAA,MACnB,SAAS,OAAO,SAAS;AAAA,MACzB,SAAS,CAAC,GAAe,QAAgB;AAlVnD;AAmVY,cAAM,YAAY,IAAI,IAAI;AAE1B,YAAI,UAAU,MAAM;AAClB,mBAAS,GAAG,UAAU,WAAW,UAAU;AAE3C,yEACI,cAAc,IAAI,UAAU,KAAK,MAAM,UAD3C,mBAEI;AAAA,eACC;AACL,mBAAS,UAAU;AAEnB,yEACI,cAAc,0BADlB,mBAEI;AAAA;AAAA;AAAA;AAAA,IAIV,WAAW;AAAA,MACT,eAAe,MAAM,mBAAmB;AAAA,MACxC,WAAW,CAAC,oBAA6B;AACvC,QAAC,gBAAgC,MAAM,YAAY,WAAW;AAAA;AAAA,MAEhE,UAAU,CAAC,oBAA6B;AAzWlD;AA0WY,QAAC,gBAAgC,MAAM,eAAe;AAEtD,8BACG,cAAc,sBADjB,mBAEI,gBAAgB;AACpB,oBACE,MAAM,KAAK,gBAAgB,iBAAiB;AAAA;AAAA;AAAA,MAKtD,CAAC,UAAU;AAGb,YAAU,MAAM;AACd,QAAI,CAAC;AAAS,aAAO,MAAM;AAAA;AAG3B,QAAI,uBAAuB;AAG3B,cAAU,SAAS,MAAM,KAAK,OAAM,6BAA4B;AAC9D,UAAI,uEAA2B,YAAW;AACxC;AAAA;AAIF,UAAI,CAAC,sBAAsB;AACzB;AAAA;AAIF,aAAO,OAAO,EAAE,KAAK;AAGrB,YAAM,4BAA4B,MAAM,WACtC;AAEF,aAAO;AAAA;AAIT,WAAO,MAAM;AACX,6BAAuB;AAAA;AAAA,KAExB,CAAC,SAAS,MAAM,WAAW;AAE9B,SAAO;AAAA;AAGT,MAAM,YAAY,CAAC;AAAA,EACjB;AAAA,EACA,UAAU,MAAM;AAAA;AAAA,EAChB,aAAa;AAAA,MACF;AAhab;AAiaE,QAAM,UAAU;AAChB,QAAM,MAAM,qBAAqB;AACjC,QAAM,eAAe,OAAuB;AAE5C,QAAM,aAAa,OAAmB;AACtC,YAAU,MAAM;AACd,eAAW,UAAU;AAAA,KACpB,CAAC;AAEJ,YAAU,MAAM;AACd,QAAI,CAAC,OAAO,CAAC,aAAa;AAAS;AACnC,UAAM,YAAY,aAAa;AAC/B,UAAM,aACJ,UAAU,cAAc,UAAU,aAAa,EAAE,MAAM;AACzD,UAAM,KAAK,WAAW,UAAU,QAAQ,WACtC,WAAW,YAAY;AAEzB,eAAW,YAAY;AACvB,eAAW;AAAA,KAGV,CAAC;AAEJ,uGAEK,wBAAD,OACC,wEAA4B,YAAd,mBAAuB,eAAvB,mBAAmC,kDAC/C,MAAD;AAAA,IAAM,WAAS;AAAA,IAAC,WAAW,QAAQ;AAAA,yCAChC,gBAAD;AAAA,IAAgB,UAAU;AAAA,2CAG7B,OAAD;AAAA,IAAK,eAAY;AAAA,IAA8B,KAAK;AAAA;AAAA;MAK7C,SAAS,CAAC;AAAA,EACrB;AAAA,EACA,UAAU,MAAM;AAAA;AAAA,EAChB,aAAa;AAAA,0CAEZ,wBAAD;AAAA,EAAwB;AAAA,uCACrB,WAAD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA;;MC3aO,qBAAqB,CAAC;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,MAC6B;AAC7B,QAAM,EAAE,SAAS;AAEjB,QAAM,EAAE,WAAW,UAAU,kBAAkB,oBAC7C,oBAAoB;AAEtB,QAAM,EAAE,kBAAkB,SAAS,kBAAkB;AACrD,QAAM,YAAY,6BAAM;AAExB,QAAM,mBAAmB,iBACrB,mBAAmB,gBAAgB,qBACnC;AAEJ,QAAM,eAAe,YAAY;AAEjC,QAAM,uGAED,aAAD;AAAA,IACE,OAAM;AAAA,IACN,2CACG,eAAD;AAAA,MACE,OAAM;AAAA,MACN;AAAA,MACA,aAAY;AAAA;AAAA,MAIjB,iBAAiB,SAAS,yCACxB,aAAD;AAAA,IACE,OAAM;AAAA,IACN,2CACG,gBAAD;AAAA,MACE,OAAM;AAAA,MACN,YAAY;AAAA,MACZ,aAAY;AAAA;AAAA,MAKnB,gDAAa,aAAD;AAAA,IAAa,OAAM;AAAA,IAAY,OAAO;AAAA,OAAgB,MAClE,oBACD,iBAAiB,SAAS,SAC1B,iBAAiB,SAAS,6CACvB,aAAD;AAAA,IACE,OAAM;AAAA,IACN,2CACG,KAAD;AAAA,MACE,MAAM,iBAAiB;AAAA,MACvB,QAAO;AAAA,MACP,KAAI;AAAA,2CAEH,UAAD;AAAA,MAAU,OAAO,EAAE,WAAW,SAAS,MAAM;AAAA;AAAA,OAIjD;AAIR,6CACG,QAAD;AAAA,IACE,OAAO,WAAW,WAAW;AAAA,IAC7B,mBAAmB,YAAY;AAAA,IAC/B,UACE,mBAAmB,oBAAoB,SAAS,kBAAkB;AAAA,IAEpE,MAAK;AAAA,IACL,UAAU;AAAA,KAET;AAAA;;MClFM,qBAAqB,MAAM;AACtC,QAAM,CAAC,eAAe,oBAAoB,SAAkB;AAC5D,QAAM,EAAE,WAAW,MAAM,SAAS;AAElC,QAAM,cAAc,OAAO;AAE3B,QAAM,EAAE,OAAO,0BAA0B,SAAS,MAAM;AACtD,QAAI,eAAe;AACjB,aAAO,YAAY,oBAAoB,EAAE,MAAM,WAAW;AAAA;AAG5D,WAAO,QAAQ,QAAQ;AAAA,KACtB,CAAC,MAAM,WAAW,MAAM,aAAa;AAExC,QAAM,EAAE,OAAO,qBAAqB,OAAO,wBACzC,SAAS,MAAM;AACb,WAAO,YAAY,kBAAkB,EAAE,MAAM,WAAW;AAAA,KACvD,CAAC,MAAM,WAAW,MAAM;AAE7B,QAAM,UAAU,YAAY,MAAM;AAChC,qBAAiB;AAAA,KAChB,CAAC;AAEJ,MAAI,qBAAqB;AACvB,+CAAQ,kBAAD;AAAA,MAAkB,cAAc,oBAAoB;AAAA;AAAA;AAG7D,6CACG,MAAD;AAAA,IAAM,SAAQ;AAAA,yCACX,oBAAD;AAAA,IACE,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA;AAAA,0CAGH,SAAD;AAAA,IAAS,eAAY;AAAA,yCAClB,QAAD;AAAA,IACE;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA;AAAA;AAAA;;MC3BC,eAAe,CAAC,EAAE,eAAkC;AAC/D,QAAM,SAAS;AAEf,QAAM,CAAC,eAAe,oBAAoB,SAAkB;AAC5D,QAAM,EAAE,WAAW,MAAM,SAAS;AAElC,QAAM,cAAc,OAAO;AAE3B,QAAM,EAAE,OAAO,0BAA0B,SAAS,MAAM;AACtD,QAAI,eAAe;AACjB,aAAO,YAAY,oBAAoB,EAAE,MAAM,WAAW;AAAA;AAG5D,WAAO,QAAQ,QAAQ;AAAA,KACtB,CAAC,MAAM,WAAW,MAAM,aAAa;AAExC,QAAM,EAAE,OAAO,qBAAqB,OAAO,wBACzC,SAAS,MAAM;AACb,WAAO,YAAY,kBAAkB,EAAE,MAAM,WAAW;AAAA,KACvD,CAAC,MAAM,WAAW,MAAM;AAE7B,QAAM,UAAU,YAAY,MAAM;AAChC,qBAAiB;AAAA,KAChB,CAAC;AAEJ,MAAI,qBAAqB;AACvB,+CAAQ,kBAAD;AAAA,MAAkB,cAAc,oBAAoB;AAAA;AAAA;AAG7D,MAAI,CAAC;AAAU,WAAO,8CAAW,oBAAD;AAEhC,6CACG,MAAD;AAAA,IAAM,SAAQ;AAAA,KACX,oBAAoB,WACjB,SAAS;AAAA,IACP;AAAA,IACA;AAAA,IACA,WAAW,EAAE,MAAM,WAAW;AAAA,IAC9B;AAAA,OAEF;AAAA;;;;;;;ACvCV,MAAM,SAAS;AAAA,aACbX;AAAA,gBACAM;AAAA;AAoBF,MAAM,cAAc,CAAC;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,MAKI;AACJ,QAAM,YAAY,WAAW;AAAA,IAC3B,gBAAgB;AAAA,MACd,cAAc;AAAA,SACV,OAAO,WAAW,OAAO,WAAW;AAAA;AAAA;AAG5C,QAAM,UAAU;AAChB,QAAM,EAAE,OAAO,SAAS;AAExB,QAAM,QAAQ,OAAO,OAAO;AAE5B,QAAM,gBAAgB,SAAS,OAAO,YAAU;AAC9C,QAAI,OAAO,oBAAoB,eAAe;AAC5C,UAAI,CAAC,MAAM;AACT,eAAO;AAAA;AAET,aAAO,UAAU,MAAM;AAAA;AAGzB,WACE,OAAO,OAAO,oBAAoB,cAClC,OAAO,gBAAgB;AAAA;AAI3B,uGAEK,eAAD;AAAA,IAAe,OAAO,OAAO;AAAA,IAAO,aAAa,OAAO;AAAA,KACrD,UAAU,wCACR,eAAD,MAAe,+CAGb,2CAEL,OAAD;AAAA,IAAK,WAAW,QAAQ;AAAA,yCACrB,OAAD;AAAA,IAAO,eAAY;AAAA,IAAwB,UAAU;AAAA;AAAA;MAMhD,qBAAqB,CAAC;AAAA,EACjC;AAAA,MAGI;AACJ,QAAM,CAAC,aAAa,kBAAkB,SAAiB;AACvD,QAAM,aAAyB,OAAO;AAEtC,QAAM;AAAA,IACJ,OAAO;AAAA,IACP;AAAA,IACA;AAAA,MACE,SAAS,YAAY;AACvB,UAAM,WAAW,MAAM,WAAW,YAAY;AAAA,MAC5C,QAAQ;AAAA,QACN,kDAAkD;AAAA;AAAA,MAEpD,QAAQ;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA;AAGJ,WAAO,SAAS,MAAM,OAAO,CAAC,WAAmB;AA9IrD;AA+IM,aAAO,CAAC,eAAQ,SAAS,gBAAhB,mBAA8B;AAAA;AAAA;AAI3C,QAAM,mBAAmB,WAAW;AAEpC,MAAI,SAAS;AACX,+CACG,qBAAD,0CACG,SAAD,0CACG,UAAD;AAAA;AAMR,MAAI,OAAO;AACT,+CACG,qBAAD,0CACG,SAAD,0CACG,cAAD;AAAA,MACE,UAAS;AAAA,MACT,OAAM;AAAA,2CAEL,aAAD;AAAA,MAAa,UAAS;AAAA,MAAO,MAAM,MAAM;AAAA;AAAA;AAOnD,6CACG,qBAAD,0CACG,YAAD;AAAA,IACE,eAAe;AAAA,IACf,UAAU,WAAS,eAAe;AAAA,IAClC,MAAM,WAAW,IAAI,CAAC,EAAE,SAAS;AAAW,MAC1C,IAAI,MAAM;AAAA,MACV;AAAA;AAAA,0CAGH,SAAD;AAAA,IAAS,eAAY;AAAA,KAClB,iBAAiB,OAAO,IAAI,CAAC,QAAQ,8CACnC,aAAD;AAAA,IACE,KAAK;AAAA,IACL;AAAA,IACA,UAAU,CAAC,CAAC,WAAW,WAAW;AAAA,IAClC;AAAA;AAAA;;;;;;;MC3KC,qBAAqB,MAAM;AACtC,QAAM,aAAa;AAAA,IACjB;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,QACN;AAAA,UACE,OAAO;AAAA,UACP,aACE;AAAA,UACF,WAAW;AAAA,UACX,iBAAiB,MAAM;AAAA;AAAA;AAAA;AAAA,IAW7B;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,QACN;AAAA,UACE,OAAO;AAAA,UACP,aAAa;AAAA,UACb,WAAW;AAAA,UAEX,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAKzB,6CAAQ,oBAAD;AAAA,IAAoB;AAAA;AAAA;;MCjChB,oBAAoB,MAAM;AACrC,QAAM,SAAS;AAEf,SAAO,8CAAW,oBAAD;AAAA;;;;;;;MCDN,iBAAiB,CAAC,EAAE,aAAiC;AAtBlE;AAuBE,QAAM,SAAS,OAAO;AACtB,6CACG,QAAD;AAAA,IACE,YAAY;AAAA,IACZ,WAAW;AAAA,MACT,WAAW,aAAa,aAAO,SAAS,cAAhB,YAA6B,WAAW;AAAA,MAChE,MAAM,aAAa,OAAO,MAAM;AAAA,MAChC,MAAM,aAAa,OAAO,SAAS,MAAM;AAAA;AAAA;AAAA;;ACLjD,MAAM,sBAAsB;MAEf,sBAAsB,CAAC,WAAgB;AA3BpD;AA4BE,iBAAQ,6CAAQ,aAAR,mBAAkB,gBAAlB,mBAAgC;AAAA;MAE7B,SAAS,MAAM;AAC1B,6CACG,QAAD,0CACG,OAAD;AAAA,IAAO,MAAK;AAAA,IAAI,6CAAU,mBAAD;AAAA,0CACxB,OAAD;AAAA,IACE,MAAK;AAAA,IACL,6CAAUM,cAAD;AAAA;AAAA;MAWJ,qBAAqB,CAAC,WAAkB;AA/CrD;AAgDE,QAAM,EAAE,WAAW;AAEnB,QAAM,YAAY,aAAO,SAAS,gBAAhB,mBAA8B;AAEhD,MAAI,CAAC,WAAW;AACd,+CAAQ,6BAAD;AAAA,MAA6B,YAAY;AAAA;AAAA;AAGlD,6CACG,QAAD,0CACG,OAAD;AAAA,IAAO,MAAK;AAAA,IAAK,6CAAU,gBAAD;AAAA,MAAgB;AAAA;AAAA;AAAA;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/api.ts","../src/client.ts","../src/components/DocsResultListItem/DocsResultListItem.tsx","../src/routes.ts","../src/home/components/actions.tsx","../src/home/components/columns.tsx","../src/helpers.ts","../src/home/components/DocsTable.tsx","../src/home/components/EntityListDocsTable.tsx","../src/home/components/TechDocsPageWrapper.tsx","../src/home/components/TechDocsPicker.tsx","../src/home/components/DefaultTechDocsHome.tsx","../src/home/components/DocsCardGrid.tsx","../src/home/components/EntityListDocsGrid.tsx","../src/plugin.ts","../src/reader/transformers/addBaseUrl.ts","../src/reader/transformers/addGitFeedbackLink.ts","../src/reader/transformers/rewriteDocLinks.ts","../src/reader/transformers/addLinkClickListener.ts","../src/reader/transformers/removeMkdocsHeader.ts","../src/reader/transformers/simplifyMkdocsFooter.ts","../src/reader/transformers/onCssReady.ts","../src/reader/transformers/sanitizeDOM.ts","../src/reader/transformers/injectCss.ts","../src/reader/transformers/scrollIntoAnchor.ts","../src/reader/transformers/transformer.ts","../src/reader/components/TechDocsSearch.tsx","../src/reader/components/TechDocsBuildLogs.tsx","../src/reader/components/TechDocsNotFound.tsx","../src/reader/components/TechDocsStateIndicator.tsx","../src/reader/components/useReaderState.ts","../src/reader/components/Reader.tsx","../src/reader/components/TechDocsPageHeader.tsx","../src/reader/components/LegacyTechDocsPage.tsx","../src/reader/components/TechDocsPage.tsx","../src/home/components/TechDocsCustomHome.tsx","../src/home/components/LegacyTechDocsHome.tsx","../src/home/components/TechDocsIndexPage.tsx","../src/EntityPageDocs.tsx","../src/Router.tsx"],"sourcesContent":["/*\n * Copyright 2020 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 { EntityName } from '@backstage/catalog-model';\nimport { TechDocsEntityMetadata, TechDocsMetadata } from './types';\nimport { createApiRef } from '@backstage/core-plugin-api';\n\nexport const techdocsStorageApiRef = createApiRef<TechDocsStorageApi>({\n id: 'plugin.techdocs.storageservice',\n});\n\nexport const techdocsApiRef = createApiRef<TechDocsApi>({\n id: 'plugin.techdocs.service',\n});\n\nexport type SyncResult = 'cached' | 'updated';\n\nexport interface TechDocsStorageApi {\n getApiOrigin(): Promise<string>;\n getStorageUrl(): Promise<string>;\n getBuilder(): Promise<string>;\n getEntityDocs(entityId: EntityName, path: string): Promise<string>;\n syncEntityDocs(\n entityId: EntityName,\n logHandler?: (line: string) => void,\n ): Promise<SyncResult>;\n getBaseUrl(\n oldBaseUrl: string,\n entityId: EntityName,\n path: string,\n ): Promise<string>;\n}\n\nexport interface TechDocsApi {\n getApiOrigin(): Promise<string>;\n getTechDocsMetadata(entityId: EntityName): Promise<TechDocsMetadata>;\n getEntityMetadata(entityId: EntityName): Promise<TechDocsEntityMetadata>;\n}\n","/*\n * Copyright 2020 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 { EntityName } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';\nimport { NotFoundError, ResponseError } from '@backstage/errors';\nimport { EventSourcePolyfill } from 'event-source-polyfill';\nimport { SyncResult, TechDocsApi, TechDocsStorageApi } from './api';\nimport { TechDocsEntityMetadata, TechDocsMetadata } from './types';\n\n/**\n * API to talk to `techdocs-backend`.\n */\nexport class TechDocsClient implements TechDocsApi {\n public configApi: Config;\n public discoveryApi: DiscoveryApi;\n public identityApi: IdentityApi;\n\n constructor({\n configApi,\n discoveryApi,\n identityApi,\n }: {\n configApi: Config;\n discoveryApi: DiscoveryApi;\n identityApi: IdentityApi;\n }) {\n this.configApi = configApi;\n this.discoveryApi = discoveryApi;\n this.identityApi = identityApi;\n }\n\n async getApiOrigin(): Promise<string> {\n return (\n this.configApi.getOptionalString('techdocs.requestUrl') ??\n (await this.discoveryApi.getBaseUrl('techdocs'))\n );\n }\n\n /**\n * Retrieve TechDocs metadata.\n *\n * When docs are built, we generate a techdocs_metadata.json and store it along with the generated\n * static files. It includes necessary data about the docs site. This method requests techdocs-backend\n * which retrieves the TechDocs metadata.\n *\n * @param entityId - Object containing entity data like name, namespace, etc.\n */\n async getTechDocsMetadata(entityId: EntityName): Promise<TechDocsMetadata> {\n const { kind, namespace, name } = entityId;\n\n const apiOrigin = await this.getApiOrigin();\n const requestUrl = `${apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`;\n const token = await this.identityApi.getIdToken();\n\n const request = await fetch(`${requestUrl}`, {\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n });\n\n if (!request.ok) {\n throw await ResponseError.fromResponse(request);\n }\n\n return await request.json();\n }\n\n /**\n * Retrieve metadata about an entity.\n *\n * This method requests techdocs-backend which uses the catalog APIs to respond with filtered\n * information required here.\n *\n * @param entityId - Object containing entity data like name, namespace, etc.\n */\n async getEntityMetadata(\n entityId: EntityName,\n ): Promise<TechDocsEntityMetadata> {\n const { kind, namespace, name } = entityId;\n\n const apiOrigin = await this.getApiOrigin();\n const requestUrl = `${apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`;\n const token = await this.identityApi.getIdToken();\n\n const request = await fetch(`${requestUrl}`, {\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n });\n\n if (!request.ok) {\n throw await ResponseError.fromResponse(request);\n }\n\n return await request.json();\n }\n}\n\n/**\n * API which talks to TechDocs storage to fetch files to render.\n */\nexport class TechDocsStorageClient implements TechDocsStorageApi {\n public configApi: Config;\n public discoveryApi: DiscoveryApi;\n public identityApi: IdentityApi;\n\n constructor({\n configApi,\n discoveryApi,\n identityApi,\n }: {\n configApi: Config;\n discoveryApi: DiscoveryApi;\n identityApi: IdentityApi;\n }) {\n this.configApi = configApi;\n this.discoveryApi = discoveryApi;\n this.identityApi = identityApi;\n }\n\n async getApiOrigin(): Promise<string> {\n return (\n this.configApi.getOptionalString('techdocs.requestUrl') ??\n (await this.discoveryApi.getBaseUrl('techdocs'))\n );\n }\n\n async getStorageUrl(): Promise<string> {\n return (\n this.configApi.getOptionalString('techdocs.storageUrl') ??\n `${await this.discoveryApi.getBaseUrl('techdocs')}/static/docs`\n );\n }\n\n async getBuilder(): Promise<string> {\n return this.configApi.getString('techdocs.builder');\n }\n\n /**\n * Fetch HTML content as text for an individual docs page in an entity's docs site.\n *\n * @param entityId - Object containing entity data like name, namespace, etc.\n * @param path - The unique path to an individual docs page e.g. overview/what-is-new\n * @returns HTML content of the docs page as string\n * @throws Throws error when the page is not found.\n */\n async getEntityDocs(entityId: EntityName, path: string): Promise<string> {\n const { kind, namespace, name } = entityId;\n\n const storageUrl = await this.getStorageUrl();\n const url = `${storageUrl}/${namespace}/${kind}/${name}/${path}`;\n const token = await this.identityApi.getIdToken();\n\n const request = await fetch(\n `${url.endsWith('/') ? url : `${url}/`}index.html`,\n {\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n },\n );\n\n let errorMessage = '';\n switch (request.status) {\n case 404:\n errorMessage = 'Page not found. ';\n // path is empty for the home page of an entity's docs site\n if (!path) {\n errorMessage +=\n 'This could be because there is no index.md file in the root of the docs directory of this repository.';\n }\n throw new NotFoundError(errorMessage);\n case 500:\n errorMessage =\n 'Could not generate documentation or an error in the TechDocs backend. ';\n throw new Error(errorMessage);\n default:\n // Do nothing\n break;\n }\n\n return request.text();\n }\n\n /**\n * Check if docs are on the latest version and trigger rebuild if not\n *\n * @param entityId - Object containing entity data like name, namespace, etc.\n * @param logHandler - Callback to receive log messages from the build process\n * @returns Whether documents are currently synchronized to newest version\n * @throws Throws error on error from sync endpoint in Techdocs Backend\n */\n async syncEntityDocs(\n entityId: EntityName,\n logHandler: (line: string) => void = () => {},\n ): Promise<SyncResult> {\n const { kind, namespace, name } = entityId;\n\n const apiOrigin = await this.getApiOrigin();\n const url = `${apiOrigin}/sync/${namespace}/${kind}/${name}`;\n const token = await this.identityApi.getIdToken();\n\n return new Promise((resolve, reject) => {\n // Polyfill is used to add support for custom headers and auth\n const source = new EventSourcePolyfill(url, {\n withCredentials: true,\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n });\n\n source.addEventListener('log', (e: any) => {\n if (e.data) {\n logHandler(JSON.parse(e.data));\n }\n });\n\n source.addEventListener('finish', (e: any) => {\n let updated: boolean = false;\n\n if (e.data) {\n ({ updated } = JSON.parse(e.data));\n }\n\n resolve(updated ? 'updated' : 'cached');\n });\n\n source.onerror = (e: any) => {\n source.close();\n\n switch (e.status) {\n // the endpoint returned a 404 status\n case 404:\n reject(new NotFoundError(e.message));\n return;\n\n // also handles the event-stream close. the reject is ignored if the Promise was already\n // resolved by a finish event.\n default:\n reject(new Error(e.data));\n return;\n }\n };\n });\n }\n\n async getBaseUrl(\n oldBaseUrl: string,\n entityId: EntityName,\n path: string,\n ): Promise<string> {\n const { kind, namespace, name } = entityId;\n\n const apiOrigin = await this.getApiOrigin();\n const newBaseUrl = `${apiOrigin}/static/docs/${namespace}/${kind}/${name}/${path}`;\n\n return new URL(\n oldBaseUrl,\n newBaseUrl.endsWith('/') ? newBaseUrl : `${newBaseUrl}/`,\n ).toString();\n }\n}\n","/*\n * Copyright 2021 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, { PropsWithChildren } from 'react';\nimport { Divider, ListItem, ListItemText, makeStyles } from '@material-ui/core';\nimport { Link } from '@backstage/core-components';\nimport TextTruncate from 'react-text-truncate';\n\nconst useStyles = makeStyles({\n flexContainer: {\n flexWrap: 'wrap',\n },\n itemText: {\n width: '100%',\n marginBottom: '1rem',\n },\n});\n\nexport const DocsResultListItem = ({\n result,\n lineClamp = 5,\n asListItem = true,\n asLink = true,\n title,\n}: {\n result: any;\n lineClamp?: number;\n asListItem?: boolean;\n asLink?: boolean;\n title?: string;\n}) => {\n const classes = useStyles();\n const TextItem = () => (\n <ListItemText\n className={classes.itemText}\n primaryTypographyProps={{ variant: 'h6' }}\n primary={\n title\n ? title\n : `${result.title} | ${result.entityTitle ?? result.name} docs`\n }\n secondary={\n <TextTruncate\n line={lineClamp}\n truncateText=\"…\"\n text={result.text}\n element=\"span\"\n />\n }\n />\n );\n\n const LinkWrapper = ({ children }: PropsWithChildren<{}>) =>\n asLink ? <Link to={result.location}>{children}</Link> : <>{children}</>;\n\n const ListItemWrapper = ({ children }: PropsWithChildren<{}>) =>\n asListItem ? (\n <>\n <ListItem alignItems=\"flex-start\" className={classes.flexContainer}>\n {children}\n </ListItem>\n <Divider component=\"li\" />\n </>\n ) : (\n <>{children}</>\n );\n\n return (\n <LinkWrapper>\n <ListItemWrapper>\n <TextItem />\n </ListItemWrapper>\n </LinkWrapper>\n );\n};\n","/*\n * Copyright 2020 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 { createRouteRef } from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'techdocs:index-page',\n});\n\nexport const rootDocsRouteRef = createRouteRef({\n id: 'techdocs:reader-page',\n params: ['namespace', 'kind', 'name'],\n});\n\nexport const rootCatalogDocsRouteRef = createRouteRef({\n id: 'techdocs:catalog-reader-view',\n});\n","/*\n * Copyright 2021 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 from 'react';\nimport ShareIcon from '@material-ui/icons/Share';\nimport {\n favoriteEntityIcon,\n favoriteEntityTooltip,\n} from '@backstage/plugin-catalog-react';\nimport { DocsTableRow } from './types';\n\nexport function createCopyDocsUrlAction(copyToClipboard: Function) {\n return (row: DocsTableRow) => {\n return {\n icon: () => <ShareIcon fontSize=\"small\" />,\n tooltip: 'Click to copy documentation link to clipboard',\n onClick: () =>\n copyToClipboard(`${window.location.origin}${row.resolved.docsUrl}`),\n };\n };\n}\n\nexport function createStarEntityAction(\n isStarredEntity: Function,\n toggleStarredEntity: Function,\n) {\n return ({ entity }: DocsTableRow) => {\n const isStarred = isStarredEntity(entity);\n return {\n cellStyle: { paddingLeft: '1em' },\n icon: () => favoriteEntityIcon(isStarred),\n tooltip: favoriteEntityTooltip(isStarred),\n onClick: () => toggleStarredEntity(entity),\n };\n };\n}\n","/*\n * Copyright 2021 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 from 'react';\nimport { Link, SubvalueCell, TableColumn } from '@backstage/core-components';\nimport { EntityRefLinks } from '@backstage/plugin-catalog-react';\nimport { Entity } from '@backstage/catalog-model';\nimport { DocsTableRow } from './types';\n\nfunction customTitle(entity: Entity): string {\n return entity.metadata.title || entity.metadata.name;\n}\n\nexport function createNameColumn(): TableColumn<DocsTableRow> {\n return {\n title: 'Document',\n field: 'entity.metadata.name',\n highlight: true,\n render: (row: DocsTableRow) => (\n <SubvalueCell\n value={<Link to={row.resolved.docsUrl}>{customTitle(row.entity)}</Link>}\n subvalue={row.entity.metadata.description}\n />\n ),\n };\n}\n\nexport function createOwnerColumn(): TableColumn<DocsTableRow> {\n return {\n title: 'Owner',\n field: 'resolved.ownedByRelationsTitle',\n render: ({ resolved }) => (\n <EntityRefLinks\n entityRefs={resolved.ownedByRelations}\n defaultKind=\"group\"\n />\n ),\n };\n}\n\nexport function createTypeColumn(): TableColumn<DocsTableRow> {\n return {\n title: 'Type',\n field: 'entity.spec.type',\n };\n}\n","/*\n * Copyright 2021 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 { Config } from '@backstage/config';\n\n// Lower-case entity triplets by default, but allow override.\nexport function toLowerMaybe(str: string, config: Config) {\n return config.getOptionalBoolean(\n 'techdocs.legacyUseCaseSensitiveTripletPaths',\n )\n ? str\n : str.toLocaleLowerCase('en-US');\n}\n","/*\n * Copyright 2021 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 from 'react';\nimport useCopyToClipboard from 'react-use/lib/useCopyToClipboard';\n\nimport { useRouteRef, useApi, configApiRef } from '@backstage/core-plugin-api';\nimport { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';\nimport {\n formatEntityRefTitle,\n getEntityRelations,\n} from '@backstage/plugin-catalog-react';\nimport { rootDocsRouteRef } from '../../routes';\nimport {\n Button,\n EmptyState,\n Table,\n TableColumn,\n TableProps,\n} from '@backstage/core-components';\nimport * as actionFactories from './actions';\nimport * as columnFactories from './columns';\nimport { DocsTableRow } from './types';\nimport { toLowerMaybe } from '../../helpers';\n\nexport const DocsTable = ({\n entities,\n title,\n loading,\n columns,\n actions,\n}: {\n entities: Entity[] | undefined;\n title?: string | undefined;\n loading?: boolean | undefined;\n columns?: TableColumn<DocsTableRow>[];\n actions?: TableProps<DocsTableRow>['actions'];\n}) => {\n const [, copyToClipboard] = useCopyToClipboard();\n const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);\n const config = useApi(configApiRef);\n if (!entities) return null;\n\n const documents = entities.map(entity => {\n const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY);\n return {\n entity,\n resolved: {\n docsUrl: getRouteToReaderPageFor({\n namespace: toLowerMaybe(\n entity.metadata.namespace ?? 'default',\n config,\n ),\n kind: toLowerMaybe(entity.kind, config),\n name: toLowerMaybe(entity.metadata.name, config),\n }),\n ownedByRelations,\n ownedByRelationsTitle: ownedByRelations\n .map(r => formatEntityRefTitle(r, { defaultKind: 'group' }))\n .join(', '),\n },\n };\n });\n\n const defaultColumns: TableColumn<DocsTableRow>[] = [\n columnFactories.createNameColumn(),\n columnFactories.createOwnerColumn(),\n columnFactories.createTypeColumn(),\n ];\n\n const defaultActions: TableProps<DocsTableRow>['actions'] = [\n actionFactories.createCopyDocsUrlAction(copyToClipboard),\n ];\n\n return (\n <>\n {loading || (documents && documents.length > 0) ? (\n <Table<DocsTableRow>\n isLoading={loading}\n options={{\n paging: true,\n pageSize: 20,\n search: true,\n actionsColumnIndex: -1,\n }}\n data={documents}\n columns={columns || defaultColumns}\n actions={actions || defaultActions}\n title={\n title\n ? `${title} (${documents.length})`\n : `All (${documents.length})`\n }\n />\n ) : (\n <EmptyState\n missing=\"data\"\n title=\"No documents to show\"\n description=\"Create your own document. Check out our Getting Started Information\"\n action={\n <Button\n color=\"primary\"\n to=\"https://backstage.io/docs/features/techdocs/getting-started\"\n variant=\"contained\"\n >\n DOCS\n </Button>\n }\n />\n )}\n </>\n );\n};\n","/*\n * Copyright 2021 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 from 'react';\nimport useCopyToClipboard from 'react-use/lib/useCopyToClipboard';\nimport { capitalize } from 'lodash';\nimport {\n CodeSnippet,\n TableColumn,\n TableProps,\n WarningPanel,\n} from '@backstage/core-components';\nimport {\n useEntityListProvider,\n useStarredEntities,\n} from '@backstage/plugin-catalog-react';\nimport { DocsTable } from './DocsTable';\nimport * as actionFactories from './actions';\nimport * as columnFactories from './columns';\nimport { DocsTableRow } from './types';\n\nexport const EntityListDocsTable = ({\n columns,\n actions,\n}: {\n columns?: TableColumn<DocsTableRow>[];\n actions?: TableProps<DocsTableRow>['actions'];\n}) => {\n const { loading, error, entities, filters } = useEntityListProvider();\n const { isStarredEntity, toggleStarredEntity } = useStarredEntities();\n const [, copyToClipboard] = useCopyToClipboard();\n\n const title = capitalize(filters.user?.value ?? 'all');\n\n const defaultActions = [\n actionFactories.createCopyDocsUrlAction(copyToClipboard),\n actionFactories.createStarEntityAction(\n isStarredEntity,\n toggleStarredEntity,\n ),\n ];\n\n if (error) {\n return (\n <WarningPanel\n severity=\"error\"\n title=\"Could not load available documentation.\"\n >\n <CodeSnippet language=\"text\" text={error.toString()} />\n </WarningPanel>\n );\n }\n\n return (\n <DocsTable\n title={title}\n entities={entities}\n loading={loading}\n actions={actions || defaultActions}\n columns={columns}\n />\n );\n};\n\nEntityListDocsTable.columns = columnFactories;\nEntityListDocsTable.actions = actionFactories;\n","/*\n * Copyright 2021 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 from 'react';\n\nimport { PageWithHeader } from '@backstage/core-components';\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\n\ntype Props = {\n children?: React.ReactNode;\n};\n\nexport const TechDocsPageWrapper = ({ children }: Props) => {\n const configApi = useApi(configApiRef);\n const generatedSubtitle = `Documentation available in ${\n configApi.getOptionalString('organization.name') ?? 'Backstage'\n }`;\n\n return (\n <PageWithHeader\n title=\"Documentation\"\n subtitle={generatedSubtitle}\n themeId=\"documentation\"\n >\n {children}\n </PageWithHeader>\n );\n};\n","/*\n * Copyright 2021 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 { useEffect } from 'react';\nimport {\n CATALOG_FILTER_EXISTS,\n DefaultEntityFilters,\n EntityFilter,\n useEntityListProvider,\n} from '@backstage/plugin-catalog-react';\n\nclass TechDocsFilter implements EntityFilter {\n getCatalogFilters(): Record<string, string | symbol | (string | symbol)[]> {\n return {\n 'metadata.annotations.backstage.io/techdocs-ref': CATALOG_FILTER_EXISTS,\n };\n }\n}\n\ntype CustomFilters = DefaultEntityFilters & {\n techdocs?: TechDocsFilter;\n};\n\nexport const TechDocsPicker = () => {\n const { updateFilters } = useEntityListProvider<CustomFilters>();\n\n useEffect(() => {\n updateFilters({\n techdocs: new TechDocsFilter(),\n });\n }, [updateFilters]);\n\n return null;\n};\n","/*\n * Copyright 2021 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 from 'react';\nimport {\n Content,\n ContentHeader,\n SupportButton,\n TableColumn,\n TableProps,\n} from '@backstage/core-components';\nimport {\n EntityListContainer,\n FilterContainer,\n FilteredEntityLayout,\n} from '@backstage/plugin-catalog';\nimport {\n EntityListProvider,\n EntityOwnerPicker,\n EntityTagPicker,\n UserListFilterKind,\n UserListPicker,\n} from '@backstage/plugin-catalog-react';\nimport { EntityListDocsTable } from './EntityListDocsTable';\nimport { TechDocsPageWrapper } from './TechDocsPageWrapper';\nimport { TechDocsPicker } from './TechDocsPicker';\nimport { DocsTableRow } from './types';\n\nexport const DefaultTechDocsHome = ({\n initialFilter = 'all',\n columns,\n actions,\n}: {\n initialFilter?: UserListFilterKind;\n columns?: TableColumn<DocsTableRow>[];\n actions?: TableProps<DocsTableRow>['actions'];\n}) => {\n return (\n <TechDocsPageWrapper>\n <Content>\n <ContentHeader title=\"\">\n <SupportButton>\n Discover documentation in your ecosystem.\n </SupportButton>\n </ContentHeader>\n <EntityListProvider>\n <FilteredEntityLayout>\n <FilterContainer>\n <TechDocsPicker />\n <UserListPicker initialFilter={initialFilter} />\n <EntityOwnerPicker />\n <EntityTagPicker />\n </FilterContainer>\n <EntityListContainer>\n <EntityListDocsTable actions={actions} columns={columns} />\n </EntityListContainer>\n </FilteredEntityLayout>\n </EntityListProvider>\n </Content>\n </TechDocsPageWrapper>\n );\n};\n","/*\n * Copyright 2021 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 from 'react';\n\nimport { Entity } from '@backstage/catalog-model';\nimport { useApi, useRouteRef, configApiRef } from '@backstage/core-plugin-api';\nimport { Card, CardActions, CardContent, CardMedia } from '@material-ui/core';\nimport { rootDocsRouteRef } from '../../routes';\n\nimport {\n Button,\n ItemCardGrid,\n ItemCardHeader,\n} from '@backstage/core-components';\nimport { toLowerMaybe } from '../../helpers';\n\nexport const DocsCardGrid = ({\n entities,\n}: {\n entities: Entity[] | undefined;\n}) => {\n const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);\n const config = useApi(configApiRef);\n if (!entities) return null;\n return (\n <ItemCardGrid data-testid=\"docs-explore\">\n {!entities?.length\n ? null\n : entities.map((entity, index: number) => (\n <Card key={index}>\n <CardMedia>\n <ItemCardHeader\n title={entity.metadata.title ?? entity.metadata.name}\n />\n </CardMedia>\n <CardContent>{entity.metadata.description}</CardContent>\n <CardActions>\n <Button\n to={getRouteToReaderPageFor({\n namespace: toLowerMaybe(\n entity.metadata.namespace ?? 'default',\n config,\n ),\n kind: toLowerMaybe(entity.kind, config),\n name: toLowerMaybe(entity.metadata.name, config),\n })}\n color=\"primary\"\n data-testid=\"read_docs\"\n >\n Read Docs\n </Button>\n </CardActions>\n </Card>\n ))}\n </ItemCardGrid>\n );\n};\n","/*\n * Copyright 2021 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 {\n CodeSnippet,\n Progress,\n WarningPanel,\n} from '@backstage/core-components';\nimport { useEntityListProvider } from '@backstage/plugin-catalog-react';\nimport React from 'react';\nimport { DocsCardGrid } from './DocsCardGrid';\n\nexport const EntityListDocsGrid = () => {\n const { loading, error, entities } = useEntityListProvider();\n\n if (error) {\n return (\n <WarningPanel\n severity=\"error\"\n title=\"Could not load available documentation.\"\n >\n <CodeSnippet language=\"text\" text={error.toString()} />\n </WarningPanel>\n );\n }\n\n if (loading || !entities) {\n return <Progress />;\n }\n\n entities.sort((a, b) =>\n (a.metadata.title ?? a.metadata.name).localeCompare(\n b.metadata.title ?? b.metadata.name,\n ),\n );\n\n return <DocsCardGrid entities={entities} />;\n};\n","/*\n * Copyright 2020 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 { techdocsApiRef, techdocsStorageApiRef } from './api';\nimport { TechDocsClient, TechDocsStorageClient } from './client';\nimport {\n rootDocsRouteRef,\n rootRouteRef,\n rootCatalogDocsRouteRef,\n} from './routes';\nimport {\n configApiRef,\n createApiFactory,\n createComponentExtension,\n createPlugin,\n createRoutableExtension,\n discoveryApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\n\nexport const techdocsPlugin = createPlugin({\n id: 'techdocs',\n apis: [\n createApiFactory({\n api: techdocsStorageApiRef,\n deps: {\n configApi: configApiRef,\n discoveryApi: discoveryApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ configApi, discoveryApi, identityApi }) =>\n new TechDocsStorageClient({\n configApi,\n discoveryApi,\n identityApi,\n }),\n }),\n createApiFactory({\n api: techdocsApiRef,\n deps: {\n configApi: configApiRef,\n discoveryApi: discoveryApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ configApi, discoveryApi, identityApi }) =>\n new TechDocsClient({\n configApi,\n discoveryApi,\n identityApi,\n }),\n }),\n ],\n routes: {\n root: rootRouteRef,\n docRoot: rootDocsRouteRef,\n entityContent: rootCatalogDocsRouteRef,\n },\n});\n\nexport const TechdocsPage = techdocsPlugin.provide(\n createRoutableExtension({\n name: 'TechdocsPage',\n component: () => import('./Router').then(m => m.Router),\n mountPoint: rootRouteRef,\n }),\n);\n\nexport const EntityTechdocsContent = techdocsPlugin.provide(\n createRoutableExtension({\n name: 'EntityTechdocsContent',\n component: () => import('./Router').then(m => m.EmbeddedDocsRouter),\n mountPoint: rootCatalogDocsRouteRef,\n }),\n);\n\n// takes a list of entities and renders documentation cards\nexport const DocsCardGrid = techdocsPlugin.provide(\n createComponentExtension({\n name: 'DocsCardGrid',\n component: {\n lazy: () =>\n import('./home/components/DocsCardGrid').then(m => m.DocsCardGrid),\n },\n }),\n);\n\n// takes a list of entities and renders table listing documentation\nexport const DocsTable = techdocsPlugin.provide(\n createComponentExtension({\n name: 'DocsTable',\n component: {\n lazy: () => import('./home/components/DocsTable').then(m => m.DocsTable),\n },\n }),\n);\n\n// takes a custom tabs config object and renders a documentation landing page\nexport const TechDocsCustomHome = techdocsPlugin.provide(\n createRoutableExtension({\n name: 'TechDocsCustomHome',\n component: () =>\n import('./home/components/TechDocsCustomHome').then(\n m => m.TechDocsCustomHome,\n ),\n mountPoint: rootRouteRef,\n }),\n);\n\nexport const TechDocsIndexPage = techdocsPlugin.provide(\n createRoutableExtension({\n name: 'TechDocsIndexPage',\n component: () =>\n import('./home/components/TechDocsIndexPage').then(\n m => m.TechDocsIndexPage,\n ),\n mountPoint: rootRouteRef,\n }),\n);\n\nexport const TechDocsReaderPage = techdocsPlugin.provide(\n createRoutableExtension({\n name: 'TechDocsReaderPage',\n component: () =>\n import('./reader/components/TechDocsPage').then(m => m.TechDocsPage),\n mountPoint: rootDocsRouteRef,\n }),\n);\n","/*\n * Copyright 2020 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 { EntityName } from '@backstage/catalog-model';\nimport { TechDocsStorageApi } from '../../api';\nimport type { Transformer } from './transformer';\n\ntype AddBaseUrlOptions = {\n techdocsStorageApi: TechDocsStorageApi;\n entityId: EntityName;\n path: string;\n};\n\n/**\n * TechDocs backend serves SVGs with text/plain content-type for security. This\n * helper determines if an SVG is being loaded from the backend, and thus needs\n * inlining to be displayed properly.\n */\nconst isSvgNeedingInlining = (\n attrName: string,\n attrVal: string,\n apiOrigin: string,\n) => {\n const isSrcToSvg = attrName === 'src' && attrVal.endsWith('.svg');\n const isRelativeUrl = !attrVal.match(/^([a-z]*:)?\\/\\//i);\n const pointsToOurBackend = attrVal.startsWith(apiOrigin);\n return isSrcToSvg && (isRelativeUrl || pointsToOurBackend);\n};\n\nexport const addBaseUrl = ({\n techdocsStorageApi,\n entityId,\n path,\n}: AddBaseUrlOptions): Transformer => {\n return async dom => {\n const apiOrigin = await techdocsStorageApi.getApiOrigin();\n\n const updateDom = async <T extends Element>(\n list: HTMLCollectionOf<T> | NodeListOf<T>,\n attributeName: string,\n ) => {\n for (const elem of list) {\n if (elem.hasAttribute(attributeName)) {\n const elemAttribute = elem.getAttribute(attributeName);\n if (!elemAttribute) return;\n\n // Special handling for SVG images.\n const newValue = await techdocsStorageApi.getBaseUrl(\n elemAttribute,\n entityId,\n path,\n );\n\n if (isSvgNeedingInlining(attributeName, elemAttribute, apiOrigin)) {\n try {\n const svg = await fetch(newValue, { credentials: 'include' });\n const svgContent = await svg.text();\n elem.setAttribute(\n attributeName,\n `data:image/svg+xml;base64,${btoa(svgContent)}`,\n );\n } catch (e) {\n elem.setAttribute('alt', `Error: ${elemAttribute}`);\n }\n } else {\n elem.setAttribute(attributeName, newValue);\n }\n }\n }\n };\n\n await Promise.all([\n updateDom<HTMLImageElement>(dom.querySelectorAll('img'), 'src'),\n updateDom<HTMLScriptElement>(dom.querySelectorAll('script'), 'src'),\n updateDom<HTMLLinkElement>(dom.querySelectorAll('link'), 'href'),\n updateDom<HTMLAnchorElement>(dom.querySelectorAll('a[download]'), 'href'),\n ]);\n\n return dom;\n };\n};\n","/*\n * Copyright 2021 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 type { Transformer } from './index';\nimport {\n replaceGitHubUrlType,\n ScmIntegrationRegistry,\n} from '@backstage/integration';\nimport FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined';\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport parseGitUrl from 'git-url-parse';\n\n// requires repo\nexport const addGitFeedbackLink = (\n scmIntegrationsApi: ScmIntegrationRegistry,\n): Transformer => {\n return dom => {\n // attempting to use selectors that are more likely to be static as MkDocs updates over time\n const sourceAnchor = dom.querySelector(\n '[title=\"Edit this page\"]',\n ) as HTMLAnchorElement;\n\n // don't show if edit link not available in raw page\n if (!sourceAnchor || !sourceAnchor.href) {\n return dom;\n }\n\n const sourceURL = new URL(sourceAnchor.href);\n const integration = scmIntegrationsApi.byUrl(sourceURL);\n\n // don't show if can't identify edit link hostname as a gitlab/github hosting\n if (integration?.type !== 'github' && integration?.type !== 'gitlab') {\n return dom;\n }\n\n // topmost h1 only contains title for whole page\n const title = (dom.querySelector('article>h1') as HTMLElement).childNodes[0]\n .textContent;\n const issueTitle = encodeURIComponent(`Documentation Feedback: ${title}`);\n const issueDesc = encodeURIComponent(\n `Page source:\\n${sourceAnchor.href}\\n\\nFeedback:`,\n );\n\n // Convert GitHub edit url to blob type so it can be parsed by git-url-parse correctly\n const gitUrl =\n integration?.type === 'github'\n ? replaceGitHubUrlType(sourceURL.href, 'blob')\n : sourceURL.href;\n const gitInfo = parseGitUrl(gitUrl);\n const repoPath = `/${gitInfo.organization}/${gitInfo.name}`;\n\n const feedbackLink = sourceAnchor.cloneNode() as HTMLAnchorElement;\n switch (integration?.type) {\n case 'gitlab':\n feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?issue[title]=${issueTitle}&issue[description]=${issueDesc}`;\n break;\n case 'github':\n feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?title=${issueTitle}&body=${issueDesc}`;\n break;\n default:\n return dom;\n }\n ReactDOM.render(React.createElement(FeedbackOutlinedIcon), feedbackLink);\n feedbackLink.style.paddingLeft = '5px';\n feedbackLink.title = 'Leave feedback for this page';\n feedbackLink.id = 'git-feedback-link';\n sourceAnchor?.insertAdjacentElement('beforebegin', feedbackLink);\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\nexport const rewriteDocLinks = (): Transformer => {\n return dom => {\n const updateDom = <T extends Element>(\n list: Array<T>,\n attributeName: string,\n ): void => {\n Array.from(list)\n .filter(elem => elem.hasAttribute(attributeName))\n .forEach((elem: T) => {\n const elemAttribute = elem.getAttribute(attributeName);\n if (elemAttribute) {\n // if link is external, add target to open in a new window or tab\n if (elemAttribute.match(/^https?:\\/\\//i)) {\n elem.setAttribute('target', '_blank');\n }\n\n try {\n const normalizedWindowLocation = normalizeUrl(\n window.location.href,\n );\n elem.setAttribute(\n attributeName,\n new URL(elemAttribute, normalizedWindowLocation).toString(),\n );\n } catch (_e) {\n // Non-parseable links should be re-written as plain text.\n elem.replaceWith(elem.textContent || elemAttribute);\n }\n }\n });\n };\n\n updateDom(Array.from(dom.getElementsByTagName('a')), 'href');\n\n return dom;\n };\n};\n\n/** Make sure that the input url always ends with a '/' */\nexport function normalizeUrl(input: string): string {\n const url = new URL(input);\n\n if (!url.pathname.endsWith('/') && !url.pathname.endsWith('.html')) {\n url.pathname += '/';\n }\n\n return url.toString();\n}\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\ntype AddLinkClickListenerOptions = {\n baseUrl: string;\n onClick: (e: MouseEvent, newUrl: string) => void;\n};\n\nexport const addLinkClickListener = ({\n baseUrl,\n onClick,\n}: AddLinkClickListenerOptions): Transformer => {\n return dom => {\n Array.from(dom.getElementsByTagName('a')).forEach(elem => {\n elem.addEventListener('click', (e: MouseEvent) => {\n const target = elem as HTMLAnchorElement;\n const href = target.getAttribute('href');\n\n if (!href) return;\n if (href.startsWith(baseUrl)) {\n e.preventDefault();\n onClick(e, href);\n }\n });\n });\n\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\nexport const removeMkdocsHeader = (): Transformer => {\n return dom => {\n // Remove the header\n dom.querySelector('.md-header')?.remove();\n\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\nexport const simplifyMkdocsFooter = (): Transformer => {\n return dom => {\n // Remove mkdocs copyright\n dom.querySelector('.md-footer-copyright')?.remove();\n\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\ntype OnCssReadyOptions = {\n docStorageUrl: string;\n onLoading: (dom: Element) => void;\n onLoaded: (dom: Element) => void;\n};\n\nexport const onCssReady = ({\n docStorageUrl,\n onLoading,\n onLoaded,\n}: OnCssReadyOptions): Transformer => {\n return dom => {\n const cssPages = Array.from(\n dom.querySelectorAll('head > link[rel=\"stylesheet\"]'),\n ).filter(elem => elem.getAttribute('href')?.startsWith(docStorageUrl));\n\n let count = cssPages.length;\n\n if (count > 0) {\n onLoading(dom);\n }\n\n cssPages.forEach(cssPage =>\n cssPage.addEventListener('load', () => {\n count -= 1;\n\n if (count === 0) {\n onLoaded(dom);\n }\n }),\n );\n\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 TECHDOCS_CSS = /main\\.[A-Fa-f0-9]{8}\\.min\\.css$/;\nconst GOOGLE_FONTS = /^https:\\/\\/fonts\\.googleapis\\.com/;\nconst GSTATIC_FONTS = /^https:\\/\\/fonts\\.gstatic\\.com/;\n\nexport const safeLinksHook = (node: Element) => {\n if (node.nodeName && node.nodeName === 'LINK') {\n const href = node.getAttribute('href') || '';\n if (href.match(TECHDOCS_CSS)) {\n node.setAttribute('rel', 'stylesheet');\n }\n if (href.match(GOOGLE_FONTS)) {\n node.setAttribute('rel', 'stylesheet');\n }\n if (href.match(GSTATIC_FONTS)) {\n node.setAttribute('rel', 'preconnect');\n }\n }\n return node;\n};\n\nconst filterIframeHook = (allowedIframeHosts: string[]) => (node: Element) => {\n if (node.nodeName === 'IFRAME') {\n const src = node.getAttribute('src');\n if (!src) {\n node.remove();\n return node;\n }\n\n try {\n const srcUrl = new URL(src);\n const isMatch = allowedIframeHosts.some(host => srcUrl.host === host);\n if (!isMatch) {\n node.remove();\n }\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn(`Invalid iframe src, ${error}`);\n node.remove();\n }\n }\n return node;\n};\n\nimport { Config } from '@backstage/config';\nimport DOMPurify from 'dompurify';\nimport type { Transformer } from './transformer';\n\nexport const sanitizeDOM = (config?: Config): Transformer => {\n const allowedIframeHosts =\n config?.getOptionalStringArray('allowedIframeHosts') || [];\n\n return dom => {\n DOMPurify.addHook('afterSanitizeAttributes', safeLinksHook);\n const addTags = ['link'];\n\n if (allowedIframeHosts.length > 0) {\n DOMPurify.addHook(\n 'beforeSanitizeElements',\n filterIframeHook(allowedIframeHosts),\n );\n addTags.push('iframe');\n }\n\n return DOMPurify.sanitize(dom.innerHTML, {\n ADD_TAGS: addTags,\n FORBID_TAGS: ['style'],\n WHOLE_DOCUMENT: true,\n RETURN_DOM: true,\n });\n };\n};\n","/*\n * Copyright 2020 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 type { Transformer } from './transformer';\n\ntype InjectCssOptions = {\n css: string;\n};\n\nexport const injectCss = ({ css }: InjectCssOptions): Transformer => {\n return dom => {\n dom\n .getElementsByTagName('head')[0]\n .insertAdjacentHTML('beforeend', `<style>${css}</style>`);\n\n return dom;\n };\n};\n","/*\n * Copyright 2021 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 type { Transformer } from './transformer';\n\nexport const scrollIntoAnchor = (): Transformer => {\n return dom => {\n setTimeout(() => {\n // Scroll to the desired anchor on initial navigation\n if (window.location.hash) {\n const hash = window.location.hash.slice(1);\n dom?.querySelector(`#${hash}`)?.scrollIntoView();\n }\n }, 200);\n return dom;\n };\n};\n","/*\n * Copyright 2020 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 type Transformer = (dom: Element) => Element | Promise<Element>;\n\nexport const transform = async (\n html: string | Element,\n transformers: Transformer[],\n): Promise<Element> => {\n let dom: Element;\n\n if (typeof html === 'string') {\n dom = new DOMParser().parseFromString(html, 'text/html').documentElement;\n } else if (html instanceof Element) {\n dom = html;\n } else {\n throw new Error('dom is not a recognized type');\n }\n\n for (const transformer of transformers) {\n dom = await transformer(dom);\n }\n\n return dom;\n};\n","/*\n * Copyright 2021 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 { EntityName } from '@backstage/catalog-model';\nimport { SearchContextProvider, useSearch } from '@backstage/plugin-search';\nimport {\n CircularProgress,\n Grid,\n IconButton,\n InputAdornment,\n TextField,\n} from '@material-ui/core';\nimport SearchIcon from '@material-ui/icons/Search';\nimport Autocomplete from '@material-ui/lab/Autocomplete';\nimport React, { ChangeEvent, useEffect, useState } from 'react';\nimport { useNavigate } from 'react-router';\nimport useDebounce from 'react-use/lib/useDebounce';\nimport { DocsResultListItem } from '../../components/DocsResultListItem';\n\ntype TechDocsSearchProps = {\n entityId: EntityName;\n debounceTime?: number;\n};\n\ntype TechDocsDoc = {\n namespace: string;\n kind: string;\n name: string;\n path: string;\n location: string;\n title: string;\n};\n\ntype TechDocsSearchResult = {\n type: string;\n document: TechDocsDoc;\n};\n\nconst TechDocsSearchBar = ({\n entityId,\n debounceTime = 150,\n}: TechDocsSearchProps) => {\n const [open, setOpen] = useState(false);\n const navigate = useNavigate();\n const {\n term,\n setTerm,\n result: { loading, value: searchVal },\n } = useSearch();\n const [options, setOptions] = useState<any[]>([]);\n useEffect(() => {\n let mounted = true;\n\n if (mounted && searchVal) {\n // TODO: Change this into getting only subset of search results from the BE in the first place\n // once pagination is implemented for search engines\n // See: https://github.com/backstage/backstage/issues/6062\n const searchResults = searchVal.results.slice(0, 10);\n setOptions(searchResults);\n }\n return () => {\n mounted = false;\n };\n }, [loading, searchVal]);\n\n const [value, setValue] = useState<string>(term);\n\n useDebounce(() => setTerm(value), debounceTime, [value]);\n\n const handleQuery = (e: ChangeEvent<HTMLInputElement>) => {\n if (!open) {\n setOpen(true);\n }\n setValue(e.target.value);\n };\n\n const handleSelection = (_: any, selection: TechDocsSearchResult | null) => {\n if (selection?.document) {\n const { location } = selection.document;\n navigate(location);\n }\n };\n\n return (\n <Grid item xs={12}>\n <Autocomplete\n data-testid=\"techdocs-search-bar\"\n size=\"small\"\n open={open}\n getOptionLabel={() => ''}\n filterOptions={x => {\n return x; // This is needed to get renderOption to be called after options change. Bug in material-ui?\n }}\n onClose={() => {\n setOpen(false);\n }}\n onFocus={() => {\n setOpen(true);\n }}\n onChange={handleSelection}\n blurOnSelect\n noOptionsText=\"No results found\"\n value={null}\n options={options}\n renderOption={({ document }) => (\n <DocsResultListItem\n result={document}\n lineClamp={3}\n asListItem={false}\n asLink={false}\n title={document.title}\n />\n )}\n loading={loading}\n renderInput={params => (\n <TextField\n {...params}\n data-testid=\"techdocs-search-bar-input\"\n variant=\"outlined\"\n fullWidth\n placeholder={`Search ${entityId.name} docs`}\n value={value}\n onChange={handleQuery}\n InputProps={{\n ...params.InputProps,\n startAdornment: (\n <InputAdornment position=\"start\">\n <IconButton aria-label=\"Query\" disabled>\n <SearchIcon />\n </IconButton>\n </InputAdornment>\n ),\n endAdornment: (\n <React.Fragment>\n {loading ? (\n <CircularProgress color=\"inherit\" size={20} />\n ) : null}\n {params.InputProps.endAdornment}\n </React.Fragment>\n ),\n }}\n />\n )}\n />\n </Grid>\n );\n};\n\nexport const TechDocsSearch = (props: TechDocsSearchProps) => {\n const initialState = {\n term: '',\n types: ['techdocs'],\n pageCursor: '',\n filters: props.entityId,\n };\n return (\n <SearchContextProvider initialState={initialState}>\n <TechDocsSearchBar {...props} />\n </SearchContextProvider>\n );\n};\n","/*\n * Copyright 2021 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 { LogViewer } from '@backstage/core-components';\nimport {\n Button,\n createStyles,\n Drawer,\n Grid,\n IconButton,\n makeStyles,\n Theme,\n Typography,\n} from '@material-ui/core';\nimport Close from '@material-ui/icons/Close';\nimport React, { useState } from 'react';\n\nconst useDrawerStyles = makeStyles((theme: Theme) =>\n createStyles({\n paper: {\n width: '100%',\n [theme.breakpoints.up('sm')]: {\n width: '75%',\n },\n [theme.breakpoints.up('md')]: {\n width: '50%',\n },\n padding: theme.spacing(2.5),\n },\n root: {\n height: '100%',\n overflow: 'hidden',\n },\n logs: {\n background: theme.palette.background.default,\n },\n }),\n);\n\nexport const TechDocsBuildLogsDrawerContent = ({\n buildLog,\n onClose,\n}: {\n buildLog: string[];\n onClose: () => void;\n}) => {\n const classes = useDrawerStyles();\n const logText =\n buildLog.length === 0 ? 'Waiting for logs...' : buildLog.join('\\n');\n return (\n <Grid\n container\n direction=\"column\"\n className={classes.root}\n spacing={0}\n wrap=\"nowrap\"\n >\n <Grid\n item\n container\n justifyContent=\"space-between\"\n alignItems=\"center\"\n spacing={0}\n wrap=\"nowrap\"\n >\n <Typography variant=\"h5\">Build Details</Typography>\n <IconButton\n key=\"dismiss\"\n title=\"Close the drawer\"\n onClick={onClose}\n color=\"inherit\"\n >\n <Close />\n </IconButton>\n </Grid>\n <LogViewer text={logText} classes={{ root: classes.logs }} />\n </Grid>\n );\n};\n\nexport const TechDocsBuildLogs = ({ buildLog }: { buildLog: string[] }) => {\n const classes = useDrawerStyles();\n const [open, setOpen] = useState(false);\n\n return (\n <>\n <Button color=\"inherit\" onClick={() => setOpen(true)}>\n Show Build Logs\n </Button>\n <Drawer\n classes={{ paper: classes.paper }}\n anchor=\"right\"\n open={open}\n onClose={() => setOpen(false)}\n >\n <TechDocsBuildLogsDrawerContent\n buildLog={buildLog}\n onClose={() => setOpen(false)}\n />\n </Drawer>\n </>\n );\n};\n","/*\n * Copyright 2020 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 from 'react';\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\nimport { ErrorPage } from '@backstage/core-components';\n\ntype Props = {\n errorMessage?: string;\n};\n\nexport const TechDocsNotFound = ({ errorMessage }: Props) => {\n const techdocsBuilder =\n useApi(configApiRef).getOptionalString('techdocs.builder');\n\n let additionalInfo = '';\n if (techdocsBuilder !== 'local') {\n additionalInfo =\n \"Note that techdocs.builder is not set to 'local' in your config, which means this Backstage app will not \" +\n \"generate docs if they are not found. Make sure the project's docs are generated and published by some external \" +\n \"process (e.g. CI/CD pipeline). Or change techdocs.builder to 'local' to generate docs from this Backstage \" +\n 'instance.';\n }\n\n return (\n <ErrorPage\n status=\"404\"\n statusMessage={errorMessage || 'Documentation not found'}\n additionalInfo={additionalInfo}\n />\n );\n};\n","/*\n * Copyright 2021 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 from 'react';\nimport { Progress } from '@backstage/core-components';\nimport { CircularProgress, Button, makeStyles } from '@material-ui/core';\nimport { Alert } from '@material-ui/lab';\n\nimport { TechDocsBuildLogs } from './TechDocsBuildLogs';\nimport { TechDocsNotFound } from './TechDocsNotFound';\nimport { useTechDocsReader } from './Reader';\n\nconst useStyles = makeStyles(() => ({\n message: {\n // `word-break: break-word` is deprecated, but gives legacy support to browsers not supporting `overflow-wrap` yet\n // https://developer.mozilla.org/en-US/docs/Web/CSS/word-break\n wordBreak: 'break-word',\n overflowWrap: 'anywhere',\n },\n}));\n\n/**\n * Note: this component is currently being exported so that we can rapidly\n * iterate on alternative <Reader /> implementations that extend core\n * functionality. There is no guarantee that this component will continue to be\n * exported by the package in the future!\n *\n * todo: Make public or stop exporting (ctrl+f \"altReaderExperiments\")\n * @internal\n */\nexport const TechDocsStateIndicator = () => {\n let StateAlert: JSX.Element | null = null;\n const classes = useStyles();\n\n const {\n state,\n contentReload,\n contentErrorMessage,\n syncErrorMessage,\n buildLog,\n } = useTechDocsReader();\n\n const ReaderProgress = state === 'CHECKING' ? <Progress /> : null;\n\n if (state === 'INITIAL_BUILD') {\n StateAlert = (\n <Alert\n variant=\"outlined\"\n severity=\"info\"\n icon={<CircularProgress size=\"24px\" />}\n action={<TechDocsBuildLogs buildLog={buildLog} />}\n >\n Documentation is accessed for the first time and is being prepared. The\n subsequent loads are much faster.\n </Alert>\n );\n }\n\n if (state === 'CONTENT_STALE_REFRESHING') {\n StateAlert = (\n <Alert\n variant=\"outlined\"\n severity=\"info\"\n icon={<CircularProgress size=\"24px\" />}\n action={<TechDocsBuildLogs buildLog={buildLog} />}\n >\n A newer version of this documentation is being prepared and will be\n available shortly.\n </Alert>\n );\n }\n\n if (state === 'CONTENT_STALE_READY') {\n StateAlert = (\n <Alert\n variant=\"outlined\"\n severity=\"success\"\n action={\n <Button color=\"inherit\" onClick={() => contentReload()}>\n Refresh\n </Button>\n }\n >\n A newer version of this documentation is now available, please refresh\n to view.\n </Alert>\n );\n }\n\n if (state === 'CONTENT_STALE_ERROR') {\n StateAlert = (\n <Alert\n variant=\"outlined\"\n severity=\"error\"\n action={<TechDocsBuildLogs buildLog={buildLog} />}\n classes={{ message: classes.message }}\n >\n Building a newer version of this documentation failed.{' '}\n {syncErrorMessage}\n </Alert>\n );\n }\n\n if (state === 'CONTENT_NOT_FOUND') {\n StateAlert = (\n <>\n {syncErrorMessage && (\n <Alert\n variant=\"outlined\"\n severity=\"error\"\n action={<TechDocsBuildLogs buildLog={buildLog} />}\n classes={{ message: classes.message }}\n >\n Building a newer version of this documentation failed.{' '}\n {syncErrorMessage}\n </Alert>\n )}\n <TechDocsNotFound errorMessage={contentErrorMessage} />\n </>\n );\n }\n\n return (\n <>\n {ReaderProgress}\n {StateAlert}\n </>\n );\n};\n","/*\n * Copyright 2021 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 { useApi } from '@backstage/core-plugin-api';\nimport { useMemo, useReducer, useRef } from 'react';\nimport useAsync from 'react-use/lib/useAsync';\nimport useAsyncRetry from 'react-use/lib/useAsyncRetry';\nimport { techdocsStorageApiRef } from '../../api';\n\n/**\n * A state representation that is used to configure the UI of <Reader />\n */\ntype ContentStateTypes =\n /** There is nothing to display but a loading indicator */\n | 'CHECKING'\n\n /** There is no content yet -> present a full screen loading page */\n | 'INITIAL_BUILD'\n\n /** There is content, but the backend is about to update it */\n | 'CONTENT_STALE_REFRESHING'\n\n /** There is content, but after a reload, the content will be different */\n | 'CONTENT_STALE_READY'\n\n /** There is content, the backend tried to update it, but failed */\n | 'CONTENT_STALE_ERROR'\n\n /** There is nothing to see but a \"not found\" page. Is also shown on page load errors */\n | 'CONTENT_NOT_FOUND'\n\n /** There is only the latest and greatest content */\n | 'CONTENT_FRESH';\n\n/**\n * Calculate the state that should be reported to the display component.\n */\nexport function calculateDisplayState({\n contentLoading,\n content,\n activeSyncState,\n}: Pick<\n ReducerState,\n 'contentLoading' | 'content' | 'activeSyncState'\n>): ContentStateTypes {\n // we have nothing to display yet\n if (contentLoading) {\n return 'CHECKING';\n }\n\n // the build is ready, but it triggered a content reload and the content variable is not trusted\n if (activeSyncState === 'BUILD_READY_RELOAD') {\n return 'CHECKING';\n }\n\n // there is no content, but the sync process is still evaluating\n if (!content && activeSyncState === 'CHECKING') {\n return 'CHECKING';\n }\n\n // there is no content yet so we assume that we are building it for the first time\n if (!content && activeSyncState === 'BUILDING') {\n return 'INITIAL_BUILD';\n }\n\n // if there is still no content after building, it might just not exist\n if (!content) {\n return 'CONTENT_NOT_FOUND';\n }\n\n // we are still building, but we already show stale content\n if (activeSyncState === 'BUILDING') {\n return 'CONTENT_STALE_REFRESHING';\n }\n\n // the build is ready, but the content is still stale\n if (activeSyncState === 'BUILD_READY') {\n return 'CONTENT_STALE_READY';\n }\n\n // the build failed, but the content is still stale\n if (activeSyncState === 'ERROR') {\n return 'CONTENT_STALE_ERROR';\n }\n\n // seems like the content is up-to-date (or we don't know yet and the sync process is still evaluating in the background)\n return 'CONTENT_FRESH';\n}\n\n/**\n * The state of the synchronization task. It checks whether the docs are\n * up-to-date. If they aren't, it triggers a build.\n */\ntype SyncStates =\n /** Checking if it should be synced */\n | 'CHECKING'\n\n /** Building the documentation */\n | 'BUILDING'\n\n /** Finished building the documentation */\n | 'BUILD_READY'\n\n /**\n * Finished building the documentation and triggered a content reload.\n * This state is left toward UP_TO_DATE when the content loading has finished.\n */\n | 'BUILD_READY_RELOAD'\n\n /** No need for a sync. The content was already up-to-date. */\n | 'UP_TO_DATE'\n\n /** An error occurred */\n | 'ERROR';\n\ntype ReducerActions =\n | {\n type: 'sync';\n state: SyncStates;\n syncError?: Error;\n }\n | { type: 'contentLoading' }\n | {\n type: 'content';\n path?: string;\n content?: string;\n contentError?: Error;\n }\n | { type: 'buildLog'; log: string };\n\ntype ReducerState = {\n /**\n * The path of the current page\n */\n path: string;\n\n /**\n * The current sync state\n */\n activeSyncState: SyncStates;\n\n /**\n * If true, the content is downloading from the storage.\n */\n contentLoading: boolean;\n /**\n * The content that has been downloaded and should be displayed.\n */\n content?: string;\n\n contentError?: Error;\n syncError?: Error;\n\n /**\n * A list of log messages that were emitted by the build process.\n */\n buildLog: string[];\n};\n\nexport function reducer(\n oldState: ReducerState,\n action: ReducerActions,\n): ReducerState {\n const newState = { ...oldState };\n\n switch (action.type) {\n case 'sync':\n // reset the build log when a new check starts\n if (action.state === 'CHECKING') {\n newState.buildLog = [];\n }\n\n newState.activeSyncState = action.state;\n newState.syncError = action.syncError;\n break;\n\n case 'contentLoading':\n newState.contentLoading = true;\n\n // only reset errors but keep the old content until it is replaced by the 'content' action\n newState.contentError = undefined;\n break;\n\n case 'content':\n // only override the path if it is part of the action\n if (typeof action.path === 'string') {\n newState.path = action.path;\n }\n\n newState.contentLoading = false;\n newState.content = action.content;\n newState.contentError = action.contentError;\n break;\n\n case 'buildLog':\n newState.buildLog = newState.buildLog.concat(action.log);\n break;\n\n default:\n throw new Error();\n }\n\n // a content update loads fresh content so the build is updated to being up-to-date\n if (\n ['BUILD_READY', 'BUILD_READY_RELOAD'].includes(newState.activeSyncState) &&\n ['contentLoading', 'content'].includes(action.type)\n ) {\n newState.activeSyncState = 'UP_TO_DATE';\n newState.buildLog = [];\n }\n\n return newState;\n}\n\nexport function useReaderState(\n kind: string,\n namespace: string,\n name: string,\n path: string,\n): {\n state: ContentStateTypes;\n path: string;\n contentReload: () => void;\n content?: string;\n contentErrorMessage?: string;\n syncErrorMessage?: string;\n buildLog: string[];\n} {\n const [state, dispatch] = useReducer(reducer, {\n activeSyncState: 'CHECKING',\n path,\n contentLoading: true,\n buildLog: [],\n });\n\n const techdocsStorageApi = useApi(techdocsStorageApiRef);\n\n // try to load the content. the function will fire events and we don't care for the return values\n const { retry: contentReload } = useAsyncRetry(async () => {\n dispatch({ type: 'contentLoading' });\n\n try {\n const entityDocs = await techdocsStorageApi.getEntityDocs(\n { kind, namespace, name },\n path,\n );\n\n // update content and path at the same time\n dispatch({ type: 'content', content: entityDocs, path });\n\n return entityDocs;\n } catch (e) {\n dispatch({ type: 'content', contentError: e, path });\n }\n\n return undefined;\n }, [techdocsStorageApi, kind, namespace, name, path]);\n\n // create a ref that holds the latest content. This provides a useAsync hook\n // with the latest content without restarting the useAsync hook.\n const contentRef = useRef<{ content?: string; reload: () => void }>({\n content: undefined,\n reload: () => {},\n });\n contentRef.current = { content: state.content, reload: contentReload };\n\n // try to derive the state. the function will fire events and we don't care for the return values\n useAsync(async () => {\n dispatch({ type: 'sync', state: 'CHECKING' });\n\n // should only switch to BUILDING if the request takes more than 1 seconds\n const buildingTimeout = setTimeout(() => {\n dispatch({ type: 'sync', state: 'BUILDING' });\n }, 1000);\n\n try {\n const result = await techdocsStorageApi.syncEntityDocs(\n {\n kind,\n namespace,\n name,\n },\n log => {\n dispatch({ type: 'buildLog', log });\n },\n );\n\n switch (result) {\n case 'updated':\n // if there was no content prior to building, retry the loading\n if (!contentRef.current.content) {\n contentRef.current.reload();\n dispatch({ type: 'sync', state: 'BUILD_READY_RELOAD' });\n } else {\n dispatch({ type: 'sync', state: 'BUILD_READY' });\n }\n break;\n case 'cached':\n dispatch({ type: 'sync', state: 'UP_TO_DATE' });\n break;\n\n default:\n dispatch({\n type: 'sync',\n state: 'ERROR',\n syncError: new Error('Unexpected return state'),\n });\n break;\n }\n } catch (e) {\n dispatch({ type: 'sync', state: 'ERROR', syncError: e });\n } finally {\n // Cancel the timer that sets the state \"BUILDING\"\n clearTimeout(buildingTimeout);\n }\n }, [kind, name, namespace, techdocsStorageApi, dispatch, contentRef]);\n\n const displayState = useMemo(\n () =>\n calculateDisplayState({\n activeSyncState: state.activeSyncState,\n contentLoading: state.contentLoading,\n content: state.content,\n }),\n [state.activeSyncState, state.content, state.contentLoading],\n );\n\n return {\n state: displayState,\n contentReload,\n path: state.path,\n content: state.content,\n contentErrorMessage: state.contentError?.toString(),\n syncErrorMessage: state.syncError?.toString(),\n buildLog: state.buildLog,\n };\n}\n","/*\n * Copyright 2020 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, {\n PropsWithChildren,\n ComponentType,\n createContext,\n useContext,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { Grid, makeStyles, useTheme } from '@material-ui/core';\n\nimport { EntityName } from '@backstage/catalog-model';\nimport { useApi, configApiRef } from '@backstage/core-plugin-api';\nimport { scmIntegrationsApiRef } from '@backstage/integration-react';\nimport { BackstageTheme } from '@backstage/theme';\nimport { SidebarPinStateContext } from '@backstage/core-components';\n\nimport { techdocsStorageApiRef } from '../../api';\n\nimport {\n addBaseUrl,\n addGitFeedbackLink,\n addLinkClickListener,\n injectCss,\n onCssReady,\n removeMkdocsHeader,\n rewriteDocLinks,\n sanitizeDOM,\n simplifyMkdocsFooter,\n scrollIntoAnchor,\n transform as transformer,\n} from '../transformers';\n\nimport { TechDocsSearch } from './TechDocsSearch';\nimport { TechDocsStateIndicator } from './TechDocsStateIndicator';\nimport { useReaderState } from './useReaderState';\n\ntype Props = {\n entityRef: EntityName;\n withSearch?: boolean;\n onReady?: () => void;\n};\n\nconst useStyles = makeStyles<BackstageTheme>(theme => ({\n searchBar: {\n marginLeft: '20rem',\n maxWidth: 'calc(100% - 20rem * 2 - 3rem)',\n marginTop: theme.spacing(1),\n '@media screen and (max-width: 76.1875em)': {\n marginLeft: '10rem',\n maxWidth: 'calc(100% - 10rem)',\n },\n },\n}));\n\ntype TechDocsReaderValue = ReturnType<typeof useReaderState>;\n\nconst TechDocsReaderContext = createContext<TechDocsReaderValue>(\n {} as TechDocsReaderValue,\n);\n\nconst TechDocsReaderProvider = ({\n children,\n entityRef,\n}: PropsWithChildren<{ entityRef: EntityName }>) => {\n const { '*': path } = useParams();\n const { kind, namespace, name } = entityRef;\n const value = useReaderState(kind, namespace, name, path);\n return (\n <TechDocsReaderContext.Provider value={value}>\n {children}\n </TechDocsReaderContext.Provider>\n );\n};\n\n/**\n * Note: this HOC is currently being exported so that we can rapidly\n * iterate on alternative <Reader /> implementations that extend core\n * functionality. There is no guarantee that this HOC will continue to be\n * exported by the package in the future!\n *\n * todo: Make public or stop exporting (ctrl+f \"altReaderExperiments\")\n * @internal\n */\nexport const withTechDocsReaderProvider =\n <T extends {}>(Component: ComponentType<T>, entityRef: EntityName) =>\n (props: T) =>\n (\n <TechDocsReaderProvider entityRef={entityRef}>\n <Component {...props} />\n </TechDocsReaderProvider>\n );\n\n/**\n * Note: this hook is currently being exported so that we can rapidly\n * iterate on alternative <Reader /> implementations that extend core\n * functionality. There is no guarantee that this hook will continue to be\n * exported by the package in the future!\n *\n * todo: Make public or stop exporting (ctrl+f \"altReaderExperiments\")\n * @internal\n */\nexport const useTechDocsReader = () => useContext(TechDocsReaderContext);\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 *\n * Note: this hook is currently being exported so that we can rapidly iterate\n * on alternative <Reader /> implementations that extend core functionality.\n * There is no guarantee that this hook will continue to be exported by the\n * package in the future!\n *\n * todo: Make public or stop exporting (see others: \"altReaderExperiments\")\n * @internal\n */\nexport const useTechDocsReaderDom = (entityRef: EntityName): Element | null => {\n const navigate = useNavigate();\n const theme = useTheme<BackstageTheme>();\n const techdocsStorageApi = useApi(techdocsStorageApiRef);\n const scmIntegrationsApi = useApi(scmIntegrationsApiRef);\n const techdocsSanitizer = useApi(configApiRef);\n const { namespace = '', kind = '', name = '' } = entityRef;\n const { state, path, content: rawPage } = useTechDocsReader();\n\n const [sidebars, setSidebars] = useState<HTMLElement[]>();\n const [dom, setDom] = useState<HTMLElement | null>(null);\n\n // sidebar pinned status to be used in computing CSS style injections\n const { isPinned } = useContext(SidebarPinStateContext);\n\n const updateSidebarPosition = useCallback(() => {\n if (!dom || !sidebars) return;\n // set sidebar height so they don't initially render in wrong position\n const mdTabs = dom.querySelector('.md-container > .md-tabs');\n sidebars.forEach(sidebar => {\n const newTop = Math.max(dom.getBoundingClientRect().top, 0);\n sidebar.style.top = mdTabs\n ? `${newTop + mdTabs.getBoundingClientRect().height}px`\n : `${newTop}px`;\n });\n }, [dom, sidebars]);\n\n useEffect(() => {\n updateSidebarPosition();\n window.addEventListener('scroll', updateSidebarPosition, true);\n window.addEventListener('resize', updateSidebarPosition);\n return () => {\n window.removeEventListener('scroll', updateSidebarPosition, true);\n window.removeEventListener('resize', updateSidebarPosition);\n };\n // an update to \"state\" might lead to an updated UI so we include it as a trigger\n }, [updateSidebarPosition, state]);\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('.md-footer') as HTMLElement;\n if (footer) {\n footer.style.width = `${dom.getBoundingClientRect().width}px`;\n }\n }, [dom]);\n\n useEffect(() => {\n updateFooterWidth();\n window.addEventListener('resize', updateFooterWidth);\n return () => {\n window.removeEventListener('resize', updateFooterWidth);\n };\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 sanitizeDOM(techdocsSanitizer.getOptionalConfig('techdocs.sanitizer')),\n addBaseUrl({\n techdocsStorageApi,\n entityId: {\n kind,\n name,\n namespace,\n },\n path: contentPath,\n }),\n rewriteDocLinks(),\n removeMkdocsHeader(),\n simplifyMkdocsFooter(),\n addGitFeedbackLink(scmIntegrationsApi),\n injectCss({\n css: `\n body {\n font-family: ${theme.typography.fontFamily};\n --md-text-color: ${theme.palette.text.primary};\n --md-text-link-color: ${theme.palette.primary.main};\n\n --md-code-fg-color: ${theme.palette.text.primary};\n --md-code-bg-color: ${theme.palette.background.paper};\n }\n .md-main__inner { margin-top: 0; }\n .md-sidebar { position: fixed; bottom: 100px; width: 20rem; }\n .md-sidebar--secondary { right: 2rem; }\n .md-content { margin-bottom: 50px }\n .md-footer { position: fixed; bottom: 0px; }\n .md-footer-nav__link { width: 20rem;}\n .md-content { margin-left: 20rem; max-width: calc(100% - 20rem * 2 - 3rem); }\n .md-typeset { font-size: 1rem; }\n .md-typeset h1, .md-typeset h2, .md-typeset h3 { font-weight: bold; }\n .md-nav { font-size: 1rem; }\n .md-grid { max-width: 90vw; margin: 0 }\n .md-typeset table:not([class]) {\n font-size: 1rem;\n border: 1px solid ${theme.palette.text.primary};\n border-bottom: none;\n border-collapse: collapse;\n }\n .md-typeset table:not([class]) td, .md-typeset table:not([class]) th {\n border-bottom: 1px solid ${theme.palette.text.primary};\n }\n .md-typeset table:not([class]) th { font-weight: bold; }\n .md-typeset .admonition, .md-typeset details {\n font-size: 1rem;\n }\n \n /* style the checkmarks of the task list */\n .md-typeset .task-list-control .task-list-indicator::before {\n background-color: ${theme.palette.action.disabledBackground};\n }\n .md-typeset .task-list-control [type=\"checkbox\"]:checked + .task-list-indicator:before {\n background-color: ${theme.palette.success.main};\n }\n /**/\n\n @media screen and (max-width: 76.1875em) {\n .md-nav {\n background-color: ${theme.palette.background.default};\n transition: none !important\n }\n .md-sidebar--secondary { display: none; }\n .md-sidebar--primary { left: ${\n isPinned ? '242px' : '72px'\n }; width: 10rem }\n .md-content { margin-left: 10rem; max-width: calc(100% - 10rem); }\n .md-content__inner { font-size: 0.9rem }\n .md-footer {\n position: static;\n padding-left: 10rem;\n }\n .md-footer-nav__link {\n /* footer links begin to overlap at small sizes without setting width */\n width: 50%;\n }\n .md-nav--primary .md-nav__title {\n white-space: normal;\n height: auto;\n line-height: 1rem;\n cursor: auto;\n }\n .md-nav--primary > .md-nav__title [for=\"none\"] {\n padding-top: 0;\n }\n }\n `,\n }),\n injectCss({\n // Disable CSS animations on link colors as they lead to issues in dark\n // mode. The dark mode color theme is applied later and theirfore there\n // is always an animation from light to dark mode when navigation\n // between pages.\n css: `\n .md-nav__link, .md-typeset a, .md-typeset a::before, .md-typeset .headerlink {\n transition: none;\n }\n `,\n }),\n injectCss({\n // Properly style code blocks.\n css: `\n .md-typeset pre > code::-webkit-scrollbar-thumb {\n background-color: hsla(0, 0%, 0%, 0.32);\n }\n .md-typeset pre > code::-webkit-scrollbar-thumb:hover {\n background-color: hsla(0, 0%, 0%, 0.87);\n }\n `,\n }),\n injectCss({\n // Admonitions and others are using SVG masks to define icons. These\n // masks are defined as CSS variables.\n // As the MkDocs output is rendered in shadow DOM, the CSS variable\n // definitions on the root selector are not applied. Instead, the have\n // to be applied on :host.\n // As there is no way to transform the served main*.css yet (for\n // example in the backend), we have to copy from main*.css and modify\n // them.\n css: `\n :host {\n --md-admonition-icon--note: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z\"/></svg>');\n --md-admonition-icon--abstract: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M4 5h16v2H4V5m0 4h16v2H4V9m0 4h16v2H4v-2m0 4h10v2H4v-2z\"/></svg>');\n --md-admonition-icon--info: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 002 12a10 10 0 0010 10 10 10 0 0010-10A10 10 0 0012 2z\"/></svg>');\n --md-admonition-icon--tip: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M17.55 11.2c-.23-.3-.5-.56-.76-.82-.65-.6-1.4-1.03-2.03-1.66C13.3 7.26 13 4.85 13.91 3c-.91.23-1.75.75-2.45 1.32-2.54 2.08-3.54 5.75-2.34 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.22.1-.46.04-.64-.12a.83.83 0 01-.15-.17c-1.1-1.43-1.28-3.48-.53-5.12C5.89 10 5 12.3 5.14 14.47c.04.5.1 1 .27 1.5.14.6.4 1.2.72 1.73 1.04 1.73 2.87 2.97 4.84 3.22 2.1.27 4.35-.12 5.96-1.6 1.8-1.66 2.45-4.32 1.5-6.6l-.13-.26c-.2-.46-.47-.87-.8-1.25l.05-.01m-3.1 6.3c-.28.24-.73.5-1.08.6-1.1.4-2.2-.16-2.87-.82 1.19-.28 1.89-1.16 2.09-2.05.17-.8-.14-1.46-.27-2.23-.12-.74-.1-1.37.18-2.06.17.38.37.76.6 1.06.76 1 1.95 1.44 2.2 2.8.04.14.06.28.06.43.03.82-.32 1.72-.92 2.27h.01z\"/></svg>');\n --md-admonition-icon--success: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2m-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>');\n --md-admonition-icon--question: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M15.07 11.25l-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 00-2-2 2 2 0 00-2 2H8a4 4 0 014-4 4 4 0 014 4 3.2 3.2 0 01-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 002 12a10 10 0 0010 10 10 10 0 0010-10c0-5.53-4.5-10-10-10z\"/></svg>');\n --md-admonition-icon--warning: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M13 14h-2v-4h2m0 8h-2v-2h2M1 21h22L12 2 1 21z\"/></svg>');\n --md-admonition-icon--failure: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M12 2c5.53 0 10 4.47 10 10s-4.47 10-10 10S2 17.53 2 12 6.47 2 12 2m3.59 5L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41 15.59 7z\"/></svg>');\n --md-admonition-icon--danger: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M11.5 20l4.86-9.73H13V4l-5 9.73h3.5V20M12 2c2.75 0 5.1 1 7.05 2.95C21 6.9 22 9.25 22 12s-1 5.1-2.95 7.05C17.1 21 14.75 22 12 22s-5.1-1-7.05-2.95C3 17.1 2 14.75 2 12s1-5.1 2.95-7.05C6.9 3 9.25 2 12 2z\"/></svg>');\n --md-admonition-icon--bug: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 00-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 00-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z\"/></svg>');\n --md-admonition-icon--example: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 01.75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z\"/></svg>');\n --md-admonition-icon--quote: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z\"/></svg>');\n }\n :host {\n --md-footnotes-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.42L5.83 13H21V7h-2z\"/></svg>');\n }\n :host {\n --md-details-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M8.59 16.58L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42z\"/></svg>');\n }\n :host {\n --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z\"/></svg>');\n --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>');\n }\n `,\n }),\n ]),\n [\n kind,\n name,\n namespace,\n scmIntegrationsApi,\n techdocsStorageApi,\n techdocsSanitizer,\n theme.palette.action.disabledBackground,\n theme.palette.background.default,\n theme.palette.background.paper,\n theme.palette.primary.main,\n theme.palette.success.main,\n theme.palette.text.primary,\n theme.typography.fontFamily,\n isPinned,\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 scrollIntoAnchor(),\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 // hash exists when anchor is clicked on secondary sidebar\n if (parsedUrl.hash) {\n if (modifierActive) {\n window.open(`${parsedUrl.pathname}${parsedUrl.hash}`, '_blank');\n } else {\n navigate(`${parsedUrl.pathname}${parsedUrl.hash}`);\n // Scroll to hash if it's on the current page\n transformedElement\n ?.querySelector(`#${parsedUrl.hash.slice(1)}`)\n ?.scrollIntoView();\n }\n } else {\n if (modifierActive) {\n window.open(parsedUrl.pathname, '_blank');\n } else {\n navigate(parsedUrl.pathname);\n // Scroll to top of reader if primary sidebar link is clicked\n transformedElement\n ?.querySelector('.md-content__inner')\n ?.scrollIntoView();\n }\n }\n },\n }),\n onCssReady({\n docStorageUrl: await techdocsStorageApi.getApiOrigin(),\n onLoading: (renderedElement: Element) => {\n (renderedElement as HTMLElement).style.setProperty('opacity', '0');\n },\n onLoaded: (renderedElement: Element) => {\n (renderedElement as HTMLElement).style.removeProperty('opacity');\n // disable MkDocs drawer toggling ('for' attribute => checkbox mechanism)\n renderedElement\n .querySelector('.md-nav__title')\n ?.removeAttribute('for');\n setSidebars(\n Array.from(renderedElement.querySelectorAll('.md-sidebar')),\n );\n },\n }),\n ]),\n [navigate, techdocsStorageApi],\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\nconst TheReader = ({\n entityRef,\n onReady = () => {},\n withSearch = true,\n}: Props) => {\n const classes = useStyles();\n const dom = useTechDocsReaderDom(entityRef);\n const shadowDomRef = useRef<HTMLDivElement>(null);\n\n const onReadyRef = useRef<() => void>(onReady);\n useEffect(() => {\n onReadyRef.current = onReady;\n }, [onReady]);\n\n useEffect(() => {\n if (!dom || !shadowDomRef.current) return;\n const shadowDiv = shadowDomRef.current;\n const shadowRoot =\n shadowDiv.shadowRoot || shadowDiv.attachShadow({ mode: 'open' });\n Array.from(shadowRoot.children).forEach(child =>\n shadowRoot.removeChild(child),\n );\n shadowRoot.appendChild(dom);\n onReadyRef.current();\n\n // this hook must ONLY be triggered by a changed dom\n }, [dom]);\n\n return (\n <>\n <TechDocsStateIndicator />\n {withSearch && shadowDomRef?.current?.shadowRoot?.innerHTML && (\n <Grid container className={classes.searchBar}>\n <TechDocsSearch entityId={entityRef} />\n </Grid>\n )}\n <div data-testid=\"techdocs-content-shadowroot\" ref={shadowDomRef} />\n </>\n );\n};\n\nexport const Reader = ({\n entityRef,\n onReady = () => {},\n withSearch = true,\n}: Props) => (\n <TechDocsReaderProvider entityRef={entityRef}>\n <TheReader\n entityRef={entityRef}\n onReady={onReady}\n withSearch={withSearch}\n />\n </TechDocsReaderProvider>\n);\n","/*\n * Copyright 2020 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 { EntityName, RELATION_OWNED_BY } from '@backstage/catalog-model';\nimport { Header, HeaderLabel } from '@backstage/core-components';\nimport { useRouteRef } from '@backstage/core-plugin-api';\nimport {\n EntityRefLink,\n EntityRefLinks,\n getEntityRelations,\n} from '@backstage/plugin-catalog-react';\nimport CodeIcon from '@material-ui/icons/Code';\nimport React from 'react';\nimport { rootRouteRef } from '../../routes';\nimport { TechDocsEntityMetadata, TechDocsMetadata } from '../../types';\n\nexport type TechDocsPageHeaderProps = {\n entityRef: EntityName;\n entityMetadata?: TechDocsEntityMetadata;\n techDocsMetadata?: TechDocsMetadata;\n};\n\nexport const TechDocsPageHeader = ({\n entityRef,\n entityMetadata,\n techDocsMetadata,\n}: TechDocsPageHeaderProps) => {\n const { name } = entityRef;\n\n const { site_name: siteName, site_description: siteDescription } =\n techDocsMetadata || {};\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=\"Component\"\n value={\n <EntityRefLink\n color=\"inherit\"\n entityRef={entityRef}\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 ? <HeaderLabel label=\"Lifecycle\" value={lifecycle} /> : null}\n {locationMetadata &&\n locationMetadata.type !== 'dir' &&\n locationMetadata.type !== 'file' ? (\n <HeaderLabel\n label=\"\"\n value={\n <a\n href={locationMetadata.target}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <CodeIcon style={{ marginTop: '-25px', fill: '#fff' }} />\n </a>\n }\n />\n ) : null}\n </>\n );\n\n return (\n <Header\n title={siteName ? siteName : '.'}\n pageTitleOverride={siteName || name}\n subtitle={\n siteDescription && siteDescription !== 'None' ? siteDescription : ''\n }\n type=\"Docs\"\n typeLink={docsRootLink}\n >\n {labels}\n </Header>\n );\n};\n","/*\n * Copyright 2020 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, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport useAsync from 'react-use/lib/useAsync';\nimport { techdocsApiRef } from '../../api';\nimport { TechDocsNotFound } from './TechDocsNotFound';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { Page, Content } from '@backstage/core-components';\nimport { Reader } from './Reader';\nimport { TechDocsPageHeader } from './TechDocsPageHeader';\n\nexport const LegacyTechDocsPage = () => {\n const [documentReady, setDocumentReady] = useState<boolean>(false);\n const { namespace, kind, name } = useParams();\n\n const techdocsApi = useApi(techdocsApiRef);\n\n const { value: techdocsMetadataValue } = useAsync(() => {\n if (documentReady) {\n return techdocsApi.getTechDocsMetadata({ kind, namespace, name });\n }\n\n return Promise.resolve(undefined);\n }, [kind, namespace, name, techdocsApi, documentReady]);\n\n const { value: entityMetadataValue, error: entityMetadataError } =\n useAsync(() => {\n return techdocsApi.getEntityMetadata({ kind, namespace, name });\n }, [kind, namespace, name, techdocsApi]);\n\n const onReady = useCallback(() => {\n setDocumentReady(true);\n }, [setDocumentReady]);\n\n if (entityMetadataError) {\n return <TechDocsNotFound errorMessage={entityMetadataError.message} />;\n }\n\n return (\n <Page themeId=\"documentation\">\n <TechDocsPageHeader\n techDocsMetadata={techdocsMetadataValue}\n entityMetadata={entityMetadataValue}\n entityRef={{\n kind,\n namespace,\n name,\n }}\n />\n <Content data-testid=\"techdocs-content\">\n <Reader\n onReady={onReady}\n entityRef={{\n kind,\n namespace,\n name,\n }}\n />\n </Content>\n </Page>\n );\n};\n","/*\n * Copyright 2020 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, useState } from 'react';\nimport { useOutlet } from 'react-router';\nimport { useParams } from 'react-router-dom';\nimport useAsync from 'react-use/lib/useAsync';\nimport { techdocsApiRef } from '../../api';\nimport { TechDocsNotFound } from './TechDocsNotFound';\nimport { LegacyTechDocsPage } from './LegacyTechDocsPage';\nimport { TechDocsEntityMetadata, TechDocsMetadata } from '../../types';\nimport { EntityName } from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { Page } from '@backstage/core-components';\n\nexport type TechDocsPageRenderFunction = ({\n techdocsMetadataValue,\n entityMetadataValue,\n entityRef,\n}: {\n techdocsMetadataValue?: TechDocsMetadata | undefined;\n entityMetadataValue?: TechDocsEntityMetadata | undefined;\n entityRef: EntityName;\n onReady: () => void;\n}) => JSX.Element;\n\nexport type TechDocsPageProps = {\n children?: TechDocsPageRenderFunction | React.ReactNode;\n};\n\nexport const TechDocsPage = ({ children }: TechDocsPageProps) => {\n const outlet = useOutlet();\n\n const [documentReady, setDocumentReady] = useState<boolean>(false);\n const { namespace, kind, name } = useParams();\n\n const techdocsApi = useApi(techdocsApiRef);\n\n const { value: techdocsMetadataValue } = useAsync(() => {\n if (documentReady) {\n return techdocsApi.getTechDocsMetadata({ kind, namespace, name });\n }\n\n return Promise.resolve(undefined);\n }, [kind, namespace, name, techdocsApi, documentReady]);\n\n const { value: entityMetadataValue, error: entityMetadataError } =\n useAsync(() => {\n return techdocsApi.getEntityMetadata({ kind, namespace, name });\n }, [kind, namespace, name, techdocsApi]);\n\n const onReady = useCallback(() => {\n setDocumentReady(true);\n }, [setDocumentReady]);\n\n if (entityMetadataError) {\n return <TechDocsNotFound errorMessage={entityMetadataError.message} />;\n }\n\n if (!children) return outlet || <LegacyTechDocsPage />;\n\n return (\n <Page themeId=\"documentation\">\n {children instanceof Function\n ? children({\n techdocsMetadataValue,\n entityMetadataValue,\n entityRef: { kind, namespace, name },\n onReady,\n })\n : children}\n </Page>\n );\n};\n","/*\n * Copyright 2021 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, { useState } from 'react';\nimport useAsync from 'react-use/lib/useAsync';\nimport { makeStyles } from '@material-ui/core';\nimport { CSSProperties } from '@material-ui/styles';\nimport {\n CATALOG_FILTER_EXISTS,\n catalogApiRef,\n CatalogApi,\n isOwnerOf,\n useOwnUser,\n} from '@backstage/plugin-catalog-react';\nimport { Entity } from '@backstage/catalog-model';\nimport { DocsTable } from './DocsTable';\nimport { DocsCardGrid } from './DocsCardGrid';\nimport { TechDocsPageWrapper } from './TechDocsPageWrapper';\n\nimport {\n CodeSnippet,\n Content,\n HeaderTabs,\n Progress,\n WarningPanel,\n SupportButton,\n ContentHeader,\n} from '@backstage/core-components';\n\nimport { useApi } from '@backstage/core-plugin-api';\n\nconst panels = {\n DocsTable: DocsTable,\n DocsCardGrid: DocsCardGrid,\n};\n\nexport type PanelType = 'DocsCardGrid' | 'DocsTable';\n\nexport interface PanelConfig {\n title: string;\n description: string;\n panelType: PanelType;\n panelCSS?: CSSProperties;\n filterPredicate: ((entity: Entity) => boolean) | string;\n}\n\nexport interface TabConfig {\n label: string;\n panels: PanelConfig[];\n}\n\nexport type TabsConfig = TabConfig[];\n\nconst CustomPanel = ({\n config,\n entities,\n index,\n}: {\n config: PanelConfig;\n entities: Entity[];\n index: number;\n}) => {\n const useStyles = makeStyles({\n panelContainer: {\n marginBottom: '2rem',\n ...(config.panelCSS ? config.panelCSS : {}),\n },\n });\n const classes = useStyles();\n const { value: user } = useOwnUser();\n\n const Panel = panels[config.panelType];\n\n const shownEntities = entities.filter(entity => {\n if (config.filterPredicate === 'ownedByUser') {\n if (!user) {\n return false;\n }\n return isOwnerOf(user, entity);\n }\n\n return (\n typeof config.filterPredicate === 'function' &&\n config.filterPredicate(entity)\n );\n });\n\n return (\n <>\n <ContentHeader title={config.title} description={config.description}>\n {index === 0 ? (\n <SupportButton>\n Discover documentation in your ecosystem.\n </SupportButton>\n ) : null}\n </ContentHeader>\n <div className={classes.panelContainer}>\n <Panel data-testid=\"techdocs-custom-panel\" entities={shownEntities} />\n </div>\n </>\n );\n};\n\nexport const TechDocsCustomHome = ({\n tabsConfig,\n}: {\n tabsConfig: TabsConfig;\n}) => {\n const [selectedTab, setSelectedTab] = useState<number>(0);\n const catalogApi: CatalogApi = useApi(catalogApiRef);\n\n const {\n value: entities,\n loading,\n error,\n } = useAsync(async () => {\n const response = await catalogApi.getEntities({\n filter: {\n 'metadata.annotations.backstage.io/techdocs-ref': CATALOG_FILTER_EXISTS,\n },\n fields: [\n 'apiVersion',\n 'kind',\n 'metadata',\n 'relations',\n 'spec.owner',\n 'spec.type',\n ],\n });\n return response.items.filter((entity: Entity) => {\n return !!entity.metadata.annotations?.['backstage.io/techdocs-ref'];\n });\n });\n\n const currentTabConfig = tabsConfig[selectedTab];\n\n if (loading) {\n return (\n <TechDocsPageWrapper>\n <Content>\n <Progress />\n </Content>\n </TechDocsPageWrapper>\n );\n }\n\n if (error) {\n return (\n <TechDocsPageWrapper>\n <Content>\n <WarningPanel\n severity=\"error\"\n title=\"Could not load available documentation.\"\n >\n <CodeSnippet language=\"text\" text={error.toString()} />\n </WarningPanel>\n </Content>\n </TechDocsPageWrapper>\n );\n }\n\n return (\n <TechDocsPageWrapper>\n <HeaderTabs\n selectedIndex={selectedTab}\n onChange={index => setSelectedTab(index)}\n tabs={tabsConfig.map(({ label }, index) => ({\n id: index.toString(),\n label,\n }))}\n />\n <Content data-testid=\"techdocs-content\">\n {currentTabConfig.panels.map((config, index) => (\n <CustomPanel\n key={index}\n config={config}\n entities={!!entities ? entities : []}\n index={index}\n />\n ))}\n </Content>\n </TechDocsPageWrapper>\n );\n};\n","/*\n * Copyright 2021 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 from 'react';\nimport { PanelType, TechDocsCustomHome } from './TechDocsCustomHome';\n\nexport const LegacyTechDocsHome = () => {\n const tabsConfig = [\n {\n label: 'Overview',\n panels: [\n {\n title: 'Overview',\n description:\n 'Explore your internal technical ecosystem through documentation.',\n panelType: 'DocsCardGrid' as PanelType,\n filterPredicate: () => true,\n },\n // uncomment this if you would like to have a secondary panel with owned documents\n // {\n // title: 'Owned',\n // description: 'Explore your owned internal documentation.',\n // panelType: 'DocsCardGrid' as PanelType,\n // filterPredicate: 'ownedByUser',\n // },\n ],\n },\n {\n label: 'Owned Documents',\n panels: [\n {\n title: 'Owned documents',\n description: 'Access your documentation.',\n panelType: 'DocsTable' as PanelType,\n // ownedByUser filters out entities owned by signed in user\n filterPredicate: 'ownedByUser',\n },\n ],\n },\n ];\n return <TechDocsCustomHome tabsConfig={tabsConfig} />;\n};\n","/*\n * Copyright 2021 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 from 'react';\nimport { useOutlet } from 'react-router';\nimport { LegacyTechDocsHome } from './LegacyTechDocsHome';\n\nexport const TechDocsIndexPage = () => {\n const outlet = useOutlet();\n\n return outlet || <LegacyTechDocsHome />;\n};\n","/*\n * Copyright 2020 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 from 'react';\nimport { Entity } from '@backstage/catalog-model';\nimport { Reader } from './reader';\nimport { toLowerMaybe } from './helpers';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\n\nexport const EntityPageDocs = ({ entity }: { entity: Entity }) => {\n const config = useApi(configApiRef);\n return (\n <Reader\n withSearch={false}\n entityRef={{\n namespace: toLowerMaybe(entity.metadata.namespace ?? 'default', config),\n kind: toLowerMaybe(entity.kind, config),\n name: toLowerMaybe(entity.metadata.name, config),\n }}\n />\n );\n};\n","/*\n * Copyright 2020 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 from 'react';\nimport { Entity } from '@backstage/catalog-model';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { Route, Routes } from 'react-router-dom';\nimport { TechDocsIndexPage } from './home/components/TechDocsIndexPage';\nimport { TechDocsPage as TechDocsReaderPage } from './reader/components/TechDocsPage';\nimport { EntityPageDocs } from './EntityPageDocs';\nimport { MissingAnnotationEmptyState } from '@backstage/core-components';\n\nconst TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref';\n\nexport const isTechDocsAvailable = (entity: Entity) =>\n Boolean(entity?.metadata?.annotations?.[TECHDOCS_ANNOTATION]);\n\nexport const Router = () => {\n return (\n <Routes>\n <Route path=\"/\" element={<TechDocsIndexPage />} />\n <Route\n path=\"/:namespace/:kind/:name/*\"\n element={<TechDocsReaderPage />}\n />\n </Routes>\n );\n};\n\ntype Props = {\n /** @deprecated The entity is now grabbed from context instead */\n entity?: Entity;\n};\n\nexport const EmbeddedDocsRouter = (_props: Props) => {\n const { entity } = useEntity();\n\n const projectId = entity.metadata.annotations?.[TECHDOCS_ANNOTATION];\n\n if (!projectId) {\n return <MissingAnnotationEmptyState annotation={TECHDOCS_ANNOTATION} />;\n }\n\n return (\n <Routes>\n <Route path=\"/*\" element={<EntityPageDocs entity={entity} />} />\n </Routes>\n );\n};\n"],"names":["useStyles","DocsTable","columnFactories.createNameColumn","columnFactories.createOwnerColumn","columnFactories.createTypeColumn","actionFactories.createCopyDocsUrlAction","actionFactories.createStarEntityAction","DocsCardGrid","TechDocsCustomHome","TechDocsIndexPage","Button","useNavigate","transformer","TechDocsReaderPage"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-techdocs",
|
|
3
3
|
"description": "The Backstage plugin that renders technical documentation for your components",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.14",
|
|
5
5
|
"main": "dist/index.esm.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -32,16 +32,16 @@
|
|
|
32
32
|
"clean": "backstage-cli clean"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@backstage/catalog-model": "^0.9.
|
|
36
|
-
"@backstage/config": "^0.1.
|
|
37
|
-
"@backstage/core-components": "^0.8.
|
|
38
|
-
"@backstage/core-plugin-api": "^0.
|
|
39
|
-
"@backstage/errors": "^0.
|
|
40
|
-
"@backstage/integration": "^0.
|
|
41
|
-
"@backstage/integration-react": "^0.1.
|
|
42
|
-
"@backstage/plugin-catalog": "^0.7.
|
|
43
|
-
"@backstage/plugin-catalog-react": "^0.6.
|
|
44
|
-
"@backstage/plugin-search": "^0.5.
|
|
35
|
+
"@backstage/catalog-model": "^0.9.9",
|
|
36
|
+
"@backstage/config": "^0.1.12",
|
|
37
|
+
"@backstage/core-components": "^0.8.4",
|
|
38
|
+
"@backstage/core-plugin-api": "^0.5.0",
|
|
39
|
+
"@backstage/errors": "^0.2.0",
|
|
40
|
+
"@backstage/integration": "^0.7.1",
|
|
41
|
+
"@backstage/integration-react": "^0.1.18",
|
|
42
|
+
"@backstage/plugin-catalog": "^0.7.8",
|
|
43
|
+
"@backstage/plugin-catalog-react": "^0.6.11",
|
|
44
|
+
"@backstage/plugin-search": "^0.5.5",
|
|
45
45
|
"@backstage/theme": "^0.2.14",
|
|
46
46
|
"@material-ui/core": "^4.12.2",
|
|
47
47
|
"@material-ui/icons": "^4.9.1",
|
|
@@ -62,10 +62,10 @@
|
|
|
62
62
|
"react-dom": "^16.13.1 || ^17.0.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@backstage/cli": "^0.
|
|
66
|
-
"@backstage/core-app-api": "^0.
|
|
67
|
-
"@backstage/dev-utils": "^0.2.
|
|
68
|
-
"@backstage/test-utils": "^0.
|
|
65
|
+
"@backstage/cli": "^0.11.0",
|
|
66
|
+
"@backstage/core-app-api": "^0.4.0",
|
|
67
|
+
"@backstage/dev-utils": "^0.2.17",
|
|
68
|
+
"@backstage/test-utils": "^0.2.2",
|
|
69
69
|
"@testing-library/jest-dom": "^5.10.1",
|
|
70
70
|
"@testing-library/react": "^11.2.5",
|
|
71
71
|
"@testing-library/react-hooks": "^7.0.2",
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
"config.d.ts"
|
|
83
83
|
],
|
|
84
84
|
"configSchema": "config.d.ts",
|
|
85
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "da66c61bdd63cdb3f0f0cd2e26dc9e6454d93c7b"
|
|
86
86
|
}
|