@backstage/plugin-techdocs 0.14.0 → 0.15.1

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/dist/index.esm.js CHANGED
@@ -1,23 +1,18 @@
1
- import { createApiRef, createRouteRef, useRouteRef, useApi, configApiRef, createPlugin, createApiFactory, discoveryApiRef, identityApiRef, fetchApiRef, createRoutableExtension, createComponentExtension, useApp } from '@backstage/core-plugin-api';
1
+ import { createApiRef, useApi, configApiRef, createRouteRef, useRouteRef, useApp, createPlugin, createApiFactory, discoveryApiRef, identityApiRef, fetchApiRef, createRoutableExtension } from '@backstage/core-plugin-api';
2
2
  import { ResponseError, NotFoundError } from '@backstage/errors';
3
3
  import { EventSourcePolyfill } from 'event-source-polyfill';
4
- import React, { useEffect, useState, useReducer, useRef, useMemo, createContext, useContext, useCallback } from 'react';
5
- import { makeStyles, ListItemText, ListItem, Divider, Card, CardMedia, CardContent, CardActions, TextField, InputAdornment, IconButton, CircularProgress, createStyles, Button as Button$1, Drawer, Grid, Typography, useTheme, lighten, alpha } from '@material-ui/core';
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
- import TextTruncate from 'react-text-truncate';
8
- import { FilteredEntityLayout, FilterContainer, EntityListContainer } from '@backstage/plugin-catalog';
9
- import { favoriteEntityIcon, favoriteEntityTooltip, EntityRefLinks, getEntityRelations, formatEntityRefTitle, useEntityList, useStarredEntities, CATALOG_FILTER_EXISTS, EntityListProvider, UserListPicker, EntityOwnerPicker, EntityTagPicker, EntityRefLink, catalogApiRef, isOwnerOf, useEntity } from '@backstage/plugin-catalog-react';
10
- import useCopyToClipboard from 'react-use/lib/useCopyToClipboard';
11
- import { capitalize } from 'lodash';
12
- import { RELATION_OWNED_BY, parseEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
13
- import ShareIcon from '@material-ui/icons/Share';
4
+ import React, { useState, useCallback, useEffect, useReducer, useRef, useMemo, createContext, useContext } from 'react';
14
5
  import { useNavigate as useNavigate$1, useParams, Routes, Route } from 'react-router-dom';
6
+ import { withStyles, Tooltip, ThemeProvider, SvgIcon, makeStyles, ListItemText, ListItem, Divider, TextField, InputAdornment, IconButton, CircularProgress, createStyles, Button, Drawer, Grid, Typography, useTheme, lighten, alpha, Card, CardMedia, CardContent, CardActions } from '@material-ui/core';
15
7
  import { scmIntegrationsApiRef } from '@backstage/integration-react';
8
+ import { Link, LogViewer, ErrorPage, Progress, SidebarPinStateContext, HeaderLabel, Header, Page, Content, ItemCardGrid, ItemCardHeader, Button as Button$1, WarningPanel, CodeSnippet, SubvalueCell, Table, EmptyState, PageWithHeader, ContentHeader, SupportButton, MissingAnnotationEmptyState } from '@backstage/core-components';
16
9
  import { replaceGitHubUrlType } from '@backstage/integration';
17
10
  import FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined';
18
11
  import ReactDOM from 'react-dom';
19
12
  import parseGitUrl from 'git-url-parse';
13
+ import MenuIcon from '@material-ui/icons/Menu';
20
14
  import DOMPurify from 'dompurify';
15
+ import TextTruncate from 'react-text-truncate';
21
16
  import { SearchContextProvider, useSearch } from '@backstage/plugin-search';
22
17
  import SearchIcon from '@material-ui/icons/Search';
23
18
  import Autocomplete from '@material-ui/lab/Autocomplete';
@@ -28,6 +23,15 @@ import Close from '@material-ui/icons/Close';
28
23
  import useAsync from 'react-use/lib/useAsync';
29
24
  import useAsyncRetry from 'react-use/lib/useAsyncRetry';
30
25
  import CodeIcon from '@material-ui/icons/Code';
26
+ import { RELATION_OWNED_BY } from '@backstage/catalog-model';
27
+ import { getEntityRelations, EntityRefLink, EntityRefLinks, useEntityList, humanizeEntityRef, useStarredEntities, CATALOG_FILTER_EXISTS, EntityListProvider, UserListPicker, EntityOwnerPicker, EntityTagPicker, useEntity } from '@backstage/plugin-catalog-react';
28
+ import useCopyToClipboard from 'react-use/lib/useCopyToClipboard';
29
+ import { capitalize } from 'lodash';
30
+ import ShareIcon from '@material-ui/icons/Share';
31
+ import { withStyles as withStyles$1 } from '@material-ui/styles';
32
+ import Star from '@material-ui/icons/Star';
33
+ import StarBorder from '@material-ui/icons/StarBorder';
34
+ import { FilteredEntityLayout, FilterContainer, EntityListContainer } from '@backstage/plugin-catalog';
31
35
 
32
36
  const techdocsStorageApiRef = createApiRef({
33
37
  id: "plugin.techdocs.storageservice"
@@ -148,577 +152,232 @@ class TechDocsStorageClient {
148
152
  }
149
153
  }
150
154
 
151
- const useStyles$3 = makeStyles({
152
- flexContainer: {
153
- flexWrap: "wrap"
154
- },
155
- itemText: {
156
- width: "100%",
157
- marginBottom: "1rem"
158
- }
159
- });
160
- const DocsResultListItem = ({
161
- result,
162
- lineClamp = 5,
163
- asListItem = true,
164
- asLink = true,
165
- title
155
+ const isSvgNeedingInlining = (attrName, attrVal, apiOrigin) => {
156
+ const isSrcToSvg = attrName === "src" && attrVal.endsWith(".svg");
157
+ const isRelativeUrl = !attrVal.match(/^([a-z]*:)?\/\//i);
158
+ const pointsToOurBackend = attrVal.startsWith(apiOrigin);
159
+ return isSrcToSvg && (isRelativeUrl || pointsToOurBackend);
160
+ };
161
+ const addBaseUrl = ({
162
+ techdocsStorageApi,
163
+ entityId,
164
+ path
166
165
  }) => {
167
- const classes = useStyles$3();
168
- const TextItem = () => {
169
- var _a;
170
- return /* @__PURE__ */ React.createElement(ListItemText, {
171
- className: classes.itemText,
172
- primaryTypographyProps: { variant: "h6" },
173
- primary: title ? title : `${result.title} | ${(_a = result.entityTitle) != null ? _a : result.name} docs`,
174
- secondary: /* @__PURE__ */ React.createElement(TextTruncate, {
175
- line: lineClamp,
176
- truncateText: "\u2026",
177
- text: result.text,
178
- element: "span"
179
- })
180
- });
166
+ return async (dom) => {
167
+ const apiOrigin = await techdocsStorageApi.getApiOrigin();
168
+ const updateDom = async (list, attributeName) => {
169
+ for (const elem of list) {
170
+ if (elem.hasAttribute(attributeName)) {
171
+ const elemAttribute = elem.getAttribute(attributeName);
172
+ if (!elemAttribute)
173
+ return;
174
+ const newValue = await techdocsStorageApi.getBaseUrl(elemAttribute, entityId, path);
175
+ if (isSvgNeedingInlining(attributeName, elemAttribute, apiOrigin)) {
176
+ try {
177
+ const svg = await fetch(newValue, { credentials: "include" });
178
+ const svgContent = await svg.text();
179
+ elem.setAttribute(attributeName, `data:image/svg+xml;base64,${btoa(svgContent)}`);
180
+ } catch (e) {
181
+ elem.setAttribute("alt", `Error: ${elemAttribute}`);
182
+ }
183
+ } else {
184
+ elem.setAttribute(attributeName, newValue);
185
+ }
186
+ }
187
+ }
188
+ };
189
+ await Promise.all([
190
+ updateDom(dom.querySelectorAll("img"), "src"),
191
+ updateDom(dom.querySelectorAll("script"), "src"),
192
+ updateDom(dom.querySelectorAll("source"), "src"),
193
+ updateDom(dom.querySelectorAll("link"), "href"),
194
+ updateDom(dom.querySelectorAll("a[download]"), "href")
195
+ ]);
196
+ return dom;
181
197
  };
182
- const LinkWrapper = ({ children }) => asLink ? /* @__PURE__ */ React.createElement(Link, {
183
- to: result.location
184
- }, children) : /* @__PURE__ */ React.createElement(React.Fragment, null, children);
185
- const ListItemWrapper = ({ children }) => asListItem ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ListItem, {
186
- alignItems: "flex-start",
187
- className: classes.flexContainer
188
- }, children), /* @__PURE__ */ React.createElement(Divider, {
189
- component: "li"
190
- })) : /* @__PURE__ */ React.createElement(React.Fragment, null, children);
191
- return /* @__PURE__ */ React.createElement(LinkWrapper, null, /* @__PURE__ */ React.createElement(ListItemWrapper, null, /* @__PURE__ */ React.createElement(TextItem, null)));
192
198
  };
193
199
 
194
- const rootRouteRef = createRouteRef({
195
- id: "techdocs:index-page"
196
- });
197
- const rootDocsRouteRef = createRouteRef({
198
- id: "techdocs:reader-page",
199
- params: ["namespace", "kind", "name"]
200
- });
201
- const rootCatalogDocsRouteRef = createRouteRef({
202
- id: "techdocs:catalog-reader-view"
203
- });
200
+ const addGitFeedbackLink = (scmIntegrationsApi) => {
201
+ return (dom) => {
202
+ const sourceAnchor = dom.querySelector('[title="Edit this page"]');
203
+ if (!sourceAnchor || !sourceAnchor.href) {
204
+ return dom;
205
+ }
206
+ const sourceURL = new URL(sourceAnchor.href);
207
+ const integration = scmIntegrationsApi.byUrl(sourceURL);
208
+ if ((integration == null ? void 0 : integration.type) !== "github" && (integration == null ? void 0 : integration.type) !== "gitlab") {
209
+ return dom;
210
+ }
211
+ const title = dom.querySelector("article>h1").childNodes[0].textContent;
212
+ const issueTitle = encodeURIComponent(`Documentation Feedback: ${title}`);
213
+ const issueDesc = encodeURIComponent(`Page source:
214
+ ${sourceAnchor.href}
204
215
 
205
- function createCopyDocsUrlAction(copyToClipboard) {
206
- return (row) => {
207
- return {
208
- icon: () => /* @__PURE__ */ React.createElement(ShareIcon, {
209
- fontSize: "small"
210
- }),
211
- tooltip: "Click to copy documentation link to clipboard",
212
- onClick: () => copyToClipboard(`${window.location.origin}${row.resolved.docsUrl}`)
213
- };
214
- };
215
- }
216
- function createStarEntityAction(isStarredEntity, toggleStarredEntity) {
217
- return ({ entity }) => {
218
- const isStarred = isStarredEntity(entity);
219
- return {
220
- cellStyle: { paddingLeft: "1em" },
221
- icon: () => favoriteEntityIcon(isStarred),
222
- tooltip: favoriteEntityTooltip(isStarred),
223
- onClick: () => toggleStarredEntity(entity)
224
- };
216
+ Feedback:`);
217
+ const gitUrl = (integration == null ? void 0 : integration.type) === "github" ? replaceGitHubUrlType(sourceURL.href, "blob") : sourceURL.href;
218
+ const gitInfo = parseGitUrl(gitUrl);
219
+ const repoPath = `/${gitInfo.organization}/${gitInfo.name}`;
220
+ const feedbackLink = sourceAnchor.cloneNode();
221
+ switch (integration == null ? void 0 : integration.type) {
222
+ case "gitlab":
223
+ feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?issue[title]=${issueTitle}&issue[description]=${issueDesc}`;
224
+ break;
225
+ case "github":
226
+ feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?title=${issueTitle}&body=${issueDesc}`;
227
+ break;
228
+ default:
229
+ return dom;
230
+ }
231
+ ReactDOM.render(React.createElement(FeedbackOutlinedIcon), feedbackLink);
232
+ feedbackLink.style.paddingLeft = "5px";
233
+ feedbackLink.title = "Leave feedback for this page";
234
+ feedbackLink.id = "git-feedback-link";
235
+ sourceAnchor == null ? void 0 : sourceAnchor.insertAdjacentElement("beforebegin", feedbackLink);
236
+ return dom;
225
237
  };
226
- }
238
+ };
227
239
 
228
- var actionFactories = /*#__PURE__*/Object.freeze({
229
- __proto__: null,
230
- createCopyDocsUrlAction: createCopyDocsUrlAction,
231
- createStarEntityAction: createStarEntityAction
232
- });
240
+ const addSidebarToggle = () => {
241
+ return (dom) => {
242
+ const mkdocsToggleSidebar = dom.querySelector('.md-header label[for="__drawer"]');
243
+ const article = dom.querySelector("article");
244
+ if (!mkdocsToggleSidebar || !article) {
245
+ return dom;
246
+ }
247
+ const toggleSidebar = mkdocsToggleSidebar.cloneNode();
248
+ ReactDOM.render(React.createElement(MenuIcon), toggleSidebar);
249
+ toggleSidebar.style.paddingLeft = "5px";
250
+ toggleSidebar.classList.add("md-content__button");
251
+ toggleSidebar.title = "Toggle Sidebar";
252
+ toggleSidebar.id = "toggle-sidebar";
253
+ article == null ? void 0 : article.prepend(toggleSidebar);
254
+ return dom;
255
+ };
256
+ };
233
257
 
234
- function customTitle(entity) {
235
- return entity.metadata.title || entity.metadata.name;
236
- }
237
- function createNameColumn() {
238
- return {
239
- title: "Document",
240
- field: "entity.metadata.name",
241
- highlight: true,
242
- render: (row) => /* @__PURE__ */ React.createElement(SubvalueCell, {
243
- value: /* @__PURE__ */ React.createElement(Link, {
244
- to: row.resolved.docsUrl
245
- }, customTitle(row.entity)),
246
- subvalue: row.entity.metadata.description
247
- })
258
+ const rewriteDocLinks = () => {
259
+ return (dom) => {
260
+ const updateDom = (list, attributeName) => {
261
+ Array.from(list).filter((elem) => elem.hasAttribute(attributeName)).forEach((elem) => {
262
+ const elemAttribute = elem.getAttribute(attributeName);
263
+ if (elemAttribute) {
264
+ if (elemAttribute.match(/^https?:\/\//i)) {
265
+ elem.setAttribute("target", "_blank");
266
+ }
267
+ try {
268
+ const normalizedWindowLocation = normalizeUrl(window.location.href);
269
+ elem.setAttribute(attributeName, new URL(elemAttribute, normalizedWindowLocation).toString());
270
+ } catch (_e) {
271
+ elem.replaceWith(elem.textContent || elemAttribute);
272
+ }
273
+ }
274
+ });
275
+ };
276
+ updateDom(Array.from(dom.getElementsByTagName("a")), "href");
277
+ return dom;
248
278
  };
279
+ };
280
+ function normalizeUrl(input) {
281
+ const url = new URL(input);
282
+ if (!url.pathname.endsWith("/") && !url.pathname.endsWith(".html")) {
283
+ url.pathname += "/";
284
+ }
285
+ return url.toString();
249
286
  }
250
- function createOwnerColumn() {
251
- return {
252
- title: "Owner",
253
- field: "resolved.ownedByRelationsTitle",
254
- render: ({ resolved }) => /* @__PURE__ */ React.createElement(EntityRefLinks, {
255
- entityRefs: resolved.ownedByRelations,
256
- defaultKind: "group"
257
- })
287
+
288
+ const addLinkClickListener = ({
289
+ baseUrl,
290
+ onClick
291
+ }) => {
292
+ return (dom) => {
293
+ Array.from(dom.getElementsByTagName("a")).forEach((elem) => {
294
+ elem.addEventListener("click", (e) => {
295
+ const target = elem;
296
+ const href = target.getAttribute("href");
297
+ if (!href)
298
+ return;
299
+ if (href.startsWith(baseUrl) && !elem.hasAttribute("download")) {
300
+ e.preventDefault();
301
+ onClick(e, href);
302
+ }
303
+ });
304
+ });
305
+ return dom;
258
306
  };
259
- }
260
- function createTypeColumn() {
261
- return {
262
- title: "Type",
263
- field: "entity.spec.type"
307
+ };
308
+
309
+ const CopyToClipboardTooltip = withStyles((theme) => ({
310
+ tooltip: {
311
+ fontSize: "inherit",
312
+ color: theme.palette.text.primary,
313
+ margin: 0,
314
+ padding: theme.spacing(0.5),
315
+ backgroundColor: "transparent",
316
+ boxShadow: "none"
317
+ }
318
+ }))(Tooltip);
319
+ const CopyToClipboardIcon = () => /* @__PURE__ */ React.createElement(SvgIcon, null, /* @__PURE__ */ React.createElement("path", {
320
+ d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
321
+ }));
322
+ const CopyToClipboardButton = ({ text }) => {
323
+ const [open, setOpen] = useState(false);
324
+ const handleClick = useCallback(() => {
325
+ navigator.clipboard.writeText(text);
326
+ setOpen(true);
327
+ }, [text]);
328
+ const handleClose = useCallback(() => {
329
+ setOpen(false);
330
+ }, [setOpen]);
331
+ return /* @__PURE__ */ React.createElement(CopyToClipboardTooltip, {
332
+ title: "Copied to clipboard",
333
+ placement: "left",
334
+ open,
335
+ onClose: handleClose,
336
+ leaveDelay: 1e3
337
+ }, /* @__PURE__ */ React.createElement("button", {
338
+ className: "md-clipboard md-icon",
339
+ onClick: handleClick
340
+ }, /* @__PURE__ */ React.createElement(CopyToClipboardIcon, null)));
341
+ };
342
+ const copyToClipboard = (theme) => {
343
+ return (dom) => {
344
+ var _a;
345
+ const codes = dom.querySelectorAll("pre > code");
346
+ for (const code of codes) {
347
+ const text = code.textContent || "";
348
+ const container = document.createElement("div");
349
+ (_a = code == null ? void 0 : code.parentElement) == null ? void 0 : _a.prepend(container);
350
+ ReactDOM.render(/* @__PURE__ */ React.createElement(ThemeProvider, {
351
+ theme
352
+ }, /* @__PURE__ */ React.createElement(CopyToClipboardButton, {
353
+ text
354
+ })), container);
355
+ }
356
+ return dom;
264
357
  };
265
- }
358
+ };
266
359
 
267
- var columnFactories = /*#__PURE__*/Object.freeze({
268
- __proto__: null,
269
- createNameColumn: createNameColumn,
270
- createOwnerColumn: createOwnerColumn,
271
- createTypeColumn: createTypeColumn
272
- });
360
+ const removeMkdocsHeader = () => {
361
+ return (dom) => {
362
+ var _a;
363
+ (_a = dom.querySelector(".md-header")) == null ? void 0 : _a.remove();
364
+ return dom;
365
+ };
366
+ };
273
367
 
274
- function toLowerMaybe(str, config) {
275
- return config.getOptionalBoolean("techdocs.legacyUseCaseSensitiveTripletPaths") ? str : str.toLocaleLowerCase("en-US");
276
- }
368
+ const simplifyMkdocsFooter = () => {
369
+ return (dom) => {
370
+ var _a, _b;
371
+ (_a = dom.querySelector(".md-footer .md-copyright")) == null ? void 0 : _a.remove();
372
+ (_b = dom.querySelector(".md-footer-copyright")) == null ? void 0 : _b.remove();
373
+ return dom;
374
+ };
375
+ };
277
376
 
278
- const DocsTable$1 = ({
279
- entities,
280
- title,
281
- loading,
282
- columns,
283
- actions
284
- }) => {
285
- const [, copyToClipboard] = useCopyToClipboard();
286
- const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);
287
- const config = useApi(configApiRef);
288
- if (!entities)
289
- return null;
290
- const documents = entities.map((entity) => {
291
- var _a;
292
- const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY);
293
- return {
294
- entity,
295
- resolved: {
296
- docsUrl: getRouteToReaderPageFor({
297
- namespace: toLowerMaybe((_a = entity.metadata.namespace) != null ? _a : "default", config),
298
- kind: toLowerMaybe(entity.kind, config),
299
- name: toLowerMaybe(entity.metadata.name, config)
300
- }),
301
- ownedByRelations,
302
- ownedByRelationsTitle: ownedByRelations.map((r) => formatEntityRefTitle(r, { defaultKind: "group" })).join(", ")
303
- }
304
- };
305
- });
306
- const defaultColumns = [
307
- createNameColumn(),
308
- createOwnerColumn(),
309
- createTypeColumn()
310
- ];
311
- const defaultActions = [
312
- createCopyDocsUrlAction(copyToClipboard)
313
- ];
314
- return /* @__PURE__ */ React.createElement(React.Fragment, null, loading || documents && documents.length > 0 ? /* @__PURE__ */ React.createElement(Table, {
315
- isLoading: loading,
316
- options: {
317
- paging: true,
318
- pageSize: 20,
319
- search: true,
320
- actionsColumnIndex: -1
321
- },
322
- data: documents,
323
- columns: columns || defaultColumns,
324
- actions: actions || defaultActions,
325
- title: title ? `${title} (${documents.length})` : `All (${documents.length})`
326
- }) : /* @__PURE__ */ React.createElement(EmptyState, {
327
- missing: "data",
328
- title: "No documents to show",
329
- description: "Create your own document. Check out our Getting Started Information",
330
- action: /* @__PURE__ */ React.createElement(Button, {
331
- color: "primary",
332
- to: "https://backstage.io/docs/features/techdocs/getting-started",
333
- variant: "contained"
334
- }, "DOCS")
335
- }));
336
- };
337
-
338
- var DocsTable$2 = /*#__PURE__*/Object.freeze({
339
- __proto__: null,
340
- DocsTable: DocsTable$1
341
- });
342
-
343
- const EntityListDocsTable = ({
344
- columns,
345
- actions
346
- }) => {
347
- var _a, _b;
348
- const { loading, error, entities, filters } = useEntityList();
349
- const { isStarredEntity, toggleStarredEntity } = useStarredEntities();
350
- const [, copyToClipboard] = useCopyToClipboard();
351
- const title = capitalize((_b = (_a = filters.user) == null ? void 0 : _a.value) != null ? _b : "all");
352
- const defaultActions = [
353
- createCopyDocsUrlAction(copyToClipboard),
354
- createStarEntityAction(isStarredEntity, toggleStarredEntity)
355
- ];
356
- if (error) {
357
- return /* @__PURE__ */ React.createElement(WarningPanel, {
358
- severity: "error",
359
- title: "Could not load available documentation."
360
- }, /* @__PURE__ */ React.createElement(CodeSnippet, {
361
- language: "text",
362
- text: error.toString()
363
- }));
364
- }
365
- return /* @__PURE__ */ React.createElement(DocsTable$1, {
366
- title,
367
- entities,
368
- loading,
369
- actions: actions || defaultActions,
370
- columns
371
- });
372
- };
373
- EntityListDocsTable.columns = columnFactories;
374
- EntityListDocsTable.actions = actionFactories;
375
-
376
- const TechDocsPageWrapper = ({ children }) => {
377
- var _a;
378
- const configApi = useApi(configApiRef);
379
- const generatedSubtitle = `Documentation available in ${(_a = configApi.getOptionalString("organization.name")) != null ? _a : "Backstage"}`;
380
- return /* @__PURE__ */ React.createElement(PageWithHeader, {
381
- title: "Documentation",
382
- subtitle: generatedSubtitle,
383
- themeId: "documentation"
384
- }, children);
385
- };
386
-
387
- class TechDocsFilter {
388
- getCatalogFilters() {
389
- return {
390
- "metadata.annotations.backstage.io/techdocs-ref": CATALOG_FILTER_EXISTS
391
- };
392
- }
393
- }
394
- const TechDocsPicker = () => {
395
- const { updateFilters } = useEntityList();
396
- useEffect(() => {
397
- updateFilters({
398
- techdocs: new TechDocsFilter()
399
- });
400
- }, [updateFilters]);
401
- return null;
402
- };
403
-
404
- const DefaultTechDocsHome = ({
405
- initialFilter = "all",
406
- columns,
407
- actions
408
- }) => {
409
- return /* @__PURE__ */ React.createElement(TechDocsPageWrapper, null, /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, {
410
- title: ""
411
- }, /* @__PURE__ */ React.createElement(SupportButton, null, "Discover documentation in your ecosystem.")), /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(FilteredEntityLayout, null, /* @__PURE__ */ React.createElement(FilterContainer, null, /* @__PURE__ */ React.createElement(TechDocsPicker, null), /* @__PURE__ */ React.createElement(UserListPicker, {
412
- initialFilter
413
- }), /* @__PURE__ */ React.createElement(EntityOwnerPicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(EntityListContainer, null, /* @__PURE__ */ React.createElement(EntityListDocsTable, {
414
- actions,
415
- columns
416
- }))))));
417
- };
418
-
419
- const DocsCardGrid$1 = ({
420
- entities
421
- }) => {
422
- const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);
423
- const config = useApi(configApiRef);
424
- if (!entities)
425
- return null;
426
- return /* @__PURE__ */ React.createElement(ItemCardGrid, {
427
- "data-testid": "docs-explore"
428
- }, !(entities == null ? void 0 : entities.length) ? null : entities.map((entity, index) => {
429
- var _a, _b;
430
- return /* @__PURE__ */ React.createElement(Card, {
431
- key: index
432
- }, /* @__PURE__ */ React.createElement(CardMedia, null, /* @__PURE__ */ React.createElement(ItemCardHeader, {
433
- title: (_a = entity.metadata.title) != null ? _a : entity.metadata.name
434
- })), /* @__PURE__ */ React.createElement(CardContent, null, entity.metadata.description), /* @__PURE__ */ React.createElement(CardActions, null, /* @__PURE__ */ React.createElement(Button, {
435
- to: getRouteToReaderPageFor({
436
- namespace: toLowerMaybe((_b = entity.metadata.namespace) != null ? _b : "default", config),
437
- kind: toLowerMaybe(entity.kind, config),
438
- name: toLowerMaybe(entity.metadata.name, config)
439
- }),
440
- color: "primary",
441
- "data-testid": "read_docs"
442
- }, "Read Docs")));
443
- }));
444
- };
445
-
446
- var DocsCardGrid$2 = /*#__PURE__*/Object.freeze({
447
- __proto__: null,
448
- DocsCardGrid: DocsCardGrid$1
449
- });
450
-
451
- const EntityListDocsGrid = () => {
452
- const { loading, error, entities } = useEntityList();
453
- if (error) {
454
- return /* @__PURE__ */ React.createElement(WarningPanel, {
455
- severity: "error",
456
- title: "Could not load available documentation."
457
- }, /* @__PURE__ */ React.createElement(CodeSnippet, {
458
- language: "text",
459
- text: error.toString()
460
- }));
461
- }
462
- if (loading || !entities) {
463
- return /* @__PURE__ */ React.createElement(Progress, null);
464
- }
465
- entities.sort((a, b) => {
466
- var _a, _b;
467
- return ((_a = a.metadata.title) != null ? _a : a.metadata.name).localeCompare((_b = b.metadata.title) != null ? _b : b.metadata.name);
468
- });
469
- return /* @__PURE__ */ React.createElement(DocsCardGrid$1, {
470
- entities
471
- });
472
- };
473
-
474
- const techdocsPlugin = createPlugin({
475
- id: "techdocs",
476
- apis: [
477
- createApiFactory({
478
- api: techdocsStorageApiRef,
479
- deps: {
480
- configApi: configApiRef,
481
- discoveryApi: discoveryApiRef,
482
- identityApi: identityApiRef,
483
- fetchApi: fetchApiRef
484
- },
485
- factory: ({ configApi, discoveryApi, identityApi, fetchApi }) => new TechDocsStorageClient({
486
- configApi,
487
- discoveryApi,
488
- identityApi,
489
- fetchApi
490
- })
491
- }),
492
- createApiFactory({
493
- api: techdocsApiRef,
494
- deps: {
495
- configApi: configApiRef,
496
- discoveryApi: discoveryApiRef,
497
- fetchApi: fetchApiRef
498
- },
499
- factory: ({ configApi, discoveryApi, fetchApi }) => new TechDocsClient({
500
- configApi,
501
- discoveryApi,
502
- fetchApi
503
- })
504
- })
505
- ],
506
- routes: {
507
- root: rootRouteRef,
508
- docRoot: rootDocsRouteRef,
509
- entityContent: rootCatalogDocsRouteRef
510
- }
511
- });
512
- const TechdocsPage = techdocsPlugin.provide(createRoutableExtension({
513
- name: "TechdocsPage",
514
- component: () => Promise.resolve().then(function () { return Router$1; }).then((m) => m.Router),
515
- mountPoint: rootRouteRef
516
- }));
517
- const EntityTechdocsContent = techdocsPlugin.provide(createRoutableExtension({
518
- name: "EntityTechdocsContent",
519
- component: () => Promise.resolve().then(function () { return Router$1; }).then((m) => m.EmbeddedDocsRouter),
520
- mountPoint: rootCatalogDocsRouteRef
521
- }));
522
- const DocsCardGrid = techdocsPlugin.provide(createComponentExtension({
523
- name: "DocsCardGrid",
524
- component: {
525
- lazy: () => Promise.resolve().then(function () { return DocsCardGrid$2; }).then((m) => m.DocsCardGrid)
526
- }
527
- }));
528
- const DocsTable = techdocsPlugin.provide(createComponentExtension({
529
- name: "DocsTable",
530
- component: {
531
- lazy: () => Promise.resolve().then(function () { return DocsTable$2; }).then((m) => m.DocsTable)
532
- }
533
- }));
534
- const TechDocsCustomHome$2 = techdocsPlugin.provide(createRoutableExtension({
535
- name: "TechDocsCustomHome",
536
- component: () => Promise.resolve().then(function () { return TechDocsCustomHome$1; }).then((m) => m.TechDocsCustomHome),
537
- mountPoint: rootRouteRef
538
- }));
539
- const TechDocsIndexPage$2 = techdocsPlugin.provide(createRoutableExtension({
540
- name: "TechDocsIndexPage",
541
- component: () => Promise.resolve().then(function () { return TechDocsIndexPage$1; }).then((m) => m.TechDocsIndexPage),
542
- mountPoint: rootRouteRef
543
- }));
544
- const TechDocsReaderPage = techdocsPlugin.provide(createRoutableExtension({
545
- name: "TechDocsReaderPage",
546
- component: () => Promise.resolve().then(function () { return TechDocsPage$1; }).then((m) => m.TechDocsPage),
547
- mountPoint: rootDocsRouteRef
548
- }));
549
-
550
- const isSvgNeedingInlining = (attrName, attrVal, apiOrigin) => {
551
- const isSrcToSvg = attrName === "src" && attrVal.endsWith(".svg");
552
- const isRelativeUrl = !attrVal.match(/^([a-z]*:)?\/\//i);
553
- const pointsToOurBackend = attrVal.startsWith(apiOrigin);
554
- return isSrcToSvg && (isRelativeUrl || pointsToOurBackend);
555
- };
556
- const addBaseUrl = ({
557
- techdocsStorageApi,
558
- entityId,
559
- path
560
- }) => {
561
- return async (dom) => {
562
- const apiOrigin = await techdocsStorageApi.getApiOrigin();
563
- const updateDom = async (list, attributeName) => {
564
- for (const elem of list) {
565
- if (elem.hasAttribute(attributeName)) {
566
- const elemAttribute = elem.getAttribute(attributeName);
567
- if (!elemAttribute)
568
- return;
569
- const newValue = await techdocsStorageApi.getBaseUrl(elemAttribute, entityId, path);
570
- if (isSvgNeedingInlining(attributeName, elemAttribute, apiOrigin)) {
571
- try {
572
- const svg = await fetch(newValue, { credentials: "include" });
573
- const svgContent = await svg.text();
574
- elem.setAttribute(attributeName, `data:image/svg+xml;base64,${btoa(svgContent)}`);
575
- } catch (e) {
576
- elem.setAttribute("alt", `Error: ${elemAttribute}`);
577
- }
578
- } else {
579
- elem.setAttribute(attributeName, newValue);
580
- }
581
- }
582
- }
583
- };
584
- await Promise.all([
585
- updateDom(dom.querySelectorAll("img"), "src"),
586
- updateDom(dom.querySelectorAll("script"), "src"),
587
- updateDom(dom.querySelectorAll("source"), "src"),
588
- updateDom(dom.querySelectorAll("link"), "href"),
589
- updateDom(dom.querySelectorAll("a[download]"), "href")
590
- ]);
591
- return dom;
592
- };
593
- };
594
-
595
- const addGitFeedbackLink = (scmIntegrationsApi) => {
596
- return (dom) => {
597
- const sourceAnchor = dom.querySelector('[title="Edit this page"]');
598
- if (!sourceAnchor || !sourceAnchor.href) {
599
- return dom;
600
- }
601
- const sourceURL = new URL(sourceAnchor.href);
602
- const integration = scmIntegrationsApi.byUrl(sourceURL);
603
- if ((integration == null ? void 0 : integration.type) !== "github" && (integration == null ? void 0 : integration.type) !== "gitlab") {
604
- return dom;
605
- }
606
- const title = dom.querySelector("article>h1").childNodes[0].textContent;
607
- const issueTitle = encodeURIComponent(`Documentation Feedback: ${title}`);
608
- const issueDesc = encodeURIComponent(`Page source:
609
- ${sourceAnchor.href}
610
-
611
- Feedback:`);
612
- const gitUrl = (integration == null ? void 0 : integration.type) === "github" ? replaceGitHubUrlType(sourceURL.href, "blob") : sourceURL.href;
613
- const gitInfo = parseGitUrl(gitUrl);
614
- const repoPath = `/${gitInfo.organization}/${gitInfo.name}`;
615
- const feedbackLink = sourceAnchor.cloneNode();
616
- switch (integration == null ? void 0 : integration.type) {
617
- case "gitlab":
618
- feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?issue[title]=${issueTitle}&issue[description]=${issueDesc}`;
619
- break;
620
- case "github":
621
- feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?title=${issueTitle}&body=${issueDesc}`;
622
- break;
623
- default:
624
- return dom;
625
- }
626
- ReactDOM.render(React.createElement(FeedbackOutlinedIcon), feedbackLink);
627
- feedbackLink.style.paddingLeft = "5px";
628
- feedbackLink.title = "Leave feedback for this page";
629
- feedbackLink.id = "git-feedback-link";
630
- sourceAnchor == null ? void 0 : sourceAnchor.insertAdjacentElement("beforebegin", feedbackLink);
631
- return dom;
632
- };
633
- };
634
-
635
- const rewriteDocLinks = () => {
636
- return (dom) => {
637
- const updateDom = (list, attributeName) => {
638
- Array.from(list).filter((elem) => elem.hasAttribute(attributeName)).forEach((elem) => {
639
- const elemAttribute = elem.getAttribute(attributeName);
640
- if (elemAttribute) {
641
- if (elemAttribute.match(/^https?:\/\//i)) {
642
- elem.setAttribute("target", "_blank");
643
- }
644
- try {
645
- const normalizedWindowLocation = normalizeUrl(window.location.href);
646
- elem.setAttribute(attributeName, new URL(elemAttribute, normalizedWindowLocation).toString());
647
- } catch (_e) {
648
- elem.replaceWith(elem.textContent || elemAttribute);
649
- }
650
- }
651
- });
652
- };
653
- updateDom(Array.from(dom.getElementsByTagName("a")), "href");
654
- return dom;
655
- };
656
- };
657
- function normalizeUrl(input) {
658
- const url = new URL(input);
659
- if (!url.pathname.endsWith("/") && !url.pathname.endsWith(".html")) {
660
- url.pathname += "/";
661
- }
662
- return url.toString();
663
- }
664
-
665
- const addLinkClickListener = ({
666
- baseUrl,
667
- onClick
668
- }) => {
669
- return (dom) => {
670
- Array.from(dom.getElementsByTagName("a")).forEach((elem) => {
671
- elem.addEventListener("click", (e) => {
672
- const target = elem;
673
- const href = target.getAttribute("href");
674
- if (!href)
675
- return;
676
- if (href.startsWith(baseUrl) && !elem.hasAttribute("download")) {
677
- e.preventDefault();
678
- onClick(e, href);
679
- }
680
- });
681
- });
682
- return dom;
683
- };
684
- };
685
-
686
- const copyToClipboard = () => {
687
- return (dom) => {
688
- Array.from(dom.querySelectorAll("pre > code")).forEach((codeElem) => {
689
- var _a;
690
- const button = document.createElement("button");
691
- const toBeCopied = codeElem.textContent || "";
692
- button.className = "md-clipboard md-icon";
693
- button.title = "Copy to clipboard";
694
- button.innerHTML = '<svg viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg>';
695
- button.addEventListener("click", () => navigator.clipboard.writeText(toBeCopied));
696
- (_a = codeElem == null ? void 0 : codeElem.parentElement) == null ? void 0 : _a.prepend(button);
697
- });
698
- return dom;
699
- };
700
- };
701
-
702
- const removeMkdocsHeader = () => {
703
- return (dom) => {
704
- var _a;
705
- (_a = dom.querySelector(".md-header")) == null ? void 0 : _a.remove();
706
- return dom;
707
- };
708
- };
709
-
710
- const simplifyMkdocsFooter = () => {
711
- return (dom) => {
712
- var _a;
713
- (_a = dom.querySelector(".md-footer .md-copyright")) == null ? void 0 : _a.remove();
714
- return dom;
715
- };
716
- };
717
-
718
- const onCssReady = ({
719
- docStorageUrl,
720
- onLoading,
721
- onLoaded
377
+ const onCssReady = ({
378
+ docStorageUrl,
379
+ onLoading,
380
+ onLoaded
722
381
  }) => {
723
382
  return (dom) => {
724
383
  const cssPages = Array.from(dom.querySelectorAll('head > link[rel="stylesheet"]')).filter((elem) => {
@@ -830,20 +489,64 @@ const transform = async (html, transformers) => {
830
489
  return dom;
831
490
  };
832
491
 
492
+ const useStyles$3 = makeStyles({
493
+ flexContainer: {
494
+ flexWrap: "wrap"
495
+ },
496
+ itemText: {
497
+ width: "100%",
498
+ marginBottom: "1rem"
499
+ }
500
+ });
501
+ const TechDocsSearchResultListItem = (props) => {
502
+ const {
503
+ result,
504
+ lineClamp = 5,
505
+ asListItem = true,
506
+ asLink = true,
507
+ title
508
+ } = props;
509
+ const classes = useStyles$3();
510
+ const TextItem = () => {
511
+ var _a;
512
+ return /* @__PURE__ */ React.createElement(ListItemText, {
513
+ className: classes.itemText,
514
+ primaryTypographyProps: { variant: "h6" },
515
+ primary: title ? title : `${result.title} | ${(_a = result.entityTitle) != null ? _a : result.name} docs`,
516
+ secondary: /* @__PURE__ */ React.createElement(TextTruncate, {
517
+ line: lineClamp,
518
+ truncateText: "\u2026",
519
+ text: result.text,
520
+ element: "span"
521
+ })
522
+ });
523
+ };
524
+ const LinkWrapper = ({ children }) => asLink ? /* @__PURE__ */ React.createElement(Link, {
525
+ to: result.location
526
+ }, children) : /* @__PURE__ */ React.createElement(React.Fragment, null, children);
527
+ const ListItemWrapper = ({ children }) => asListItem ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ListItem, {
528
+ alignItems: "flex-start",
529
+ className: classes.flexContainer
530
+ }, children), /* @__PURE__ */ React.createElement(Divider, {
531
+ component: "li"
532
+ })) : /* @__PURE__ */ React.createElement(React.Fragment, null, children);
533
+ return /* @__PURE__ */ React.createElement(LinkWrapper, null, /* @__PURE__ */ React.createElement(ListItemWrapper, null, /* @__PURE__ */ React.createElement(TextItem, null)));
534
+ };
535
+ const DocsResultListItem = TechDocsSearchResultListItem;
536
+
833
537
  const useStyles$2 = makeStyles({
834
538
  root: {
835
539
  width: "100%"
836
540
  }
837
541
  });
838
- const TechDocsSearchBar = ({
839
- entityId,
840
- debounceTime = 150
841
- }) => {
542
+ const TechDocsSearchBar = (props) => {
543
+ const { entityId, debounceTime = 150 } = props;
842
544
  const [open, setOpen] = useState(false);
843
545
  const navigate = useNavigate();
844
546
  const {
845
547
  term,
846
548
  setTerm,
549
+ setFilters,
847
550
  result: { loading, value: searchVal }
848
551
  } = useSearch();
849
552
  const classes = useStyles$2();
@@ -860,6 +563,17 @@ const TechDocsSearchBar = ({
860
563
  }, [loading, searchVal]);
861
564
  const [value, setValue] = useState(term);
862
565
  useDebounce(() => setTerm(value), debounceTime, [value]);
566
+ const { kind, name, namespace } = entityId;
567
+ useEffect(() => {
568
+ setFilters((prevFilters) => {
569
+ return {
570
+ ...prevFilters,
571
+ kind,
572
+ namespace,
573
+ name
574
+ };
575
+ });
576
+ }, [kind, namespace, name, setFilters]);
863
577
  const handleQuery = (e) => {
864
578
  if (!open) {
865
579
  setOpen(true);
@@ -892,7 +606,7 @@ const TechDocsSearchBar = ({
892
606
  noOptionsText: "No results found",
893
607
  value: null,
894
608
  options,
895
- renderOption: ({ document }) => /* @__PURE__ */ React.createElement(DocsResultListItem, {
609
+ renderOption: ({ document }) => /* @__PURE__ */ React.createElement(TechDocsSearchResultListItem, {
896
610
  result: document,
897
611
  lineClamp: 3,
898
612
  asListItem: false,
@@ -991,7 +705,7 @@ const TechDocsBuildLogsDrawerContent = ({
991
705
  const TechDocsBuildLogs = ({ buildLog }) => {
992
706
  const classes = useDrawerStyles();
993
707
  const [open, setOpen] = useState(false);
994
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Button$1, {
708
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Button, {
995
709
  color: "inherit",
996
710
  onClick: () => setOpen(true)
997
711
  }, "Show Build Logs"), /* @__PURE__ */ React.createElement(Drawer, {
@@ -1068,7 +782,7 @@ const TechDocsStateIndicator = () => {
1068
782
  StateAlert = /* @__PURE__ */ React.createElement(Alert, {
1069
783
  variant: "outlined",
1070
784
  severity: "success",
1071
- action: /* @__PURE__ */ React.createElement(Button$1, {
785
+ action: /* @__PURE__ */ React.createElement(Button, {
1072
786
  color: "inherit",
1073
787
  onClick: () => contentReload()
1074
788
  }, "Refresh"),
@@ -1253,8 +967,8 @@ const useStyles = makeStyles((theme) => ({
1253
967
  marginBottom: theme.spacing(1),
1254
968
  marginLeft: "calc(16rem + 1.2rem)",
1255
969
  "@media screen and (max-width: 76.1875em)": {
1256
- marginLeft: "calc(10rem + 0.8rem)",
1257
- maxWidth: "calc(100% - 10rem - 1.6rem)"
970
+ marginLeft: "0",
971
+ maxWidth: "100%"
1258
972
  }
1259
973
  }
1260
974
  }));
@@ -1293,9 +1007,16 @@ const useTechDocsReaderDom = (entityRef) => {
1293
1007
  if (!dom || !sidebars)
1294
1008
  return;
1295
1009
  const mdTabs = dom.querySelector(".md-container > .md-tabs");
1010
+ const sidebarsCollapsed = window.matchMedia("screen and (max-width: 76.1875em)").matches;
1011
+ const newTop = Math.max(dom.getBoundingClientRect().top, 0);
1296
1012
  sidebars.forEach((sidebar) => {
1297
- const newTop = Math.max(dom.getBoundingClientRect().top, 0);
1298
- sidebar.style.top = mdTabs ? `${newTop + mdTabs.getBoundingClientRect().height}px` : `${newTop}px`;
1013
+ if (sidebarsCollapsed) {
1014
+ sidebar.style.top = "0px";
1015
+ } else if (mdTabs) {
1016
+ sidebar.style.top = `${newTop + mdTabs.getBoundingClientRect().height}px`;
1017
+ } else {
1018
+ sidebar.style.top = `${newTop}px`;
1019
+ }
1299
1020
  });
1300
1021
  }, [dom, sidebars]);
1301
1022
  useEffect(() => {
@@ -1334,6 +1055,7 @@ const useTechDocsReaderDom = (entityRef) => {
1334
1055
  path: contentPath
1335
1056
  }),
1336
1057
  rewriteDocLinks(),
1058
+ addSidebarToggle(),
1337
1059
  removeMkdocsHeader(),
1338
1060
  simplifyMkdocsFooter(),
1339
1061
  addGitFeedbackLink(scmIntegrationsApi),
@@ -1402,7 +1124,7 @@ const useTechDocsReaderDom = (entityRef) => {
1402
1124
  --md-source-version-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M2.5 7.775V2.75a.25.25 0 0 1 .25-.25h5.025a.25.25 0 0 1 .177.073l6.25 6.25a.25.25 0 0 1 0 .354l-5.025 5.025a.25.25 0 0 1-.354 0l-6.25-6.25a.25.25 0 0 1-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 0 1 0 2.474l-5.026 5.026a1.75 1.75 0 0 1-2.474 0l-6.25-6.25A1.75 1.75 0 0 1 1 7.775zM6 5a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/></svg>');
1403
1125
  --md-version-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.--><path d="m310.6 246.6-127.1 128c-7.1 6.3-15.3 9.4-23.5 9.4s-16.38-3.125-22.63-9.375l-127.1-128C.224 237.5-2.516 223.7 2.438 211.8S19.07 192 32 192h255.1c12.94 0 24.62 7.781 29.58 19.75s3.12 25.75-6.08 34.85z"/></svg>');
1404
1126
  }
1405
-
1127
+
1406
1128
  :host > * {
1407
1129
  /* CODE */
1408
1130
  --md-code-fg-color: ${theme.palette.text.primary};
@@ -1467,9 +1189,20 @@ const useTechDocsReaderDom = (entityRef) => {
1467
1189
  .md-nav {
1468
1190
  font-size: calc(var(--md-typeset-font-size) * 0.9);
1469
1191
  }
1192
+ .md-nav__link {
1193
+ display: flex;
1194
+ align-items: center;
1195
+ justify-content: space-between;
1196
+ }
1470
1197
  .md-nav__icon {
1471
- width: auto !important;
1472
- height: auto !important;
1198
+ height: 20px !important;
1199
+ width: 20px !important;
1200
+ margin-left:${theme.spacing(1)}px;
1201
+ }
1202
+ .md-nav__icon svg {
1203
+ margin: 0;
1204
+ width: 20px !important;
1205
+ height: 20px !important;
1473
1206
  }
1474
1207
  .md-nav__icon:after {
1475
1208
  width: 20px !important;
@@ -1479,22 +1212,25 @@ const useTechDocsReaderDom = (entityRef) => {
1479
1212
  .md-main__inner {
1480
1213
  margin-top: 0;
1481
1214
  }
1482
-
1215
+
1483
1216
  .md-sidebar {
1217
+ height: calc(100% - 100px);
1484
1218
  position: fixed;
1485
- bottom: 100px;
1486
1219
  width: 16rem;
1487
1220
  }
1221
+ .md-sidebar .md-sidebar__scrollwrap {
1222
+ max-height: calc(100% - 100px);
1223
+ }
1488
1224
  .md-sidebar--secondary {
1489
1225
  right: ${theme.spacing(3)}px;
1490
1226
  }
1491
-
1227
+
1492
1228
  .md-content {
1493
1229
  max-width: calc(100% - 16rem * 2);
1494
1230
  margin-left: 16rem;
1495
1231
  margin-bottom: 50px;
1496
1232
  }
1497
-
1233
+
1498
1234
  .md-footer {
1499
1235
  position: fixed;
1500
1236
  bottom: 0px;
@@ -1509,7 +1245,7 @@ const useTechDocsReaderDom = (entityRef) => {
1509
1245
  .md-dialog {
1510
1246
  background-color: unset;
1511
1247
  }
1512
-
1248
+
1513
1249
  @media screen and (max-width: 76.1875em) {
1514
1250
  .md-nav {
1515
1251
  transition: none !important;
@@ -1556,22 +1292,36 @@ const useTechDocsReaderDom = (entityRef) => {
1556
1292
  display: none;
1557
1293
  }
1558
1294
 
1295
+ .md-sidebar {
1296
+ height: 100%;
1297
+ }
1559
1298
  .md-sidebar--primary {
1560
- width: 10rem !important;
1561
- left: ${isPinned ? "242px" : "72px"} !important;
1299
+ width: 12.1rem !important;
1300
+ z-index: 200;
1301
+ left: ${isPinned ? "calc(-12.1rem + 242px)" : "calc(-12.1rem + 72px)"} !important;
1562
1302
  }
1563
1303
  .md-sidebar--secondary:not([hidden]) {
1564
1304
  display: none;
1565
1305
  }
1566
1306
 
1567
1307
  .md-content {
1568
- max-width: calc(100% - 10rem);
1569
- margin-left: 10rem;
1308
+ max-width: 100%;
1309
+ margin-left: 0;
1310
+ }
1311
+
1312
+ .md-header__button {
1313
+ margin: 0.4rem 0;
1314
+ margin-left: 0.4rem;
1315
+ padding: 0;
1316
+ }
1317
+
1318
+ .md-overlay {
1319
+ left: 0;
1570
1320
  }
1571
1321
 
1572
1322
  .md-footer {
1573
1323
  position: static;
1574
- padding-left: 10rem;
1324
+ padding-left: 0;
1575
1325
  }
1576
1326
  .md-footer-nav__link {
1577
1327
  /* footer links begin to overlap at small sizes without setting width */
@@ -1581,13 +1331,14 @@ const useTechDocsReaderDom = (entityRef) => {
1581
1331
 
1582
1332
  @media screen and (max-width: 600px) {
1583
1333
  .md-sidebar--primary {
1584
- left: 1rem !important;
1334
+ left: -12.1rem !important;
1335
+ width: 12.1rem;
1585
1336
  }
1586
1337
  }
1587
1338
  `
1588
1339
  }),
1589
1340
  injectCss({
1590
- css: `
1341
+ css: `
1591
1342
  .md-typeset {
1592
1343
  font-size: var(--md-typeset-font-size);
1593
1344
  }
@@ -1619,11 +1370,11 @@ const useTechDocsReaderDom = (entityRef) => {
1619
1370
  .md-typeset .md-content__button {
1620
1371
  color: var(--md-default-fg-color);
1621
1372
  }
1622
-
1373
+
1623
1374
  .md-typeset hr {
1624
1375
  border-bottom: 0.05rem dotted ${theme.palette.divider};
1625
1376
  }
1626
-
1377
+
1627
1378
  .md-typeset details {
1628
1379
  font-size: var(--md-typeset-font-size) !important;
1629
1380
  }
@@ -1640,7 +1391,7 @@ const useTechDocsReaderDom = (entityRef) => {
1640
1391
  .md-typeset details[open] > summary:after {
1641
1392
  transform: rotate(90deg) translateX(-50%) !important;
1642
1393
  }
1643
-
1394
+
1644
1395
  .md-typeset blockquote {
1645
1396
  color: var(--md-default-fg-color--light);
1646
1397
  border-left: 0.2rem solid var(--md-default-fg-color--light);
@@ -1684,13 +1435,13 @@ const useTechDocsReaderDom = (entityRef) => {
1684
1435
  .highlight .md-clipboard:after {
1685
1436
  content: unset;
1686
1437
  }
1687
-
1438
+
1688
1439
  .highlight .nx {
1689
1440
  color: ${isDarkTheme ? "#ff53a3" : "#ec407a"};
1690
1441
  }
1691
1442
 
1692
1443
  /* CODE HILITE */
1693
- .codehilite .gd {
1444
+ .codehilite .gd {
1694
1445
  background-color: ${isDarkTheme ? "rgba(248,81,73,0.65)" : "#fdd"};
1695
1446
  }
1696
1447
 
@@ -1760,7 +1511,7 @@ const useTechDocsReaderDom = (entityRef) => {
1760
1511
  ]);
1761
1512
  const postRender = useCallback(async (transformedElement) => transform(transformedElement, [
1762
1513
  scrollIntoAnchor(),
1763
- copyToClipboard(),
1514
+ copyToClipboard(theme),
1764
1515
  addLinkClickListener({
1765
1516
  baseUrl: window.location.origin,
1766
1517
  onClick: (event, url) => {
@@ -1796,7 +1547,7 @@ const useTechDocsReaderDom = (entityRef) => {
1796
1547
  setSidebars(Array.from(renderedElement.querySelectorAll(".md-sidebar")));
1797
1548
  }
1798
1549
  })
1799
- ]), [navigate, techdocsStorageApi]);
1550
+ ]), [theme, navigate, techdocsStorageApi]);
1800
1551
  useEffect(() => {
1801
1552
  if (!rawPage)
1802
1553
  return () => {
@@ -1852,25 +1603,31 @@ const TheReader = ({
1852
1603
  ref: shadowDomRef
1853
1604
  }));
1854
1605
  };
1855
- const Reader = ({
1856
- entityRef,
1857
- onReady = () => {
1858
- },
1859
- withSearch = true
1860
- }) => /* @__PURE__ */ React.createElement(TechDocsReaderProvider, {
1861
- entityRef
1862
- }, /* @__PURE__ */ React.createElement(TheReader, {
1863
- entityRef,
1864
- onReady,
1865
- withSearch
1866
- }));
1606
+ const Reader = (props) => {
1607
+ const { entityRef, onReady = () => {
1608
+ }, withSearch = true } = props;
1609
+ return /* @__PURE__ */ React.createElement(TechDocsReaderProvider, {
1610
+ entityRef
1611
+ }, /* @__PURE__ */ React.createElement(TheReader, {
1612
+ entityRef,
1613
+ onReady,
1614
+ withSearch
1615
+ }));
1616
+ };
1867
1617
 
1868
- const TechDocsPageHeader = ({
1869
- entityRef,
1870
- entityMetadata,
1871
- techDocsMetadata,
1872
- children
1873
- }) => {
1618
+ const rootRouteRef = createRouteRef({
1619
+ id: "techdocs:index-page"
1620
+ });
1621
+ const rootDocsRouteRef = createRouteRef({
1622
+ id: "techdocs:reader-page",
1623
+ params: ["namespace", "kind", "name"]
1624
+ });
1625
+ const rootCatalogDocsRouteRef = createRouteRef({
1626
+ id: "techdocs:catalog-reader-view"
1627
+ });
1628
+
1629
+ const TechDocsReaderPageHeader = (props) => {
1630
+ const { entityRef, entityMetadata, techDocsMetadata, children } = props;
1874
1631
  const { name } = entityRef;
1875
1632
  const { site_name: siteName, site_description: siteDescription } = techDocsMetadata || {};
1876
1633
  const { locationMetadata, spec } = entityMetadata || {};
@@ -1912,6 +1669,7 @@ const TechDocsPageHeader = ({
1912
1669
  typeLink: docsRootLink
1913
1670
  }, labels, children);
1914
1671
  };
1672
+ const TechDocsPageHeader = TechDocsReaderPageHeader;
1915
1673
 
1916
1674
  const LegacyTechDocsPage = () => {
1917
1675
  const [documentReady, setDocumentReady] = useState(false);
@@ -1936,7 +1694,7 @@ const LegacyTechDocsPage = () => {
1936
1694
  }
1937
1695
  return /* @__PURE__ */ React.createElement(Page, {
1938
1696
  themeId: "documentation"
1939
- }, /* @__PURE__ */ React.createElement(TechDocsPageHeader, {
1697
+ }, /* @__PURE__ */ React.createElement(TechDocsReaderPageHeader, {
1940
1698
  techDocsMetadata: techdocsMetadataValue,
1941
1699
  entityMetadata: entityMetadataValue,
1942
1700
  entityRef: {
@@ -1956,187 +1714,360 @@ const LegacyTechDocsPage = () => {
1956
1714
  })));
1957
1715
  };
1958
1716
 
1959
- const TechDocsPage = ({ children }) => {
1960
- const { NotFoundErrorPage } = useApp().getComponents();
1961
- const outlet = useOutlet();
1962
- const [documentReady, setDocumentReady] = useState(false);
1963
- const { namespace, kind, name } = useParams();
1964
- const techdocsApi = useApi(techdocsApiRef);
1965
- const { value: techdocsMetadataValue } = useAsync(() => {
1966
- if (documentReady) {
1967
- return techdocsApi.getTechDocsMetadata({ kind, namespace, name });
1968
- }
1969
- return Promise.resolve(void 0);
1970
- }, [kind, namespace, name, techdocsApi, documentReady]);
1971
- const { value: entityMetadataValue, error: entityMetadataError } = useAsync(() => {
1972
- return techdocsApi.getEntityMetadata({ kind, namespace, name });
1973
- }, [kind, namespace, name, techdocsApi]);
1974
- const onReady = useCallback(() => {
1975
- setDocumentReady(true);
1976
- }, [setDocumentReady]);
1977
- if (entityMetadataError)
1978
- return /* @__PURE__ */ React.createElement(NotFoundErrorPage, null);
1979
- if (!children)
1980
- return outlet || /* @__PURE__ */ React.createElement(LegacyTechDocsPage, null);
1981
- return /* @__PURE__ */ React.createElement(Page, {
1982
- themeId: "documentation"
1983
- }, children instanceof Function ? children({
1984
- techdocsMetadataValue,
1985
- entityMetadataValue,
1986
- entityRef: { kind, namespace, name },
1987
- onReady
1988
- }) : children);
1717
+ const TechDocsReaderPage$1 = (props) => {
1718
+ const { children } = props;
1719
+ const { NotFoundErrorPage } = useApp().getComponents();
1720
+ const outlet = useOutlet();
1721
+ const [documentReady, setDocumentReady] = useState(false);
1722
+ const { namespace, kind, name } = useParams();
1723
+ const techdocsApi = useApi(techdocsApiRef);
1724
+ const { value: techdocsMetadataValue } = useAsync(() => {
1725
+ if (documentReady) {
1726
+ return techdocsApi.getTechDocsMetadata({ kind, namespace, name });
1727
+ }
1728
+ return Promise.resolve(void 0);
1729
+ }, [kind, namespace, name, techdocsApi, documentReady]);
1730
+ const { value: entityMetadataValue, error: entityMetadataError } = useAsync(() => {
1731
+ return techdocsApi.getEntityMetadata({ kind, namespace, name });
1732
+ }, [kind, namespace, name, techdocsApi]);
1733
+ const onReady = useCallback(() => {
1734
+ setDocumentReady(true);
1735
+ }, [setDocumentReady]);
1736
+ if (entityMetadataError)
1737
+ return /* @__PURE__ */ React.createElement(NotFoundErrorPage, null);
1738
+ if (!children)
1739
+ return outlet || /* @__PURE__ */ React.createElement(LegacyTechDocsPage, null);
1740
+ return /* @__PURE__ */ React.createElement(Page, {
1741
+ themeId: "documentation"
1742
+ }, children instanceof Function ? children({
1743
+ techdocsMetadataValue,
1744
+ entityMetadataValue,
1745
+ entityRef: { kind, namespace, name },
1746
+ onReady
1747
+ }) : children);
1748
+ };
1749
+ const TechDocsPage = TechDocsReaderPage$1;
1750
+
1751
+ var TechDocsReaderPage$2 = /*#__PURE__*/Object.freeze({
1752
+ __proto__: null,
1753
+ TechDocsReaderPage: TechDocsReaderPage$1,
1754
+ TechDocsPage: TechDocsPage
1755
+ });
1756
+
1757
+ function toLowerMaybe(str, config) {
1758
+ return config.getOptionalBoolean("techdocs.legacyUseCaseSensitiveTripletPaths") ? str : str.toLocaleLowerCase("en-US");
1759
+ }
1760
+
1761
+ const DocsCardGrid = (props) => {
1762
+ const { entities } = props;
1763
+ const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);
1764
+ const config = useApi(configApiRef);
1765
+ if (!entities)
1766
+ return null;
1767
+ return /* @__PURE__ */ React.createElement(ItemCardGrid, {
1768
+ "data-testid": "docs-explore"
1769
+ }, !(entities == null ? void 0 : entities.length) ? null : entities.map((entity, index) => {
1770
+ var _a, _b;
1771
+ return /* @__PURE__ */ React.createElement(Card, {
1772
+ key: index
1773
+ }, /* @__PURE__ */ React.createElement(CardMedia, null, /* @__PURE__ */ React.createElement(ItemCardHeader, {
1774
+ title: (_a = entity.metadata.title) != null ? _a : entity.metadata.name
1775
+ })), /* @__PURE__ */ React.createElement(CardContent, null, entity.metadata.description), /* @__PURE__ */ React.createElement(CardActions, null, /* @__PURE__ */ React.createElement(Button$1, {
1776
+ to: getRouteToReaderPageFor({
1777
+ namespace: toLowerMaybe((_b = entity.metadata.namespace) != null ? _b : "default", config),
1778
+ kind: toLowerMaybe(entity.kind, config),
1779
+ name: toLowerMaybe(entity.metadata.name, config)
1780
+ }),
1781
+ color: "primary",
1782
+ "data-testid": "read_docs"
1783
+ }, "Read Docs")));
1784
+ }));
1785
+ };
1786
+
1787
+ const EntityListDocsGrid = () => {
1788
+ const { loading, error, entities } = useEntityList();
1789
+ if (error) {
1790
+ return /* @__PURE__ */ React.createElement(WarningPanel, {
1791
+ severity: "error",
1792
+ title: "Could not load available documentation."
1793
+ }, /* @__PURE__ */ React.createElement(CodeSnippet, {
1794
+ language: "text",
1795
+ text: error.toString()
1796
+ }));
1797
+ }
1798
+ if (loading || !entities) {
1799
+ return /* @__PURE__ */ React.createElement(Progress, null);
1800
+ }
1801
+ entities.sort((a, b) => {
1802
+ var _a, _b;
1803
+ return ((_a = a.metadata.title) != null ? _a : a.metadata.name).localeCompare((_b = b.metadata.title) != null ? _b : b.metadata.name);
1804
+ });
1805
+ return /* @__PURE__ */ React.createElement(DocsCardGrid, {
1806
+ entities
1807
+ });
1808
+ };
1809
+
1810
+ const YellowStar = withStyles$1({
1811
+ root: {
1812
+ color: "#f3ba37"
1813
+ }
1814
+ })(Star);
1815
+ const actionFactories = {
1816
+ createCopyDocsUrlAction(copyToClipboard) {
1817
+ return (row) => {
1818
+ return {
1819
+ icon: () => /* @__PURE__ */ React.createElement(ShareIcon, {
1820
+ fontSize: "small"
1821
+ }),
1822
+ tooltip: "Click to copy documentation link to clipboard",
1823
+ onClick: () => copyToClipboard(`${window.location.origin}${row.resolved.docsUrl}`)
1824
+ };
1825
+ };
1826
+ },
1827
+ createStarEntityAction(isStarredEntity, toggleStarredEntity) {
1828
+ return ({ entity }) => {
1829
+ const isStarred = isStarredEntity(entity);
1830
+ return {
1831
+ cellStyle: { paddingLeft: "1em" },
1832
+ icon: () => isStarred ? /* @__PURE__ */ React.createElement(YellowStar, null) : /* @__PURE__ */ React.createElement(StarBorder, null),
1833
+ tooltip: isStarred ? "Remove from favorites" : "Add to favorites",
1834
+ onClick: () => toggleStarredEntity(entity)
1835
+ };
1836
+ };
1837
+ }
1989
1838
  };
1990
1839
 
1991
- var TechDocsPage$1 = /*#__PURE__*/Object.freeze({
1992
- __proto__: null,
1993
- TechDocsPage: TechDocsPage
1994
- });
1995
-
1996
- const panels = {
1997
- DocsTable: DocsTable$1,
1998
- DocsCardGrid: DocsCardGrid$1
1840
+ function customTitle(entity) {
1841
+ return entity.metadata.title || entity.metadata.name;
1842
+ }
1843
+ const columnFactories = {
1844
+ createNameColumn() {
1845
+ return {
1846
+ title: "Document",
1847
+ field: "entity.metadata.name",
1848
+ highlight: true,
1849
+ render: (row) => /* @__PURE__ */ React.createElement(SubvalueCell, {
1850
+ value: /* @__PURE__ */ React.createElement(Link, {
1851
+ to: row.resolved.docsUrl
1852
+ }, customTitle(row.entity)),
1853
+ subvalue: row.entity.metadata.description
1854
+ })
1855
+ };
1856
+ },
1857
+ createOwnerColumn() {
1858
+ return {
1859
+ title: "Owner",
1860
+ field: "resolved.ownedByRelationsTitle",
1861
+ render: ({ resolved }) => /* @__PURE__ */ React.createElement(EntityRefLinks, {
1862
+ entityRefs: resolved.ownedByRelations,
1863
+ defaultKind: "group"
1864
+ })
1865
+ };
1866
+ },
1867
+ createTypeColumn() {
1868
+ return {
1869
+ title: "Type",
1870
+ field: "entity.spec.type"
1871
+ };
1872
+ }
1999
1873
  };
2000
- const CustomPanel = ({
2001
- config,
2002
- entities,
2003
- index
2004
- }) => {
2005
- const useStyles = makeStyles({
2006
- panelContainer: {
2007
- marginBottom: "2rem",
2008
- ...config.panelCSS ? config.panelCSS : {}
2009
- }
2010
- });
2011
- const classes = useStyles();
2012
- const { value: user } = useOwnUser();
2013
- const Panel = panels[config.panelType];
2014
- const shownEntities = entities.filter((entity) => {
2015
- if (config.filterPredicate === "ownedByUser") {
2016
- if (!user) {
2017
- return false;
1874
+
1875
+ const DocsTable = (props) => {
1876
+ const { entities, title, loading, columns, actions } = props;
1877
+ const [, copyToClipboard] = useCopyToClipboard();
1878
+ const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);
1879
+ const config = useApi(configApiRef);
1880
+ if (!entities)
1881
+ return null;
1882
+ const documents = entities.map((entity) => {
1883
+ var _a;
1884
+ const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY);
1885
+ return {
1886
+ entity,
1887
+ resolved: {
1888
+ docsUrl: getRouteToReaderPageFor({
1889
+ namespace: toLowerMaybe((_a = entity.metadata.namespace) != null ? _a : "default", config),
1890
+ kind: toLowerMaybe(entity.kind, config),
1891
+ name: toLowerMaybe(entity.metadata.name, config)
1892
+ }),
1893
+ ownedByRelations,
1894
+ ownedByRelationsTitle: ownedByRelations.map((r) => humanizeEntityRef(r, { defaultKind: "group" })).join(", ")
2018
1895
  }
2019
- return isOwnerOf(user, entity);
2020
- }
2021
- return typeof config.filterPredicate === "function" && config.filterPredicate(entity);
1896
+ };
2022
1897
  });
2023
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ContentHeader, {
2024
- title: config.title,
2025
- description: config.description
2026
- }, index === 0 ? /* @__PURE__ */ React.createElement(SupportButton, null, "Discover documentation in your ecosystem.") : null), /* @__PURE__ */ React.createElement("div", {
2027
- className: classes.panelContainer
2028
- }, /* @__PURE__ */ React.createElement(Panel, {
2029
- "data-testid": "techdocs-custom-panel",
2030
- entities: shownEntities
2031
- })));
1898
+ const defaultColumns = [
1899
+ columnFactories.createNameColumn(),
1900
+ columnFactories.createOwnerColumn(),
1901
+ columnFactories.createTypeColumn()
1902
+ ];
1903
+ const defaultActions = [
1904
+ actionFactories.createCopyDocsUrlAction(copyToClipboard)
1905
+ ];
1906
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, loading || documents && documents.length > 0 ? /* @__PURE__ */ React.createElement(Table, {
1907
+ isLoading: loading,
1908
+ options: {
1909
+ paging: true,
1910
+ pageSize: 20,
1911
+ search: true,
1912
+ actionsColumnIndex: -1
1913
+ },
1914
+ data: documents,
1915
+ columns: columns || defaultColumns,
1916
+ actions: actions || defaultActions,
1917
+ title: title ? `${title} (${documents.length})` : `All (${documents.length})`
1918
+ }) : /* @__PURE__ */ React.createElement(EmptyState, {
1919
+ missing: "data",
1920
+ title: "No documents to show",
1921
+ description: "Create your own document. Check out our Getting Started Information",
1922
+ action: /* @__PURE__ */ React.createElement(Button$1, {
1923
+ color: "primary",
1924
+ to: "https://backstage.io/docs/features/techdocs/getting-started",
1925
+ variant: "contained"
1926
+ }, "DOCS")
1927
+ }));
2032
1928
  };
2033
- const TechDocsCustomHome = ({
2034
- tabsConfig
2035
- }) => {
2036
- const [selectedTab, setSelectedTab] = useState(0);
2037
- const catalogApi = useApi(catalogApiRef);
2038
- const {
2039
- value: entities,
2040
- loading,
2041
- error
2042
- } = useAsync(async () => {
2043
- const response = await catalogApi.getEntities({
2044
- filter: {
2045
- "metadata.annotations.backstage.io/techdocs-ref": CATALOG_FILTER_EXISTS
2046
- },
2047
- fields: [
2048
- "apiVersion",
2049
- "kind",
2050
- "metadata",
2051
- "relations",
2052
- "spec.owner",
2053
- "spec.type"
2054
- ]
2055
- });
2056
- return response.items.filter((entity) => {
2057
- var _a;
2058
- return !!((_a = entity.metadata.annotations) == null ? void 0 : _a["backstage.io/techdocs-ref"]);
2059
- });
2060
- });
2061
- const currentTabConfig = tabsConfig[selectedTab];
2062
- if (loading) {
2063
- return /* @__PURE__ */ React.createElement(TechDocsPageWrapper, null, /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Progress, null)));
2064
- }
1929
+ DocsTable.columns = columnFactories;
1930
+ DocsTable.actions = actionFactories;
1931
+
1932
+ const EntityListDocsTable = (props) => {
1933
+ var _a, _b;
1934
+ const { columns, actions } = props;
1935
+ const { loading, error, entities, filters } = useEntityList();
1936
+ const { isStarredEntity, toggleStarredEntity } = useStarredEntities();
1937
+ const [, copyToClipboard] = useCopyToClipboard();
1938
+ const title = capitalize((_b = (_a = filters.user) == null ? void 0 : _a.value) != null ? _b : "all");
1939
+ const defaultActions = [
1940
+ actionFactories.createCopyDocsUrlAction(copyToClipboard),
1941
+ actionFactories.createStarEntityAction(isStarredEntity, toggleStarredEntity)
1942
+ ];
2065
1943
  if (error) {
2066
- return /* @__PURE__ */ React.createElement(TechDocsPageWrapper, null, /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(WarningPanel, {
1944
+ return /* @__PURE__ */ React.createElement(WarningPanel, {
2067
1945
  severity: "error",
2068
1946
  title: "Could not load available documentation."
2069
1947
  }, /* @__PURE__ */ React.createElement(CodeSnippet, {
2070
1948
  language: "text",
2071
1949
  text: error.toString()
2072
- }))));
1950
+ }));
2073
1951
  }
2074
- return /* @__PURE__ */ React.createElement(TechDocsPageWrapper, null, /* @__PURE__ */ React.createElement(HeaderTabs, {
2075
- selectedIndex: selectedTab,
2076
- onChange: (index) => setSelectedTab(index),
2077
- tabs: tabsConfig.map(({ label }, index) => ({
2078
- id: index.toString(),
2079
- label
2080
- }))
2081
- }), /* @__PURE__ */ React.createElement(Content, {
2082
- "data-testid": "techdocs-content"
2083
- }, currentTabConfig.panels.map((config, index) => /* @__PURE__ */ React.createElement(CustomPanel, {
2084
- key: index,
2085
- config,
2086
- entities: !!entities ? entities : [],
2087
- index
2088
- }))));
1952
+ return /* @__PURE__ */ React.createElement(DocsTable, {
1953
+ title,
1954
+ entities,
1955
+ loading,
1956
+ actions: actions || defaultActions,
1957
+ columns
1958
+ });
2089
1959
  };
2090
- function useOwnUser() {
2091
- const catalogApi = useApi(catalogApiRef);
2092
- const identityApi = useApi(identityApiRef);
2093
- return useAsync(async () => {
2094
- const identity = await identityApi.getBackstageIdentity();
2095
- return catalogApi.getEntityByName(parseEntityRef(identity.userEntityRef, {
2096
- defaultKind: "User",
2097
- defaultNamespace: DEFAULT_NAMESPACE
2098
- }));
2099
- }, [catalogApi, identityApi]);
2100
- }
1960
+ EntityListDocsTable.columns = columnFactories;
1961
+ EntityListDocsTable.actions = actionFactories;
2101
1962
 
2102
- var TechDocsCustomHome$1 = /*#__PURE__*/Object.freeze({
2103
- __proto__: null,
2104
- TechDocsCustomHome: TechDocsCustomHome
2105
- });
1963
+ const TechDocsPageWrapper = (props) => {
1964
+ var _a;
1965
+ const { children } = props;
1966
+ const configApi = useApi(configApiRef);
1967
+ const generatedSubtitle = `Documentation available in ${(_a = configApi.getOptionalString("organization.name")) != null ? _a : "Backstage"}`;
1968
+ return /* @__PURE__ */ React.createElement(PageWithHeader, {
1969
+ title: "Documentation",
1970
+ subtitle: generatedSubtitle,
1971
+ themeId: "documentation"
1972
+ }, children);
1973
+ };
2106
1974
 
2107
- const LegacyTechDocsHome = () => {
2108
- const tabsConfig = [
2109
- {
2110
- label: "Overview",
2111
- panels: [
2112
- {
2113
- title: "Overview",
2114
- description: "Explore your internal technical ecosystem through documentation.",
2115
- panelType: "DocsCardGrid",
2116
- filterPredicate: () => true
2117
- }
2118
- ]
2119
- },
2120
- {
2121
- label: "Owned Documents",
2122
- panels: [
2123
- {
2124
- title: "Owned documents",
2125
- description: "Access your documentation.",
2126
- panelType: "DocsTable",
2127
- filterPredicate: "ownedByUser"
2128
- }
2129
- ]
2130
- }
2131
- ];
2132
- return /* @__PURE__ */ React.createElement(TechDocsCustomHome, {
2133
- tabsConfig
2134
- });
1975
+ class TechDocsFilter {
1976
+ getCatalogFilters() {
1977
+ return {
1978
+ "metadata.annotations.backstage.io/techdocs-ref": CATALOG_FILTER_EXISTS
1979
+ };
1980
+ }
1981
+ }
1982
+ const TechDocsPicker = () => {
1983
+ const { updateFilters } = useEntityList();
1984
+ useEffect(() => {
1985
+ updateFilters({
1986
+ techdocs: new TechDocsFilter()
1987
+ });
1988
+ }, [updateFilters]);
1989
+ return null;
1990
+ };
1991
+
1992
+ const DefaultTechDocsHome = (props) => {
1993
+ const { initialFilter = "all", columns, actions } = props;
1994
+ return /* @__PURE__ */ React.createElement(TechDocsPageWrapper, null, /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, {
1995
+ title: ""
1996
+ }, /* @__PURE__ */ React.createElement(SupportButton, null, "Discover documentation in your ecosystem.")), /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(FilteredEntityLayout, null, /* @__PURE__ */ React.createElement(FilterContainer, null, /* @__PURE__ */ React.createElement(TechDocsPicker, null), /* @__PURE__ */ React.createElement(UserListPicker, {
1997
+ initialFilter
1998
+ }), /* @__PURE__ */ React.createElement(EntityOwnerPicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(EntityListContainer, null, /* @__PURE__ */ React.createElement(EntityListDocsTable, {
1999
+ actions,
2000
+ columns
2001
+ }))))));
2135
2002
  };
2136
2003
 
2004
+ const techdocsPlugin = createPlugin({
2005
+ id: "techdocs",
2006
+ apis: [
2007
+ createApiFactory({
2008
+ api: techdocsStorageApiRef,
2009
+ deps: {
2010
+ configApi: configApiRef,
2011
+ discoveryApi: discoveryApiRef,
2012
+ identityApi: identityApiRef,
2013
+ fetchApi: fetchApiRef
2014
+ },
2015
+ factory: ({ configApi, discoveryApi, identityApi, fetchApi }) => new TechDocsStorageClient({
2016
+ configApi,
2017
+ discoveryApi,
2018
+ identityApi,
2019
+ fetchApi
2020
+ })
2021
+ }),
2022
+ createApiFactory({
2023
+ api: techdocsApiRef,
2024
+ deps: {
2025
+ configApi: configApiRef,
2026
+ discoveryApi: discoveryApiRef,
2027
+ fetchApi: fetchApiRef
2028
+ },
2029
+ factory: ({ configApi, discoveryApi, fetchApi }) => new TechDocsClient({
2030
+ configApi,
2031
+ discoveryApi,
2032
+ fetchApi
2033
+ })
2034
+ })
2035
+ ],
2036
+ routes: {
2037
+ root: rootRouteRef,
2038
+ docRoot: rootDocsRouteRef,
2039
+ entityContent: rootCatalogDocsRouteRef
2040
+ }
2041
+ });
2042
+ const TechdocsPage = techdocsPlugin.provide(createRoutableExtension({
2043
+ name: "TechdocsPage",
2044
+ component: () => Promise.resolve().then(function () { return Router$1; }).then((m) => m.Router),
2045
+ mountPoint: rootRouteRef
2046
+ }));
2047
+ const EntityTechdocsContent = techdocsPlugin.provide(createRoutableExtension({
2048
+ name: "EntityTechdocsContent",
2049
+ component: () => Promise.resolve().then(function () { return Router$1; }).then((m) => m.EmbeddedDocsRouter),
2050
+ mountPoint: rootCatalogDocsRouteRef
2051
+ }));
2052
+ const TechDocsCustomHome = techdocsPlugin.provide(createRoutableExtension({
2053
+ name: "TechDocsCustomHome",
2054
+ component: () => import('./esm/TechDocsCustomHome-a4f542cd.esm.js').then((m) => m.TechDocsCustomHome),
2055
+ mountPoint: rootRouteRef
2056
+ }));
2057
+ const TechDocsIndexPage$2 = techdocsPlugin.provide(createRoutableExtension({
2058
+ name: "TechDocsIndexPage",
2059
+ component: () => Promise.resolve().then(function () { return TechDocsIndexPage$1; }).then((m) => m.TechDocsIndexPage),
2060
+ mountPoint: rootRouteRef
2061
+ }));
2062
+ const TechDocsReaderPage = techdocsPlugin.provide(createRoutableExtension({
2063
+ name: "TechDocsReaderPage",
2064
+ component: () => Promise.resolve().then(function () { return TechDocsReaderPage$2; }).then((m) => m.TechDocsReaderPage),
2065
+ mountPoint: rootDocsRouteRef
2066
+ }));
2067
+
2137
2068
  const TechDocsIndexPage = () => {
2138
2069
  const outlet = useOutlet();
2139
- return outlet || /* @__PURE__ */ React.createElement(LegacyTechDocsHome, null);
2070
+ return outlet || /* @__PURE__ */ React.createElement(DefaultTechDocsHome, null);
2140
2071
  };
2141
2072
 
2142
2073
  var TechDocsIndexPage$1 = /*#__PURE__*/Object.freeze({
@@ -2168,10 +2099,10 @@ const Router = () => {
2168
2099
  element: /* @__PURE__ */ React.createElement(TechDocsIndexPage, null)
2169
2100
  }), /* @__PURE__ */ React.createElement(Route, {
2170
2101
  path: "/:namespace/:kind/:name/*",
2171
- element: /* @__PURE__ */ React.createElement(TechDocsPage, null)
2102
+ element: /* @__PURE__ */ React.createElement(TechDocsReaderPage$1, null)
2172
2103
  }));
2173
2104
  };
2174
- const EmbeddedDocsRouter = (_props) => {
2105
+ const EmbeddedDocsRouter = () => {
2175
2106
  var _a;
2176
2107
  const { entity } = useEntity();
2177
2108
  const projectId = (_a = entity.metadata.annotations) == null ? void 0 : _a[TECHDOCS_ANNOTATION];
@@ -2195,5 +2126,5 @@ var Router$1 = /*#__PURE__*/Object.freeze({
2195
2126
  EmbeddedDocsRouter: EmbeddedDocsRouter
2196
2127
  });
2197
2128
 
2198
- export { DefaultTechDocsHome, DocsCardGrid, DocsResultListItem, DocsTable, EmbeddedDocsRouter, EntityListDocsGrid, EntityListDocsTable, EntityTechdocsContent, Reader, Router, TechDocsClient, TechDocsCustomHome$2 as TechDocsCustomHome, TechDocsIndexPage$2 as TechDocsIndexPage, TechDocsPage, TechDocsPageHeader, TechDocsPageWrapper, TechDocsPicker, TechDocsReaderPage, TechDocsSearch, TechDocsStateIndicator, TechDocsStorageClient, TechdocsPage, isTechDocsAvailable, techdocsPlugin as plugin, techdocsApiRef, techdocsPlugin, techdocsStorageApiRef, useTechDocsReader, useTechDocsReaderDom, withTechDocsReaderProvider };
2129
+ export { DefaultTechDocsHome, DocsCardGrid, DocsResultListItem, DocsTable, EmbeddedDocsRouter, EntityListDocsGrid, EntityListDocsTable, EntityTechdocsContent, Reader, Router, TechDocsClient, TechDocsCustomHome, TechDocsIndexPage$2 as TechDocsIndexPage, TechDocsPage, TechDocsPageHeader, TechDocsPageWrapper, TechDocsPicker, TechDocsReaderPage, TechDocsReaderPageHeader, TechDocsSearch, TechDocsSearchResultListItem, TechDocsStateIndicator, TechDocsStorageClient, TechdocsPage, isTechDocsAvailable, techdocsPlugin as plugin, techdocsApiRef, techdocsPlugin, techdocsStorageApiRef, useTechDocsReader, useTechDocsReaderDom, withTechDocsReaderProvider };
2199
2130
  //# sourceMappingURL=index.esm.js.map