@backstage/plugin-techdocs 1.7.1-next.2 → 1.8.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,19 @@
1
- import { createApiRef, useApi, configApiRef, useAnalytics, createRouteRef, useRouteRef, useRouteRefParams, getComponentData, createPlugin, createApiFactory, discoveryApiRef, identityApiRef, fetchApiRef, createRoutableExtension } from '@backstage/core-plugin-api';
2
- import { ResponseError, NotFoundError } from '@backstage/errors';
3
- import { EventSourcePolyfill } from 'event-source-polyfill';
4
1
  import React, { useReducer, useRef, useMemo, createContext, useContext, useState, useEffect, useCallback, Children } from 'react';
5
- import { useParams, useNavigate, useOutlet, Routes, Route, useRoutes } from 'react-router-dom';
6
- import { techdocsStorageApiRef as techdocsStorageApiRef$1, useTechDocsReaderPage, SHADOW_DOM_STYLE_LOAD_EVENT, useShadowDomStylesLoading, useTechDocsAddons, TechDocsAddonLocations, TechDocsShadowDom, TECHDOCS_ADDONS_WRAPPER_KEY, TECHDOCS_ADDONS_KEY, TechDocsReaderPageProvider, techdocsApiRef as techdocsApiRef$1 } from '@backstage/plugin-techdocs-react';
7
- import useAsync from 'react-use/lib/useAsync';
8
- import useAsyncRetry from 'react-use/lib/useAsyncRetry';
9
- import { LogViewer, ErrorPage, useSidebarPinState, Content, HeaderLabel, Header, Page, ItemCardGrid, ItemCardHeader, LinkButton, WarningPanel, CodeSnippet, Progress, Link, ContentHeader, SubvalueCell, Table, EmptyState, PageWithHeader, SupportButton, MissingAnnotationEmptyState } from '@backstage/core-components';
10
- import { makeStyles, createStyles, Button, Drawer, Grid, Typography, IconButton, CircularProgress, lighten, alpha, useTheme, withStyles, Tooltip, ThemeProvider, SvgIcon, useMediaQuery, Portal, Toolbar, Box, Menu, Card, CardMedia, CardContent, CardActions } from '@material-ui/core';
11
- import { SearchContextProvider, useSearch, SearchAutocomplete, createSearchResultListItemExtension } from '@backstage/plugin-search-react';
12
- import { TechDocsSearchResultListItem as TechDocsSearchResultListItem$1 } from './TechDocsSearchResultListItem-4736f829.esm.js';
2
+ import { useParams, useNavigate, useOutlet } from 'react-router-dom';
3
+ import { LogViewer, ErrorPage, useSidebarPinState, Content, HeaderLabel, Header, Page } from '@backstage/core-components';
4
+ import { techdocsStorageApiRef, useTechDocsReaderPage, SHADOW_DOM_STYLE_LOAD_EVENT, useShadowDomStylesLoading, useTechDocsAddons, TechDocsAddonLocations, useShadowRootElements, TechDocsShadowDom, TECHDOCS_ADDONS_WRAPPER_KEY, TECHDOCS_ADDONS_KEY, TechDocsReaderPageProvider } from '@backstage/plugin-techdocs-react';
5
+ import { makeStyles, createStyles, Button, Drawer, Grid, Typography, IconButton, CircularProgress, lighten, alpha, useTheme, withStyles, Tooltip, ThemeProvider, SvgIcon, useMediaQuery, Portal, Toolbar, Box, Menu } from '@material-ui/core';
6
+ import { SearchContextProvider, useSearch, SearchAutocomplete } from '@backstage/plugin-search-react';
7
+ import { TechDocsSearchResultListItem } from './TechDocsSearchResultListItem-4736f829.esm.js';
13
8
  import { Alert, Skeleton } from '@material-ui/lab';
14
9
  import Close from '@material-ui/icons/Close';
10
+ import { useApi, configApiRef, useAnalytics, useRouteRef, useRouteRefParams, getComponentData } from '@backstage/core-plugin-api';
11
+ import useAsync from 'react-use/lib/useAsync';
12
+ import useAsyncRetry from 'react-use/lib/useAsyncRetry';
15
13
  import { scmIntegrationsApiRef } from '@backstage/integration-react';
16
14
  import DOMPurify from 'dompurify';
17
15
  import { replaceGithubUrlType } from '@backstage/integration';
18
16
  import FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined';
19
- import ReactDOM from 'react-dom';
20
17
  import parseGitUrl from 'git-url-parse';
21
18
  import MenuIcon from '@material-ui/icons/Menu';
22
19
  import IconButton$1 from '@material-ui/core/IconButton';
@@ -24,182 +21,11 @@ import useCopyToClipboard from 'react-use/lib/useCopyToClipboard';
24
21
  import Helmet from 'react-helmet';
25
22
  import { useTheme as useTheme$1 } from '@material-ui/core/styles';
26
23
  import CodeIcon from '@material-ui/icons/Code';
27
- import { getEntityRelations, EntityRefLink, EntityRefLinks, useEntityList, useEntityOwnership, humanizeEntityRef, useStarredEntities, CATALOG_FILTER_EXISTS, EntityListProvider, CatalogFilterLayout, UserListPicker, EntityOwnerPicker, EntityTagPicker, useEntity } from '@backstage/plugin-catalog-react';
28
- import { RELATION_OWNED_BY, getCompoundEntityRef, parseEntityRef } from '@backstage/catalog-model';
24
+ import { getEntityRelations, EntityRefLink, EntityRefLinks } from '@backstage/plugin-catalog-react';
25
+ import { RELATION_OWNED_BY } from '@backstage/catalog-model';
29
26
  import { capitalize } from 'lodash';
27
+ import { r as rootRouteRef, b as rootDocsRouteRef } from './routes-691e552c.esm.js';
30
28
  import SettingsIcon from '@material-ui/icons/Settings';
31
- import ShareIcon from '@material-ui/icons/Share';
32
- import { withStyles as withStyles$1 } from '@material-ui/styles';
33
- import Star from '@material-ui/icons/Star';
34
- import StarBorder from '@material-ui/icons/StarBorder';
35
-
36
- const techdocsStorageApiRef = createApiRef({
37
- id: "plugin.techdocs.storageservice"
38
- });
39
- const techdocsApiRef = createApiRef({
40
- id: "plugin.techdocs.service"
41
- });
42
-
43
- var __defProp = Object.defineProperty;
44
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
45
- var __publicField = (obj, key, value) => {
46
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
47
- return value;
48
- };
49
- class TechDocsClient {
50
- constructor(options) {
51
- __publicField(this, "configApi");
52
- __publicField(this, "discoveryApi");
53
- __publicField(this, "fetchApi");
54
- this.configApi = options.configApi;
55
- this.discoveryApi = options.discoveryApi;
56
- this.fetchApi = options.fetchApi;
57
- }
58
- async getApiOrigin() {
59
- return await this.discoveryApi.getBaseUrl("techdocs");
60
- }
61
- /**
62
- * Retrieve TechDocs metadata.
63
- *
64
- * When docs are built, we generate a techdocs_metadata.json and store it along with the generated
65
- * static files. It includes necessary data about the docs site. This method requests techdocs-backend
66
- * which retrieves the TechDocs metadata.
67
- *
68
- * @param entityId - Object containing entity data like name, namespace, etc.
69
- */
70
- async getTechDocsMetadata(entityId) {
71
- const { kind, namespace, name } = entityId;
72
- const apiOrigin = await this.getApiOrigin();
73
- const requestUrl = `${apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`;
74
- const request = await this.fetchApi.fetch(`${requestUrl}`);
75
- if (!request.ok) {
76
- throw await ResponseError.fromResponse(request);
77
- }
78
- return await request.json();
79
- }
80
- /**
81
- * Retrieve metadata about an entity.
82
- *
83
- * This method requests techdocs-backend which uses the catalog APIs to respond with filtered
84
- * information required here.
85
- *
86
- * @param entityId - Object containing entity data like name, namespace, etc.
87
- */
88
- async getEntityMetadata(entityId) {
89
- const { kind, namespace, name } = entityId;
90
- const apiOrigin = await this.getApiOrigin();
91
- const requestUrl = `${apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`;
92
- const request = await this.fetchApi.fetch(`${requestUrl}`);
93
- if (!request.ok) {
94
- throw await ResponseError.fromResponse(request);
95
- }
96
- return await request.json();
97
- }
98
- }
99
- class TechDocsStorageClient {
100
- constructor(options) {
101
- __publicField(this, "configApi");
102
- __publicField(this, "discoveryApi");
103
- __publicField(this, "identityApi");
104
- __publicField(this, "fetchApi");
105
- this.configApi = options.configApi;
106
- this.discoveryApi = options.discoveryApi;
107
- this.identityApi = options.identityApi;
108
- this.fetchApi = options.fetchApi;
109
- }
110
- async getApiOrigin() {
111
- return await this.discoveryApi.getBaseUrl("techdocs");
112
- }
113
- async getStorageUrl() {
114
- var _a;
115
- return (_a = this.configApi.getOptionalString("techdocs.storageUrl")) != null ? _a : `${await this.discoveryApi.getBaseUrl("techdocs")}/static/docs`;
116
- }
117
- async getBuilder() {
118
- return this.configApi.getString("techdocs.builder");
119
- }
120
- /**
121
- * Fetch HTML content as text for an individual docs page in an entity's docs site.
122
- *
123
- * @param entityId - Object containing entity data like name, namespace, etc.
124
- * @param path - The unique path to an individual docs page e.g. overview/what-is-new
125
- * @returns HTML content of the docs page as string
126
- * @throws Throws error when the page is not found.
127
- */
128
- async getEntityDocs(entityId, path) {
129
- const { kind, namespace, name } = entityId;
130
- const storageUrl = await this.getStorageUrl();
131
- const url = `${storageUrl}/${namespace}/${kind}/${name}/${path}`;
132
- const request = await this.fetchApi.fetch(
133
- `${url.endsWith("/") ? url : `${url}/`}index.html`
134
- );
135
- let errorMessage = "";
136
- switch (request.status) {
137
- case 404:
138
- errorMessage = "Page not found. ";
139
- if (!path) {
140
- errorMessage += "This could be because there is no index.md file in the root of the docs directory of this repository.";
141
- }
142
- throw new NotFoundError(errorMessage);
143
- case 500:
144
- errorMessage = "Could not generate documentation or an error in the TechDocs backend. ";
145
- throw new Error(errorMessage);
146
- }
147
- return request.text();
148
- }
149
- /**
150
- * Check if docs are on the latest version and trigger rebuild if not
151
- *
152
- * @param entityId - Object containing entity data like name, namespace, etc.
153
- * @param logHandler - Callback to receive log messages from the build process
154
- * @returns Whether documents are currently synchronized to newest version
155
- * @throws Throws error on error from sync endpoint in TechDocs Backend
156
- */
157
- async syncEntityDocs(entityId, logHandler = () => {
158
- }) {
159
- const { kind, namespace, name } = entityId;
160
- const apiOrigin = await this.getApiOrigin();
161
- const url = `${apiOrigin}/sync/${namespace}/${kind}/${name}`;
162
- const { token } = await this.identityApi.getCredentials();
163
- return new Promise((resolve, reject) => {
164
- const source = new EventSourcePolyfill(url, {
165
- withCredentials: true,
166
- headers: token ? { Authorization: `Bearer ${token}` } : {}
167
- });
168
- source.addEventListener("log", (e) => {
169
- if (e.data) {
170
- logHandler(JSON.parse(e.data));
171
- }
172
- });
173
- source.addEventListener("finish", (e) => {
174
- let updated = false;
175
- if (e.data) {
176
- ({ updated } = JSON.parse(e.data));
177
- }
178
- resolve(updated ? "updated" : "cached");
179
- });
180
- source.onerror = (e) => {
181
- source.close();
182
- switch (e.status) {
183
- case 404:
184
- reject(new NotFoundError(e.message));
185
- return;
186
- default:
187
- reject(new Error(e.data));
188
- return;
189
- }
190
- };
191
- });
192
- }
193
- async getBaseUrl(oldBaseUrl, entityId, path) {
194
- const { kind, namespace, name } = entityId;
195
- const apiOrigin = await this.getApiOrigin();
196
- const newBaseUrl = `${apiOrigin}/static/docs/${namespace}/${kind}/${name}/${path}`;
197
- return new URL(
198
- oldBaseUrl,
199
- newBaseUrl.endsWith("/") ? newBaseUrl : `${newBaseUrl}/`
200
- ).toString();
201
- }
202
- }
203
29
 
204
30
  function calculateDisplayState({
205
31
  contentLoading,
@@ -274,7 +100,7 @@ function useReaderState(kind, namespace, name, path) {
274
100
  contentLoading: true,
275
101
  buildLog: []
276
102
  });
277
- const techdocsStorageApi = useApi(techdocsStorageApiRef$1);
103
+ const techdocsStorageApi = useApi(techdocsStorageApiRef);
278
104
  const { retry: contentReload } = useAsyncRetry(async () => {
279
105
  dispatch({ type: "contentLoading" });
280
106
  try {
@@ -429,7 +255,7 @@ const TechDocsSearchBar = (props) => {
429
255
  value: null,
430
256
  options,
431
257
  renderOption: ({ document, highlight }) => /* @__PURE__ */ React.createElement(
432
- TechDocsSearchResultListItem$1,
258
+ TechDocsSearchResultListItem,
433
259
  {
434
260
  result: document,
435
261
  lineClamp: 3,
@@ -1312,6 +1138,22 @@ const addBaseUrl = ({
1312
1138
  };
1313
1139
  };
1314
1140
 
1141
+ let ReactDOMPromise;
1142
+ if (process.env.HAS_REACT_DOM_CLIENT) {
1143
+ ReactDOMPromise = import('react-dom/client');
1144
+ } else {
1145
+ ReactDOMPromise = import('react-dom');
1146
+ }
1147
+ function renderReactElement(element, root) {
1148
+ ReactDOMPromise.then((ReactDOM) => {
1149
+ if ("createRoot" in ReactDOM) {
1150
+ ReactDOM.createRoot(root).render(element);
1151
+ } else {
1152
+ ReactDOM.render(element, root);
1153
+ }
1154
+ });
1155
+ }
1156
+
1315
1157
  const addGitFeedbackLink = (scmIntegrationsApi) => {
1316
1158
  return (dom) => {
1317
1159
  var _a;
@@ -1348,7 +1190,7 @@ Feedback:`
1348
1190
  default:
1349
1191
  return dom;
1350
1192
  }
1351
- ReactDOM.render(React.createElement(FeedbackOutlinedIcon), feedbackLink);
1193
+ renderReactElement(React.createElement(FeedbackOutlinedIcon), feedbackLink);
1352
1194
  feedbackLink.style.paddingLeft = "5px";
1353
1195
  feedbackLink.title = "Leave feedback for this page";
1354
1196
  feedbackLink.id = "git-feedback-link";
@@ -1367,7 +1209,7 @@ const addSidebarToggle = () => {
1367
1209
  return dom;
1368
1210
  }
1369
1211
  const toggleSidebar = mkdocsToggleSidebar.cloneNode();
1370
- ReactDOM.render(React.createElement(MenuIcon), toggleSidebar);
1212
+ renderReactElement(React.createElement(MenuIcon), toggleSidebar);
1371
1213
  toggleSidebar.id = "toggle-sidebar";
1372
1214
  toggleSidebar.title = "Toggle Sidebar";
1373
1215
  toggleSidebar.classList.add("md-content__button");
@@ -1483,7 +1325,7 @@ const copyToClipboard = (theme) => {
1483
1325
  const text = code.textContent || "";
1484
1326
  const container = document.createElement("div");
1485
1327
  (_a = code == null ? void 0 : code.parentElement) == null ? void 0 : _a.prepend(container);
1486
- ReactDOM.render(
1328
+ renderReactElement(
1487
1329
  /* @__PURE__ */ React.createElement(ThemeProvider, { theme }, /* @__PURE__ */ React.createElement(CopyToClipboardButton, { text })),
1488
1330
  container
1489
1331
  );
@@ -1529,26 +1371,6 @@ const onCssReady = ({
1529
1371
  };
1530
1372
  };
1531
1373
 
1532
- const scrollIntoAnchor = () => {
1533
- return (dom) => {
1534
- dom.addEventListener(
1535
- SHADOW_DOM_STYLE_LOAD_EVENT,
1536
- function handleShadowDomStyleLoad() {
1537
- var _a;
1538
- if (window.location.hash) {
1539
- const hash = window.location.hash.slice(1);
1540
- (_a = dom == null ? void 0 : dom.querySelector(`[id="${hash}"]`)) == null ? void 0 : _a.scrollIntoView();
1541
- }
1542
- dom.removeEventListener(
1543
- SHADOW_DOM_STYLE_LOAD_EVENT,
1544
- handleShadowDomStyleLoad
1545
- );
1546
- }
1547
- );
1548
- return dom;
1549
- };
1550
- };
1551
-
1552
1374
  const scrollIntoNavigation = () => {
1553
1375
  return (dom) => {
1554
1376
  setTimeout(() => {
@@ -1621,7 +1443,7 @@ const useTechDocsReaderDom = (entityRef) => {
1621
1443
  const sanitizerTransformer = useSanitizerTransformer();
1622
1444
  const stylesTransformer = useStylesTransformer();
1623
1445
  const analytics = useAnalytics();
1624
- const techdocsStorageApi = useApi(techdocsStorageApiRef$1);
1446
+ const techdocsStorageApi = useApi(techdocsStorageApiRef);
1625
1447
  const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
1626
1448
  const { state, path, content: rawPage } = useTechDocsReader();
1627
1449
  const [dom, setDom] = useState(null);
@@ -1702,7 +1524,6 @@ const useTechDocsReaderDom = (entityRef) => {
1702
1524
  );
1703
1525
  const postRender = useCallback(
1704
1526
  async (transformedElement) => transform(transformedElement, [
1705
- scrollIntoAnchor(),
1706
1527
  scrollIntoNavigation(),
1707
1528
  copyToClipboard(theme),
1708
1529
  addLinkClickListener({
@@ -1840,6 +1661,22 @@ const TechDocsReaderPageContent = withTechDocsReaderProvider(
1840
1661
  setShadowRoot
1841
1662
  } = useTechDocsReaderPage();
1842
1663
  const dom = useTechDocsReaderDom(entityRef);
1664
+ const path = window.location.pathname;
1665
+ const hash = window.location.hash;
1666
+ const isStyleLoading = useShadowDomStylesLoading(dom);
1667
+ const [hashElement] = useShadowRootElements([`[id="${hash.slice(1)}"]`]);
1668
+ useEffect(() => {
1669
+ var _a2;
1670
+ if (isStyleLoading)
1671
+ return;
1672
+ if (hash) {
1673
+ if (hashElement) {
1674
+ hashElement.scrollIntoView();
1675
+ }
1676
+ } else {
1677
+ (_a2 = document == null ? void 0 : document.querySelector("header")) == null ? void 0 : _a2.scrollIntoView();
1678
+ }
1679
+ }, [path, hash, hashElement, isStyleLoading]);
1843
1680
  const handleAppend = useCallback(
1844
1681
  (newShadowRoot) => {
1845
1682
  setShadowRoot(newShadowRoot);
@@ -1865,17 +1702,6 @@ const TechDocsReaderPageContent = withTechDocsReaderProvider(
1865
1702
  );
1866
1703
  const Reader = TechDocsReaderPageContent;
1867
1704
 
1868
- const rootRouteRef = createRouteRef({
1869
- id: "techdocs:index-page"
1870
- });
1871
- const rootDocsRouteRef = createRouteRef({
1872
- id: "techdocs:reader-page",
1873
- params: ["namespace", "kind", "name"]
1874
- });
1875
- const rootCatalogDocsRouteRef = createRouteRef({
1876
- id: "techdocs:catalog-reader-view"
1877
- });
1878
-
1879
1705
  const skeleton = /* @__PURE__ */ React.createElement(Skeleton, { animation: "wave", variant: "text", height: 40 });
1880
1706
  const TechDocsReaderPageHeader = (props) => {
1881
1707
  const {
@@ -1940,7 +1766,7 @@ const TechDocsReaderPageHeader = (props) => {
1940
1766
  }
1941
1767
  )
1942
1768
  }
1943
- ), lifecycle ? /* @__PURE__ */ React.createElement(HeaderLabel, { label: "Lifecycle", value: lifecycle }) : null, locationMetadata && locationMetadata.type !== "dir" && locationMetadata.type !== "file" ? /* @__PURE__ */ React.createElement(
1769
+ ), lifecycle ? /* @__PURE__ */ React.createElement(HeaderLabel, { label: "Lifecycle", value: String(lifecycle) }) : null, locationMetadata && locationMetadata.type !== "dir" && locationMetadata.type !== "file" ? /* @__PURE__ */ React.createElement(
1944
1770
  HeaderLabel,
1945
1771
  {
1946
1772
  label: "",
@@ -2046,7 +1872,7 @@ const TechDocsReaderLayout = (props) => {
2046
1872
  const { withSearch, withHeader = true } = props;
2047
1873
  return /* @__PURE__ */ React.createElement(Page, { themeId: "documentation" }, withHeader && /* @__PURE__ */ React.createElement(TechDocsReaderPageHeader, null), /* @__PURE__ */ React.createElement(TechDocsReaderPageSubheader, null), /* @__PURE__ */ React.createElement(TechDocsReaderPageContent, { withSearch }));
2048
1874
  };
2049
- const TechDocsReaderPage$1 = (props) => {
1875
+ const TechDocsReaderPage = (props) => {
2050
1876
  const { kind, name, namespace } = useRouteRefParams(rootDocsRouteRef);
2051
1877
  const { children, entityRef = { kind, name, namespace } } = props;
2052
1878
  const outlet = useOutlet();
@@ -2071,487 +1897,5 @@ const TechDocsReaderPage$1 = (props) => {
2071
1897
  }) : children)));
2072
1898
  };
2073
1899
 
2074
- function toLowerMaybe(str, config) {
2075
- return config.getOptionalBoolean(
2076
- "techdocs.legacyUseCaseSensitiveTripletPaths"
2077
- ) ? str : str.toLocaleLowerCase("en-US");
2078
- }
2079
-
2080
- const DocsCardGrid = (props) => {
2081
- const { entities } = props;
2082
- const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);
2083
- const config = useApi(configApiRef);
2084
- if (!entities)
2085
- return null;
2086
- return /* @__PURE__ */ React.createElement(ItemCardGrid, { "data-testid": "docs-explore" }, !(entities == null ? void 0 : entities.length) ? null : entities.map((entity, index) => {
2087
- var _a, _b;
2088
- return /* @__PURE__ */ React.createElement(Card, { key: index }, /* @__PURE__ */ React.createElement(CardMedia, null, /* @__PURE__ */ React.createElement(
2089
- ItemCardHeader,
2090
- {
2091
- title: (_a = entity.metadata.title) != null ? _a : entity.metadata.name
2092
- }
2093
- )), /* @__PURE__ */ React.createElement(CardContent, null, entity.metadata.description), /* @__PURE__ */ React.createElement(CardActions, null, /* @__PURE__ */ React.createElement(
2094
- LinkButton,
2095
- {
2096
- to: getRouteToReaderPageFor({
2097
- namespace: toLowerMaybe(
2098
- (_b = entity.metadata.namespace) != null ? _b : "default",
2099
- config
2100
- ),
2101
- kind: toLowerMaybe(entity.kind, config),
2102
- name: toLowerMaybe(entity.metadata.name, config)
2103
- }),
2104
- color: "primary",
2105
- "data-testid": "read_docs"
2106
- },
2107
- "Read Docs"
2108
- )));
2109
- }));
2110
- };
2111
-
2112
- const allEntitiesGroup = {
2113
- title: "All Documentation",
2114
- filterPredicate: () => true
2115
- };
2116
- const EntityListDocsGridGroup = (props) => {
2117
- const { entities, group } = props;
2118
- const { loading: loadingOwnership, isOwnedEntity } = useEntityOwnership();
2119
- const shownEntities = entities.filter((entity) => {
2120
- if (group.filterPredicate === "ownedByUser") {
2121
- if (loadingOwnership) {
2122
- return false;
2123
- }
2124
- return isOwnedEntity(entity);
2125
- }
2126
- return typeof group.filterPredicate === "function" && group.filterPredicate(entity);
2127
- });
2128
- const titleComponent = (() => {
2129
- return typeof group.title === "string" ? /* @__PURE__ */ React.createElement(ContentHeader, { title: group.title }) : group.title;
2130
- })();
2131
- if (shownEntities.length === 0) {
2132
- return null;
2133
- }
2134
- return /* @__PURE__ */ React.createElement(Content, null, titleComponent, /* @__PURE__ */ React.createElement(DocsCardGrid, { entities: shownEntities }));
2135
- };
2136
- const EntityListDocsGrid = (props) => {
2137
- const { loading, error, entities } = useEntityList();
2138
- if (error) {
2139
- return /* @__PURE__ */ React.createElement(
2140
- WarningPanel,
2141
- {
2142
- severity: "error",
2143
- title: "Could not load available documentation."
2144
- },
2145
- /* @__PURE__ */ React.createElement(CodeSnippet, { language: "text", text: error.toString() })
2146
- );
2147
- }
2148
- if (loading) {
2149
- return /* @__PURE__ */ React.createElement(Progress, null);
2150
- }
2151
- if (entities.length === 0) {
2152
- return /* @__PURE__ */ React.createElement("div", { "data-testid": "doc-not-found" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "No documentation found that match your filter. Learn more about", " ", /* @__PURE__ */ React.createElement(Link, { to: "https://backstage.io/docs/features/techdocs/creating-and-publishing" }, "publishing documentation"), "."));
2153
- }
2154
- entities.sort(
2155
- (a, b) => {
2156
- var _a, _b;
2157
- return ((_a = a.metadata.title) != null ? _a : a.metadata.name).localeCompare(
2158
- (_b = b.metadata.title) != null ? _b : b.metadata.name
2159
- );
2160
- }
2161
- );
2162
- return /* @__PURE__ */ React.createElement(Content, null, (props.groups || [allEntitiesGroup]).map((group, index) => /* @__PURE__ */ React.createElement(
2163
- EntityListDocsGridGroup,
2164
- {
2165
- entities,
2166
- group,
2167
- key: `${group.title}-${index}`
2168
- }
2169
- )));
2170
- };
2171
-
2172
- const YellowStar = withStyles$1({
2173
- root: {
2174
- color: "#f3ba37"
2175
- }
2176
- })(Star);
2177
- const actionFactories = {
2178
- createCopyDocsUrlAction(copyToClipboard) {
2179
- return (row) => {
2180
- return {
2181
- icon: () => /* @__PURE__ */ React.createElement(ShareIcon, { fontSize: "small" }),
2182
- tooltip: "Click to copy documentation link to clipboard",
2183
- onClick: () => copyToClipboard(`${window.location.origin}${row.resolved.docsUrl}`)
2184
- };
2185
- };
2186
- },
2187
- createStarEntityAction(isStarredEntity, toggleStarredEntity) {
2188
- return (row) => {
2189
- const entity = row.entity;
2190
- const isStarred = isStarredEntity(entity);
2191
- return {
2192
- cellStyle: { paddingLeft: "1em" },
2193
- icon: () => isStarred ? /* @__PURE__ */ React.createElement(YellowStar, null) : /* @__PURE__ */ React.createElement(StarBorder, null),
2194
- tooltip: isStarred ? "Remove from favorites" : "Add to favorites",
2195
- onClick: () => toggleStarredEntity(entity)
2196
- };
2197
- };
2198
- }
2199
- };
2200
-
2201
- function customTitle(entity) {
2202
- return entity.metadata.title || entity.metadata.name;
2203
- }
2204
- const columnFactories = {
2205
- createNameColumn() {
2206
- return {
2207
- title: "Document",
2208
- field: "entity.metadata.name",
2209
- highlight: true,
2210
- render: (row) => /* @__PURE__ */ React.createElement(
2211
- SubvalueCell,
2212
- {
2213
- value: /* @__PURE__ */ React.createElement(Link, { to: row.resolved.docsUrl }, customTitle(row.entity)),
2214
- subvalue: row.entity.metadata.description
2215
- }
2216
- )
2217
- };
2218
- },
2219
- createOwnerColumn() {
2220
- return {
2221
- title: "Owner",
2222
- field: "resolved.ownedByRelationsTitle",
2223
- render: ({ resolved }) => /* @__PURE__ */ React.createElement(
2224
- EntityRefLinks,
2225
- {
2226
- entityRefs: resolved.ownedByRelations,
2227
- defaultKind: "group"
2228
- }
2229
- )
2230
- };
2231
- },
2232
- createKindColumn() {
2233
- return {
2234
- title: "Kind",
2235
- field: "entity.kind"
2236
- };
2237
- },
2238
- createTypeColumn() {
2239
- return {
2240
- title: "Type",
2241
- field: "entity.spec.type"
2242
- };
2243
- }
2244
- };
2245
-
2246
- const DocsTable = (props) => {
2247
- const { entities, title, loading, columns, actions, options } = props;
2248
- const [, copyToClipboard] = useCopyToClipboard();
2249
- const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);
2250
- const config = useApi(configApiRef);
2251
- if (!entities)
2252
- return null;
2253
- const documents = entities.map((entity) => {
2254
- var _a;
2255
- const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY);
2256
- return {
2257
- entity,
2258
- resolved: {
2259
- docsUrl: getRouteToReaderPageFor({
2260
- namespace: toLowerMaybe(
2261
- (_a = entity.metadata.namespace) != null ? _a : "default",
2262
- config
2263
- ),
2264
- kind: toLowerMaybe(entity.kind, config),
2265
- name: toLowerMaybe(entity.metadata.name, config)
2266
- }),
2267
- ownedByRelations,
2268
- ownedByRelationsTitle: ownedByRelations.map((r) => humanizeEntityRef(r, { defaultKind: "group" })).join(", ")
2269
- }
2270
- };
2271
- });
2272
- const defaultColumns = [
2273
- columnFactories.createNameColumn(),
2274
- columnFactories.createOwnerColumn(),
2275
- columnFactories.createKindColumn(),
2276
- columnFactories.createTypeColumn()
2277
- ];
2278
- const defaultActions = [
2279
- actionFactories.createCopyDocsUrlAction(copyToClipboard)
2280
- ];
2281
- const pageSize = 20;
2282
- const paging = documents && documents.length > pageSize;
2283
- return /* @__PURE__ */ React.createElement(React.Fragment, null, loading || documents && documents.length > 0 ? /* @__PURE__ */ React.createElement(
2284
- Table,
2285
- {
2286
- isLoading: loading,
2287
- options: {
2288
- paging,
2289
- pageSize,
2290
- search: true,
2291
- actionsColumnIndex: -1,
2292
- ...options
2293
- },
2294
- data: documents,
2295
- columns: columns || defaultColumns,
2296
- actions: actions || defaultActions,
2297
- title: title ? `${title} (${documents.length})` : `All (${documents.length})`
2298
- }
2299
- ) : /* @__PURE__ */ React.createElement(
2300
- EmptyState,
2301
- {
2302
- missing: "data",
2303
- title: "No documents to show",
2304
- description: "Create your own document. Check out our Getting Started Information",
2305
- action: /* @__PURE__ */ React.createElement(
2306
- LinkButton,
2307
- {
2308
- color: "primary",
2309
- to: "https://backstage.io/docs/features/techdocs/getting-started",
2310
- variant: "contained"
2311
- },
2312
- "DOCS"
2313
- )
2314
- }
2315
- ));
2316
- };
2317
- DocsTable.columns = columnFactories;
2318
- DocsTable.actions = actionFactories;
2319
-
2320
- const EntityListDocsTable = (props) => {
2321
- var _a, _b;
2322
- const { columns, actions, options } = props;
2323
- const { loading, error, entities, filters } = useEntityList();
2324
- const { isStarredEntity, toggleStarredEntity } = useStarredEntities();
2325
- const [, copyToClipboard] = useCopyToClipboard();
2326
- const title = capitalize((_b = (_a = filters.user) == null ? void 0 : _a.value) != null ? _b : "all");
2327
- const defaultActions = [
2328
- actionFactories.createCopyDocsUrlAction(copyToClipboard),
2329
- actionFactories.createStarEntityAction(
2330
- isStarredEntity,
2331
- toggleStarredEntity
2332
- )
2333
- ];
2334
- if (error) {
2335
- return /* @__PURE__ */ React.createElement(
2336
- WarningPanel,
2337
- {
2338
- severity: "error",
2339
- title: "Could not load available documentation."
2340
- },
2341
- /* @__PURE__ */ React.createElement(CodeSnippet, { language: "text", text: error.toString() })
2342
- );
2343
- }
2344
- return /* @__PURE__ */ React.createElement(
2345
- DocsTable,
2346
- {
2347
- title,
2348
- entities,
2349
- loading,
2350
- actions: actions || defaultActions,
2351
- columns,
2352
- options
2353
- }
2354
- );
2355
- };
2356
- EntityListDocsTable.columns = columnFactories;
2357
- EntityListDocsTable.actions = actionFactories;
2358
-
2359
- const TechDocsPageWrapper = (props) => {
2360
- var _a;
2361
- const { children } = props;
2362
- const configApi = useApi(configApiRef);
2363
- const generatedSubtitle = `Documentation available in ${(_a = configApi.getOptionalString("organization.name")) != null ? _a : "Backstage"}`;
2364
- return /* @__PURE__ */ React.createElement(
2365
- PageWithHeader,
2366
- {
2367
- title: "Documentation",
2368
- subtitle: generatedSubtitle,
2369
- themeId: "documentation"
2370
- },
2371
- children
2372
- );
2373
- };
2374
-
2375
- class TechDocsFilter {
2376
- getCatalogFilters() {
2377
- return {
2378
- "metadata.annotations.backstage.io/techdocs-ref": CATALOG_FILTER_EXISTS
2379
- };
2380
- }
2381
- }
2382
- const TechDocsPicker = () => {
2383
- const { updateFilters } = useEntityList();
2384
- useEffect(() => {
2385
- updateFilters({
2386
- techdocs: new TechDocsFilter()
2387
- });
2388
- }, [updateFilters]);
2389
- return null;
2390
- };
2391
-
2392
- const DefaultTechDocsHome = (props) => {
2393
- const { initialFilter = "owned", columns, actions } = props;
2394
- return /* @__PURE__ */ React.createElement(TechDocsPageWrapper, null, /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "" }, /* @__PURE__ */ React.createElement(SupportButton, null, "Discover documentation in your ecosystem.")), /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(CatalogFilterLayout, null, /* @__PURE__ */ React.createElement(CatalogFilterLayout.Filters, null, /* @__PURE__ */ React.createElement(TechDocsPicker, null), /* @__PURE__ */ React.createElement(UserListPicker, { initialFilter }), /* @__PURE__ */ React.createElement(EntityOwnerPicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, /* @__PURE__ */ React.createElement(EntityListDocsTable, { actions, columns }))))));
2395
- };
2396
-
2397
- const techdocsPlugin = createPlugin({
2398
- id: "techdocs",
2399
- apis: [
2400
- createApiFactory({
2401
- api: techdocsStorageApiRef$1,
2402
- deps: {
2403
- configApi: configApiRef,
2404
- discoveryApi: discoveryApiRef,
2405
- identityApi: identityApiRef,
2406
- fetchApi: fetchApiRef
2407
- },
2408
- factory: ({ configApi, discoveryApi, identityApi, fetchApi }) => new TechDocsStorageClient({
2409
- configApi,
2410
- discoveryApi,
2411
- identityApi,
2412
- fetchApi
2413
- })
2414
- }),
2415
- createApiFactory({
2416
- api: techdocsApiRef$1,
2417
- deps: {
2418
- configApi: configApiRef,
2419
- discoveryApi: discoveryApiRef,
2420
- fetchApi: fetchApiRef
2421
- },
2422
- factory: ({ configApi, discoveryApi, fetchApi }) => new TechDocsClient({
2423
- configApi,
2424
- discoveryApi,
2425
- fetchApi
2426
- })
2427
- })
2428
- ],
2429
- routes: {
2430
- root: rootRouteRef,
2431
- docRoot: rootDocsRouteRef,
2432
- entityContent: rootCatalogDocsRouteRef
2433
- }
2434
- });
2435
- const TechdocsPage = techdocsPlugin.provide(
2436
- createRoutableExtension({
2437
- name: "TechdocsPage",
2438
- component: () => Promise.resolve().then(function () { return Router$1; }).then((m) => m.Router),
2439
- mountPoint: rootRouteRef
2440
- })
2441
- );
2442
- const EntityTechdocsContent = techdocsPlugin.provide(
2443
- createRoutableExtension({
2444
- name: "EntityTechdocsContent",
2445
- component: () => Promise.resolve().then(function () { return Router$1; }).then((m) => m.EmbeddedDocsRouter),
2446
- mountPoint: rootCatalogDocsRouteRef
2447
- })
2448
- );
2449
- const TechDocsCustomHome = techdocsPlugin.provide(
2450
- createRoutableExtension({
2451
- name: "TechDocsCustomHome",
2452
- component: () => import('./TechDocsCustomHome-453580db.esm.js').then(
2453
- (m) => m.TechDocsCustomHome
2454
- ),
2455
- mountPoint: rootRouteRef
2456
- })
2457
- );
2458
- const TechDocsIndexPage$2 = techdocsPlugin.provide(
2459
- createRoutableExtension({
2460
- name: "TechDocsIndexPage",
2461
- component: () => Promise.resolve().then(function () { return TechDocsIndexPage$1; }).then(
2462
- (m) => m.TechDocsIndexPage
2463
- ),
2464
- mountPoint: rootRouteRef
2465
- })
2466
- );
2467
- const TechDocsReaderPage = techdocsPlugin.provide(
2468
- createRoutableExtension({
2469
- name: "TechDocsReaderPage",
2470
- component: () => import('./index-715820dc.esm.js').then(
2471
- (m) => m.TechDocsReaderPage
2472
- ),
2473
- mountPoint: rootDocsRouteRef
2474
- })
2475
- );
2476
- const TechDocsSearchResultListItem = techdocsPlugin.provide(
2477
- createSearchResultListItemExtension({
2478
- name: "TechDocsSearchResultListItem",
2479
- component: () => import('./TechDocsSearchResultListItem-4736f829.esm.js').then(
2480
- (m) => m.TechDocsSearchResultListItem
2481
- ),
2482
- predicate: (result) => result.type === "techdocs"
2483
- })
2484
- );
2485
-
2486
- const TECHDOCS_EXTERNAL_ANNOTATION$1 = "backstage.io/techdocs-entity";
2487
- const EntityPageDocs = ({ entity }) => {
2488
- var _a, _b;
2489
- let entityRef = getCompoundEntityRef(entity);
2490
- if ((_a = entity.metadata.annotations) == null ? void 0 : _a[TECHDOCS_EXTERNAL_ANNOTATION$1]) {
2491
- try {
2492
- entityRef = parseEntityRef(
2493
- (_b = entity.metadata.annotations) == null ? void 0 : _b[TECHDOCS_EXTERNAL_ANNOTATION$1]
2494
- );
2495
- } catch {
2496
- }
2497
- }
2498
- return /* @__PURE__ */ React.createElement(TechDocsReaderPage, { entityRef }, /* @__PURE__ */ React.createElement(TechDocsReaderPageSubheader, null), /* @__PURE__ */ React.createElement(TechDocsReaderPageContent, { withSearch: false }));
2499
- };
2500
-
2501
- const TechDocsIndexPage = (props) => {
2502
- const outlet = useOutlet();
2503
- return outlet || /* @__PURE__ */ React.createElement(DefaultTechDocsHome, { ...props });
2504
- };
2505
-
2506
- var TechDocsIndexPage$1 = /*#__PURE__*/Object.freeze({
2507
- __proto__: null,
2508
- TechDocsIndexPage: TechDocsIndexPage
2509
- });
2510
-
2511
- const TECHDOCS_ANNOTATION = "backstage.io/techdocs-ref";
2512
- const TECHDOCS_EXTERNAL_ANNOTATION = "backstage.io/techdocs-entity";
2513
- const isTechDocsAvailable = (entity) => {
2514
- var _a, _b, _c, _d;
2515
- return Boolean((_b = (_a = entity == null ? void 0 : entity.metadata) == null ? void 0 : _a.annotations) == null ? void 0 : _b[TECHDOCS_ANNOTATION]) || Boolean((_d = (_c = entity == null ? void 0 : entity.metadata) == null ? void 0 : _c.annotations) == null ? void 0 : _d[TECHDOCS_EXTERNAL_ANNOTATION]);
2516
- };
2517
- const Router = () => {
2518
- return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: "/", element: /* @__PURE__ */ React.createElement(TechDocsIndexPage, null) }), /* @__PURE__ */ React.createElement(
2519
- Route,
2520
- {
2521
- path: "/:namespace/:kind/:name/*",
2522
- element: /* @__PURE__ */ React.createElement(TechDocsReaderPage$1, null)
2523
- }
2524
- ));
2525
- };
2526
- const EmbeddedDocsRouter = (props) => {
2527
- var _a, _b;
2528
- const { children } = props;
2529
- const { entity } = useEntity();
2530
- const element = useRoutes([
2531
- {
2532
- path: "/*",
2533
- element: /* @__PURE__ */ React.createElement(EntityPageDocs, { entity }),
2534
- children: [
2535
- {
2536
- path: "*",
2537
- element: children
2538
- }
2539
- ]
2540
- }
2541
- ]);
2542
- const projectId = ((_a = entity.metadata.annotations) == null ? void 0 : _a[TECHDOCS_ANNOTATION]) || ((_b = entity.metadata.annotations) == null ? void 0 : _b[TECHDOCS_EXTERNAL_ANNOTATION]);
2543
- if (!projectId) {
2544
- return /* @__PURE__ */ React.createElement(MissingAnnotationEmptyState, { annotation: [TECHDOCS_ANNOTATION] });
2545
- }
2546
- return element;
2547
- };
2548
-
2549
- var Router$1 = /*#__PURE__*/Object.freeze({
2550
- __proto__: null,
2551
- isTechDocsAvailable: isTechDocsAvailable,
2552
- Router: Router,
2553
- EmbeddedDocsRouter: EmbeddedDocsRouter
2554
- });
2555
-
2556
- export { DocsTable as D, EntityTechdocsContent as E, Reader as R, TechDocsPageWrapper as T, DocsCardGrid as a, TechDocsReaderPage$1 as b, TechDocsReaderLayout as c, TechDocsCustomHome as d, TechDocsIndexPage$2 as e, TechdocsPage as f, TechDocsReaderPage as g, TechDocsSearchResultListItem as h, techdocsStorageApiRef as i, techdocsApiRef as j, TechDocsClient as k, TechDocsStorageClient as l, TechDocsReaderProvider as m, TechDocsReaderPageHeader as n, TechDocsReaderPageContent as o, TechDocsReaderPageSubheader as p, TechDocsSearch as q, EntityListDocsGrid as r, EntityListDocsTable as s, techdocsPlugin as t, DefaultTechDocsHome as u, TechDocsPicker as v, isTechDocsAvailable as w, Router as x, EmbeddedDocsRouter as y };
2557
- //# sourceMappingURL=index-683d6465.esm.js.map
1900
+ export { Reader as R, TechDocsReaderProvider as T, TechDocsReaderLayout as a, TechDocsReaderPageHeader as b, TechDocsReaderPageContent as c, TechDocsReaderPageSubheader as d, TechDocsSearch as e, TechDocsReaderPage as f };
1901
+ //# sourceMappingURL=TechDocsReaderPage-89ab8b88.esm.js.map