@axis-backstage/plugin-readme 0.18.2 → 0.19.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/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # @axis-backstage/plugin-readme
2
2
 
3
+ ## 0.19.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 8ce6673: Updated lodash dependency, updated tests to use msw 2.
8
+
9
+ ## 0.19.0
10
+
11
+ ### Minor Changes
12
+
13
+ - b5e5411: Added a `hideIfNotFound` config option to the readme card extension for
14
+ the new frontend system. When set to `true`, the entire card is hidden
15
+ when no README.md file is found, instead of showing an error message.
16
+ This is the NFS equivalent of the `isReadmeAvailable` entity filter
17
+ available in the old frontend system.
18
+
19
+ Example app-config.yaml:
20
+
21
+ app:
22
+ extensions: - entity-card:readme:
23
+ config:
24
+ hideIfNotFound: true
25
+
26
+ - bb83b10: Migrated `ReadmeCard` from the legacy MUI `InfoCard` to `EntityInfoCard` from `@backstage/plugin-catalog-react`.
27
+
28
+ The `variant` and `maxHeight` props have been removed from `ReadmeCardProps`. Users relying on these props should switch to `ReadmeCardLegacyProps`, which preserves the old behaviour and is accepted by `ReadmeCard` for backward compatibility. Both `variant` and `maxHeight` are deprecated and will be removed in a future release.
29
+
30
+ ### Patch Changes
31
+
32
+ - 8a8e8da: NFS: Make the Readme entity card height configurable
33
+
34
+ The height of the entity card now defaults to the same height as
35
+ the legacy card. If used in NFS the height can be configured
36
+ using the config as follows:
37
+
38
+ ```yaml
39
+ app:
40
+ extensions:
41
+ - entity-card:readme:
42
+ config:
43
+ hideIfNotFound: true
44
+ maxHeight: 500px
45
+ ```
46
+
3
47
  ## 0.18.2
4
48
 
5
49
  ### Patch Changes
package/README.md CHANGED
@@ -66,7 +66,9 @@ const defaultEntityPage = (
66
66
  )
67
67
  ```
68
68
 
69
- To use `ReadmeCard` in a seperate page with full height:
69
+ > **Note:** `isReadmeAvailable` is only available in the legacy frontend system. In the new frontend system, use the `hideIfNotFound` config option instead — see the [New Frontend System](#new-frontend-system) section below.
70
+
71
+ To use `ReadmeCard` in a separate page with full height:
70
72
 
71
73
  ```tsx
72
74
  import { ReadmeCard } from '@axis-backstage/plugin-readme';
@@ -104,19 +106,21 @@ const app = createApp({
104
106
  });
105
107
  ```
106
108
 
107
- 3. If necessary, you can also customize the plugin:
109
+ 3. If necessary, you can also customize the plugin using the following config options:
110
+
111
+ | Config key | Type | Default | Description |
112
+ | ---------------- | --------- | --------- | ----------------------------------------------------------------------------------------------------------------------------- |
113
+ | `hideIfNotFound` | `boolean` | `false` | When `true`, the card is hidden entirely if no README file is found, instead of showing an informational message. |
114
+ | `maxHeight` | `string` | `'235px'` | Maximum height of the card content area. Accepts any valid CSS length (e.g. `'500px'`, `'50vh'`) or `'none'` for full height. |
108
115
 
109
116
  ```yaml
110
117
  # app-config.yaml
111
118
  app:
112
- # ...
113
119
  extensions:
114
- # ...
115
120
  - entity-card:readme:
116
121
  config:
117
- filter:
118
- kind: component
119
- # ...
122
+ hideIfNotFound: true
123
+ maxHeight: 500px
120
124
  ```
121
125
 
122
126
  ## Layout
package/dist/alpha.d.ts CHANGED
@@ -20,15 +20,17 @@ declare const _default: _backstage_frontend_plugin_api.OverridableFrontendPlugin
20
20
  params: <TApi, TImpl extends TApi, TDeps extends { [name in string]: unknown; }>(params: _backstage_frontend_plugin_api.ApiFactory<TApi, TImpl, TDeps>) => _backstage_frontend_plugin_api.ExtensionBlueprintParams<_backstage_frontend_plugin_api.AnyApiFactory>;
21
21
  }>;
22
22
  "entity-card:readme": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
23
- kind: "entity-card";
24
- name: undefined;
25
23
  config: {
24
+ hideIfNotFound: boolean;
25
+ maxHeight: string;
26
26
  filter: _backstage_filter_predicates.FilterPredicate | undefined;
27
27
  type: "content" | "info" | undefined;
28
28
  };
29
29
  configInput: {
30
+ hideIfNotFound?: boolean | undefined;
31
+ maxHeight?: string | undefined;
30
32
  filter?: _backstage_filter_predicates.FilterPredicate | undefined;
31
- type?: "content" | "info" | undefined;
33
+ type?: "content" | "info" | undefined | undefined;
32
34
  };
33
35
  output: _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<(entity: _backstage_catalog_model.Entity) => boolean, "catalog.entity-filter-function", {
34
36
  optional: true;
@@ -38,6 +40,8 @@ declare const _default: _backstage_frontend_plugin_api.OverridableFrontendPlugin
38
40
  optional: true;
39
41
  }>;
40
42
  inputs: {};
43
+ kind: "entity-card";
44
+ name: undefined;
41
45
  params: {
42
46
  loader: () => Promise<JSX.Element>;
43
47
  filter?: string | _backstage_filter_predicates.FilterPredicate | ((entity: _backstage_catalog_model.Entity) => boolean);
package/dist/alpha.esm.js CHANGED
@@ -3,6 +3,7 @@ import { ApiBlueprint, identityApiRef, fetchApiRef, discoveryApiRef, createFront
3
3
  import { readmeApiRef } from './api/ReadmeApi.esm.js';
4
4
  import { ReadmeClient } from './api/ReadmeClient.esm.js';
5
5
  import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha';
6
+ import { z } from 'zod';
6
7
 
7
8
  const readmeApi = ApiBlueprint.make({
8
9
  params: (defineParams) => defineParams({
@@ -15,9 +16,55 @@ const readmeApi = ApiBlueprint.make({
15
16
  factory: ({ discoveryApi, fetchApi, identityApi }) => new ReadmeClient({ discoveryApi, fetchApi, identityApi })
16
17
  })
17
18
  });
18
- const readmeCard = EntityCardBlueprint.make({
19
- params: {
20
- loader: async () => import('./components/ReadmeCard/index.esm.js').then((m) => /* @__PURE__ */ jsx(m.ReadmeCard, {}))
19
+ const readmeCard = EntityCardBlueprint.makeWithOverrides({
20
+ configSchema: {
21
+ /**
22
+ * When set to `true`, the entire README card is hidden if no README file
23
+ * is found for the entity (i.e. the backend returns a 404).
24
+ *
25
+ * Defaults to `false`, which shows an informational message with a link
26
+ * to the documentation instead.
27
+ *
28
+ * @example
29
+ * ```yaml
30
+ * # app-config.yaml
31
+ * app:
32
+ * extensions:
33
+ * - entity-card:readme:
34
+ * config:
35
+ * hideIfNotFound: true
36
+ * ```
37
+ */
38
+ hideIfNotFound: z.boolean().default(false),
39
+ /**
40
+ * Sets the maximum height of the README card content area. Accepts any
41
+ * valid CSS length value (e.g. `'235px'`, `'50vh'`) or `'none'` to show
42
+ * the full content without scrolling.
43
+ *
44
+ * Defaults to `'235px'`.
45
+ *
46
+ * @example
47
+ * ```yaml
48
+ * # app-config.yaml
49
+ * app:
50
+ * extensions:
51
+ * - entity-card:readme:
52
+ * config:
53
+ * maxHeight: 500px
54
+ * ```
55
+ */
56
+ maxHeight: z.string().default("235px")
57
+ },
58
+ factory(originalFactory, { config }) {
59
+ return originalFactory({
60
+ loader: async () => import('./components/ReadmeCard/index.esm.js').then((m) => /* @__PURE__ */ jsx(
61
+ m.ReadmeCard,
62
+ {
63
+ hideIfNotFound: config.hideIfNotFound,
64
+ maxHeight: config.maxHeight
65
+ }
66
+ ))
67
+ });
21
68
  }
22
69
  });
23
70
  var alpha = createFrontendPlugin({
@@ -1 +1 @@
1
- {"version":3,"file":"alpha.esm.js","sources":["../src/alpha.tsx"],"sourcesContent":["/**\n * Frontend plugin that fetches and displays the readme for an entity\n *\n * @packageDocumentation\n */\n\nimport {\n ApiBlueprint,\n createFrontendPlugin,\n discoveryApiRef,\n fetchApiRef,\n identityApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { readmeApiRef } from './api/ReadmeApi';\nimport { ReadmeClient } from './api/ReadmeClient';\nimport { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha';\n\nconst readmeApi = ApiBlueprint.make({\n params: defineParams =>\n defineParams({\n api: readmeApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ discoveryApi, fetchApi, identityApi }) =>\n new ReadmeClient({ discoveryApi, fetchApi, identityApi }),\n }),\n});\n\nconst readmeCard = EntityCardBlueprint.make({\n params: {\n loader: async () =>\n import('./components/ReadmeCard').then(m => <m.ReadmeCard />),\n },\n});\n\nexport default createFrontendPlugin({\n pluginId: 'readme',\n extensions: [readmeApi, readmeCard],\n});\n"],"names":[],"mappings":";;;;;;AAiBA,MAAM,SAAA,GAAY,aAAa,IAAA,CAAK;AAAA,EAClC,MAAA,EAAQ,kBACN,YAAA,CAAa;AAAA,IACX,GAAA,EAAK,YAAA;AAAA,IACL,IAAA,EAAM;AAAA,MACJ,YAAA,EAAc,eAAA;AAAA,MACd,QAAA,EAAU,WAAA;AAAA,MACV,WAAA,EAAa;AAAA,KACf;AAAA,IACA,OAAA,EAAS,CAAC,EAAE,YAAA,EAAc,QAAA,EAAU,WAAA,EAAY,KAC9C,IAAI,YAAA,CAAa,EAAE,YAAA,EAAc,QAAA,EAAU,aAAa;AAAA,GAC3D;AACL,CAAC,CAAA;AAED,MAAM,UAAA,GAAa,oBAAoB,IAAA,CAAK;AAAA,EAC1C,MAAA,EAAQ;AAAA,IACN,MAAA,EAAQ,YACN,OAAO,sCAAyB,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAAK,GAAA,CAAC,CAAA,CAAE,UAAA,EAAF,EAAa,CAAE;AAAA;AAElE,CAAC,CAAA;AAED,YAAe,oBAAA,CAAqB;AAAA,EAClC,QAAA,EAAU,QAAA;AAAA,EACV,UAAA,EAAY,CAAC,SAAA,EAAW,UAAU;AACpC,CAAC,CAAA;;;;"}
1
+ {"version":3,"file":"alpha.esm.js","sources":["../src/alpha.tsx"],"sourcesContent":["/**\n * Frontend plugin that fetches and displays the readme for an entity\n *\n * @packageDocumentation\n */\n\nimport {\n ApiBlueprint,\n createFrontendPlugin,\n discoveryApiRef,\n fetchApiRef,\n identityApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { readmeApiRef } from './api/ReadmeApi';\nimport { ReadmeClient } from './api/ReadmeClient';\nimport { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha';\nimport { z } from 'zod';\n\nconst readmeApi = ApiBlueprint.make({\n params: defineParams =>\n defineParams({\n api: readmeApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ discoveryApi, fetchApi, identityApi }) =>\n new ReadmeClient({ discoveryApi, fetchApi, identityApi }),\n }),\n});\n\nconst readmeCard = EntityCardBlueprint.makeWithOverrides({\n configSchema: {\n /**\n * When set to `true`, the entire README card is hidden if no README file\n * is found for the entity (i.e. the backend returns a 404).\n *\n * Defaults to `false`, which shows an informational message with a link\n * to the documentation instead.\n *\n * @example\n * ```yaml\n * # app-config.yaml\n * app:\n * extensions:\n * - entity-card:readme:\n * config:\n * hideIfNotFound: true\n * ```\n */\n hideIfNotFound: z.boolean().default(false),\n /**\n * Sets the maximum height of the README card content area. Accepts any\n * valid CSS length value (e.g. `'235px'`, `'50vh'`) or `'none'` to show\n * the full content without scrolling.\n *\n * Defaults to `'235px'`.\n *\n * @example\n * ```yaml\n * # app-config.yaml\n * app:\n * extensions:\n * - entity-card:readme:\n * config:\n * maxHeight: 500px\n * ```\n */\n maxHeight: z.string().default('235px'),\n },\n factory(originalFactory, { config }) {\n return originalFactory({\n loader: async () =>\n import('./components/ReadmeCard').then(m => (\n <m.ReadmeCard\n hideIfNotFound={config.hideIfNotFound}\n maxHeight={config.maxHeight}\n />\n )),\n });\n },\n});\n\nexport default createFrontendPlugin({\n pluginId: 'readme',\n extensions: [readmeApi, readmeCard],\n});\n"],"names":[],"mappings":";;;;;;;AAkBA,MAAM,SAAA,GAAY,aAAa,IAAA,CAAK;AAAA,EAClC,MAAA,EAAQ,kBACN,YAAA,CAAa;AAAA,IACX,GAAA,EAAK,YAAA;AAAA,IACL,IAAA,EAAM;AAAA,MACJ,YAAA,EAAc,eAAA;AAAA,MACd,QAAA,EAAU,WAAA;AAAA,MACV,WAAA,EAAa;AAAA,KACf;AAAA,IACA,OAAA,EAAS,CAAC,EAAE,YAAA,EAAc,QAAA,EAAU,WAAA,EAAY,KAC9C,IAAI,YAAA,CAAa,EAAE,YAAA,EAAc,QAAA,EAAU,aAAa;AAAA,GAC3D;AACL,CAAC,CAAA;AAED,MAAM,UAAA,GAAa,oBAAoB,iBAAA,CAAkB;AAAA,EACvD,YAAA,EAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAkBZ,cAAA,EAAgB,CAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,KAAK,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAkBzC,SAAA,EAAW,CAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,OAAO;AAAA,GACvC;AAAA,EACA,OAAA,CAAQ,eAAA,EAAiB,EAAE,MAAA,EAAO,EAAG;AACnC,IAAA,OAAO,eAAA,CAAgB;AAAA,MACrB,QAAQ,YACN,OAAO,sCAAyB,CAAA,CAAE,KAAK,CAAA,CAAA,qBACrC,GAAA;AAAA,QAAC,CAAA,CAAE,UAAA;AAAA,QAAF;AAAA,UACC,gBAAgB,MAAA,CAAO,cAAA;AAAA,UACvB,WAAW,MAAA,CAAO;AAAA;AAAA,OAErB;AAAA,KACJ,CAAA;AAAA,EACH;AACF,CAAC,CAAA;AAED,YAAe,oBAAA,CAAqB;AAAA,EAClC,QAAA,EAAU,QAAA;AAAA,EACV,UAAA,EAAY,CAAC,SAAA,EAAW,UAAU;AACpC,CAAC,CAAA;;;;"}
@@ -1,16 +1,30 @@
1
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import IconButton from '@mui/material/IconButton';
3
3
  import Box from '@mui/material/Box';
4
4
  import FullscreenIcon from '@mui/icons-material/Fullscreen';
5
+ import { EntityInfoCard } from '@backstage/plugin-catalog-react';
5
6
  import { InfoCard } from '@backstage/core-components';
6
- import { FetchComponent } from '../FetchComponent/FetchComponent.esm.js';
7
+ import { ReadmeContent } from '../ReadmeContent/ReadmeContent.esm.js';
7
8
  import { ReadmeDialog } from '../ReadmeDialog/ReadmeDialog.esm.js';
8
9
  import { useFullViewParam } from '../../hooks/useFullViewParam.esm.js';
10
+ import { useReadmeContent } from '../../hooks/useReadmeContent.esm.js';
11
+ import { ResponseError } from '@backstage/errors';
9
12
 
10
- const ReadmeCard = (props) => {
11
- const { variant = "gridItem", maxHeight: propMaxHeight } = props;
13
+ function isLegacyProps(props) {
14
+ return "variant" in props;
15
+ }
16
+ function ReadmeCardLegacy(props) {
17
+ const {
18
+ variant = "gridItem",
19
+ maxHeight: propMaxHeight,
20
+ hideIfNotFound
21
+ } = props;
12
22
  const [isFullViewOpen, setIsFullViewOpen] = useFullViewParam();
23
+ const readmeContent = useReadmeContent();
13
24
  const maxHeight = variant === "fullHeight" ? "none" : propMaxHeight ?? "235px";
25
+ if (hideIfNotFound && readmeContent.error instanceof ResponseError && readmeContent.error.statusCode === 404) {
26
+ return null;
27
+ }
14
28
  return /* @__PURE__ */ jsxs(Fragment, { children: [
15
29
  /* @__PURE__ */ jsx(
16
30
  InfoCard,
@@ -27,17 +41,59 @@ const ReadmeCard = (props) => {
27
41
  children: /* @__PURE__ */ jsx(FullscreenIcon, {})
28
42
  }
29
43
  ) : void 0,
30
- children: /* @__PURE__ */ jsx("div", { style: { overflow: "auto" }, children: /* @__PURE__ */ jsx(Box, { maxHeight, children: /* @__PURE__ */ jsx(FetchComponent, {}) }) })
44
+ children: /* @__PURE__ */ jsx("div", { style: { overflow: "auto" }, children: /* @__PURE__ */ jsx(Box, { maxHeight, children: /* @__PURE__ */ jsx(ReadmeContent, { ...readmeContent }) }) })
45
+ }
46
+ ),
47
+ /* @__PURE__ */ jsx(
48
+ ReadmeDialog,
49
+ {
50
+ open: isFullViewOpen,
51
+ onClose: () => setIsFullViewOpen(false),
52
+ ...readmeContent
53
+ }
54
+ )
55
+ ] });
56
+ }
57
+ function ReadmeCardNew(props) {
58
+ const { hideIfNotFound, maxHeight = "235px" } = props;
59
+ const [isFullViewOpen, setIsFullViewOpen] = useFullViewParam();
60
+ const readmeContent = useReadmeContent();
61
+ if (hideIfNotFound && readmeContent.error instanceof ResponseError && readmeContent.error.statusCode === 404) {
62
+ return null;
63
+ }
64
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
65
+ /* @__PURE__ */ jsx(
66
+ EntityInfoCard,
67
+ {
68
+ title: "README",
69
+ headerActions: /* @__PURE__ */ jsx(
70
+ IconButton,
71
+ {
72
+ onClick: () => setIsFullViewOpen(true),
73
+ "aria-label": "Open full view",
74
+ title: "Open full view",
75
+ size: "large",
76
+ children: /* @__PURE__ */ jsx(FullscreenIcon, {})
77
+ }
78
+ ),
79
+ children: /* @__PURE__ */ jsx("div", { style: { overflow: "auto", maxHeight }, children: /* @__PURE__ */ jsx(ReadmeContent, { ...readmeContent }) })
31
80
  }
32
81
  ),
33
82
  /* @__PURE__ */ jsx(
34
83
  ReadmeDialog,
35
84
  {
36
85
  open: isFullViewOpen,
37
- onClose: () => setIsFullViewOpen(false)
86
+ onClose: () => setIsFullViewOpen(false),
87
+ ...readmeContent
38
88
  }
39
89
  )
40
90
  ] });
91
+ }
92
+ const ReadmeCard = (props) => {
93
+ if (isLegacyProps(props)) {
94
+ return /* @__PURE__ */ jsx(ReadmeCardLegacy, { ...props });
95
+ }
96
+ return /* @__PURE__ */ jsx(ReadmeCardNew, { ...props });
41
97
  };
42
98
 
43
99
  export { ReadmeCard };
@@ -1 +1 @@
1
- {"version":3,"file":"ReadmeCard.esm.js","sources":["../../../src/components/ReadmeCard/ReadmeCard.tsx"],"sourcesContent":["import IconButton from '@mui/material/IconButton';\nimport Box from '@mui/material/Box';\nimport FullscreenIcon from '@mui/icons-material/Fullscreen';\nimport { InfoCard, InfoCardVariants } from '@backstage/core-components';\nimport { FetchComponent } from '../FetchComponent';\nimport { ReadmeDialog } from '../ReadmeDialog/ReadmeDialog';\nimport { useFullViewParam } from '../../hooks/useFullViewParam';\n\n/**\n * ReadmeCardProps props.\n *\n * @public\n */\nexport type ReadmeCardProps = {\n variant?: InfoCardVariants;\n maxHeight?: string | number;\n};\n\nexport const ReadmeCard = (props: ReadmeCardProps) => {\n const { variant = 'gridItem', maxHeight: propMaxHeight } = props;\n const [isFullViewOpen, setIsFullViewOpen] = useFullViewParam();\n\n const maxHeight =\n variant === 'fullHeight' ? 'none' : propMaxHeight ?? '235px';\n\n return (\n <>\n <InfoCard\n title=\"README\"\n variant={variant}\n action={\n variant !== 'fullHeight' ? (\n <IconButton\n onClick={() => setIsFullViewOpen(true)}\n aria-label=\"Open full view\"\n title=\"Open full view\"\n size=\"large\"\n >\n <FullscreenIcon />\n </IconButton>\n ) : undefined\n }\n >\n <div style={{ overflow: 'auto' }}>\n <Box maxHeight={maxHeight}>\n <FetchComponent />\n </Box>\n </div>\n </InfoCard>\n\n <ReadmeDialog\n open={isFullViewOpen}\n onClose={() => setIsFullViewOpen(false)}\n />\n </>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;AAkBO,MAAM,UAAA,GAAa,CAAC,KAAA,KAA2B;AACpD,EAAA,MAAM,EAAE,OAAA,GAAU,UAAA,EAAY,SAAA,EAAW,eAAc,GAAI,KAAA;AAC3D,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,gBAAA,EAAiB;AAE7D,EAAA,MAAM,SAAA,GACJ,OAAA,KAAY,YAAA,GAAe,MAAA,GAAS,aAAA,IAAiB,OAAA;AAEvD,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAM,QAAA;AAAA,QACN,OAAA;AAAA,QACA,MAAA,EACE,YAAY,YAAA,mBACV,GAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACC,OAAA,EAAS,MAAM,iBAAA,CAAkB,IAAI,CAAA;AAAA,YACrC,YAAA,EAAW,gBAAA;AAAA,YACX,KAAA,EAAM,gBAAA;AAAA,YACN,IAAA,EAAK,OAAA;AAAA,YAEL,8BAAC,cAAA,EAAA,EAAe;AAAA;AAAA,SAClB,GACE,MAAA;AAAA,QAGN,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,EAAE,QAAA,EAAU,MAAA,EAAO,EAC7B,QAAA,kBAAA,GAAA,CAAC,GAAA,EAAA,EAAI,SAAA,EACH,QAAA,kBAAA,GAAA,CAAC,cAAA,EAAA,EAAe,GAClB,CAAA,EACF;AAAA;AAAA,KACF;AAAA,oBAEA,GAAA;AAAA,MAAC,YAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAM,cAAA;AAAA,QACN,OAAA,EAAS,MAAM,iBAAA,CAAkB,KAAK;AAAA;AAAA;AACxC,GAAA,EACF,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"ReadmeCard.esm.js","sources":["../../../src/components/ReadmeCard/ReadmeCard.tsx"],"sourcesContent":["import IconButton from '@mui/material/IconButton';\nimport Box from '@mui/material/Box';\nimport FullscreenIcon from '@mui/icons-material/Fullscreen';\nimport { EntityInfoCard } from '@backstage/plugin-catalog-react';\nimport { InfoCard, InfoCardVariants } from '@backstage/core-components';\nimport { ReadmeContent } from '../ReadmeContent';\nimport { ReadmeDialog } from '../ReadmeDialog/ReadmeDialog';\nimport { useFullViewParam } from '../../hooks/useFullViewParam';\nimport { useReadmeContent } from '../../hooks/useReadmeContent';\nimport { ResponseError } from '@backstage/errors';\n\n/**\n * Props for the ReadmeCard component.\n * @public\n */\nexport type ReadmeCardProps = {\n hideIfNotFound?: boolean;\n maxHeight?: string | number;\n};\n\n/**\n * Props for the legacy MUI-based rendering of ReadmeCard.\n * @deprecated Use {@link ReadmeCardProps} instead.\n * @public\n */\nexport type ReadmeCardLegacyProps = {\n /** @deprecated Use the new {@link ReadmeCardProps} instead. */\n variant?: InfoCardVariants;\n /** @deprecated Use the new {@link ReadmeCardProps} instead. */\n maxHeight?: string | number;\n hideIfNotFound?: boolean;\n};\n\nfunction isLegacyProps(\n props: ReadmeCardProps | ReadmeCardLegacyProps,\n): props is ReadmeCardLegacyProps {\n return 'variant' in props;\n}\n\nfunction ReadmeCardLegacy(props: ReadmeCardLegacyProps) {\n const {\n variant = 'gridItem',\n maxHeight: propMaxHeight,\n hideIfNotFound,\n } = props;\n const [isFullViewOpen, setIsFullViewOpen] = useFullViewParam();\n const readmeContent = useReadmeContent();\n\n const maxHeight =\n variant === 'fullHeight' ? 'none' : propMaxHeight ?? '235px';\n\n if (\n hideIfNotFound &&\n readmeContent.error instanceof ResponseError &&\n readmeContent.error.statusCode === 404\n ) {\n return null;\n }\n\n return (\n <>\n <InfoCard\n title=\"README\"\n variant={variant}\n action={\n variant !== 'fullHeight' ? (\n <IconButton\n onClick={() => setIsFullViewOpen(true)}\n aria-label=\"Open full view\"\n title=\"Open full view\"\n size=\"large\"\n >\n <FullscreenIcon />\n </IconButton>\n ) : undefined\n }\n >\n <div style={{ overflow: 'auto' }}>\n <Box maxHeight={maxHeight}>\n <ReadmeContent {...readmeContent} />\n </Box>\n </div>\n </InfoCard>\n\n <ReadmeDialog\n open={isFullViewOpen}\n onClose={() => setIsFullViewOpen(false)}\n {...readmeContent}\n />\n </>\n );\n}\n\nfunction ReadmeCardNew(props: ReadmeCardProps) {\n const { hideIfNotFound, maxHeight = '235px' } = props;\n const [isFullViewOpen, setIsFullViewOpen] = useFullViewParam();\n const readmeContent = useReadmeContent();\n\n if (\n hideIfNotFound &&\n readmeContent.error instanceof ResponseError &&\n readmeContent.error.statusCode === 404\n ) {\n return null;\n }\n\n return (\n <>\n <EntityInfoCard\n title=\"README\"\n headerActions={\n <IconButton\n onClick={() => setIsFullViewOpen(true)}\n aria-label=\"Open full view\"\n title=\"Open full view\"\n size=\"large\"\n >\n <FullscreenIcon />\n </IconButton>\n }\n >\n <div style={{ overflow: 'auto', maxHeight }}>\n <ReadmeContent {...readmeContent} />\n </div>\n </EntityInfoCard>\n\n <ReadmeDialog\n open={isFullViewOpen}\n onClose={() => setIsFullViewOpen(false)}\n {...readmeContent}\n />\n </>\n );\n}\n\nexport const ReadmeCard = (props: ReadmeCardProps | ReadmeCardLegacyProps) => {\n if (isLegacyProps(props)) {\n return <ReadmeCardLegacy {...props} />;\n }\n return <ReadmeCardNew {...props} />;\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { Progress, ResponseErrorPanel, ErrorPanel, MarkdownContent, CodeSnippet } from '@backstage/core-components';
3
+ import Box from '@mui/material/Box';
4
+ import Typography from '@mui/material/Typography';
5
+ import { ResponseError } from '@backstage/errors';
6
+
7
+ const ReadmeContent = ({
8
+ content,
9
+ loading,
10
+ error,
11
+ location
12
+ }) => {
13
+ if (loading) {
14
+ return /* @__PURE__ */ jsx(Progress, {});
15
+ }
16
+ if (error instanceof ResponseError) {
17
+ if (error.statusCode === 404) {
18
+ return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Typography, { pb: 2, variant: "body2", children: [
19
+ "No README.md file found at source location:",
20
+ " ",
21
+ location && /* @__PURE__ */ jsx("strong", { children: location })
22
+ ] }) });
23
+ }
24
+ return /* @__PURE__ */ jsx(ResponseErrorPanel, { error });
25
+ }
26
+ if (error) {
27
+ return /* @__PURE__ */ jsx(ErrorPanel, { error });
28
+ }
29
+ if (!content) {
30
+ return /* @__PURE__ */ jsx(ErrorPanel, { error: Error("Unknown error") });
31
+ }
32
+ if (!content[1] || content[1] === "error") {
33
+ return /* @__PURE__ */ jsx(ErrorPanel, { error: Error(content[1] ?? "Unknown error") });
34
+ }
35
+ if (content[1].startsWith("text/markdown")) {
36
+ return /* @__PURE__ */ jsx(MarkdownContent, { content: content[0] });
37
+ }
38
+ return /* @__PURE__ */ jsx("div", { "data-testid": "readme-content", children: /* @__PURE__ */ jsx(CodeSnippet, { text: content[0], language: "plaintext" }) });
39
+ };
40
+
41
+ export { ReadmeContent };
42
+ //# sourceMappingURL=ReadmeContent.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReadmeContent.esm.js","sources":["../../../src/components/ReadmeContent/ReadmeContent.tsx"],"sourcesContent":["import {\n CodeSnippet,\n ErrorPanel,\n MarkdownContent,\n Progress,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport { ResponseError } from '@backstage/errors';\nimport type { UseReadmeContentResult } from '../../hooks/useReadmeContent';\n\nexport type ReadmeContentProps = UseReadmeContentResult;\n\nexport const ReadmeContent = ({\n content,\n loading,\n error,\n location,\n}: ReadmeContentProps) => {\n if (loading) {\n return <Progress />;\n }\n\n if (error instanceof ResponseError) {\n if (error.statusCode === 404) {\n return (\n <Box>\n <Typography pb={2} variant=\"body2\">\n No README.md file found at source location:{' '}\n {location && <strong>{location}</strong>}\n </Typography>\n </Box>\n );\n }\n return <ResponseErrorPanel error={error} />;\n }\n\n if (error) {\n return <ErrorPanel error={error} />;\n }\n if (!content) {\n return <ErrorPanel error={Error('Unknown error')} />;\n }\n if (!content[1] || content[1] === 'error') {\n return <ErrorPanel error={Error(content[1] ?? 'Unknown error')} />;\n }\n if (content[1].startsWith('text/markdown')) {\n return <MarkdownContent content={content[0]} />;\n }\n // probably text/plain\n return (\n <div data-testid=\"readme-content\">\n <CodeSnippet text={content[0]} language=\"plaintext\" />\n </div>\n );\n};\n"],"names":[],"mappings":";;;;;;AAcO,MAAM,gBAAgB,CAAC;AAAA,EAC5B,OAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,KAA0B;AACxB,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,2BAAQ,QAAA,EAAA,EAAS,CAAA;AAAA,EACnB;AAEA,EAAA,IAAI,iBAAiB,aAAA,EAAe;AAClC,IAAA,IAAI,KAAA,CAAM,eAAe,GAAA,EAAK;AAC5B,MAAA,2BACG,GAAA,EAAA,EACC,QAAA,kBAAA,IAAA,CAAC,cAAW,EAAA,EAAI,CAAA,EAAG,SAAQ,OAAA,EAAQ,QAAA,EAAA;AAAA,QAAA,6CAAA;AAAA,QACW,GAAA;AAAA,QAC3C,QAAA,oBAAY,GAAA,CAAC,QAAA,EAAA,EAAQ,QAAA,EAAA,QAAA,EAAS;AAAA,OAAA,EACjC,CAAA,EACF,CAAA;AAAA,IAEJ;AACA,IAAA,uBAAO,GAAA,CAAC,sBAAmB,KAAA,EAAc,CAAA;AAAA,EAC3C;AAEA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,uBAAO,GAAA,CAAC,cAAW,KAAA,EAAc,CAAA;AAAA,EACnC;AACA,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,uBAAO,GAAA,CAAC,UAAA,EAAA,EAAW,KAAA,EAAO,KAAA,CAAM,eAAe,CAAA,EAAG,CAAA;AAAA,EACpD;AACA,EAAA,IAAI,CAAC,OAAA,CAAQ,CAAC,KAAK,OAAA,CAAQ,CAAC,MAAM,OAAA,EAAS;AACzC,IAAA,uBAAO,GAAA,CAAC,cAAW,KAAA,EAAO,KAAA,CAAM,QAAQ,CAAC,CAAA,IAAK,eAAe,CAAA,EAAG,CAAA;AAAA,EAClE;AACA,EAAA,IAAI,OAAA,CAAQ,CAAC,CAAA,CAAE,UAAA,CAAW,eAAe,CAAA,EAAG;AAC1C,IAAA,uBAAO,GAAA,CAAC,eAAA,EAAA,EAAgB,OAAA,EAAS,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA;AAAA,EAC/C;AAEA,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,aAAA,EAAY,gBAAA,EACf,QAAA,kBAAA,GAAA,CAAC,WAAA,EAAA,EAAY,IAAA,EAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,QAAA,EAAS,WAAA,EAAY,CAAA,EACtD,CAAA;AAEJ;;;;"}
@@ -5,9 +5,9 @@ import DialogActions from '@mui/material/DialogActions';
5
5
  import DialogContent from '@mui/material/DialogContent';
6
6
  import DialogTitle from '@mui/material/DialogTitle';
7
7
  import Box from '@mui/material/Box';
8
- import { FetchComponent } from '../FetchComponent/FetchComponent.esm.js';
8
+ import { ReadmeContent } from '../ReadmeContent/ReadmeContent.esm.js';
9
9
 
10
- const ReadmeDialog = ({ open, onClose }) => {
10
+ const ReadmeDialog = ({ open, onClose, ...contentProps }) => {
11
11
  return /* @__PURE__ */ jsxs(
12
12
  Dialog,
13
13
  {
@@ -17,7 +17,7 @@ const ReadmeDialog = ({ open, onClose }) => {
17
17
  "data-testid": "content-dialog",
18
18
  children: [
19
19
  /* @__PURE__ */ jsx(DialogTitle, { children: "README" }),
20
- /* @__PURE__ */ jsx(DialogContent, { dividers: true, children: /* @__PURE__ */ jsx(Box, { minWidth: 650, children: /* @__PURE__ */ jsx(FetchComponent, {}) }) }),
20
+ /* @__PURE__ */ jsx(DialogContent, { dividers: true, children: /* @__PURE__ */ jsx(Box, { minWidth: 650, children: /* @__PURE__ */ jsx(ReadmeContent, { ...contentProps }) }) }),
21
21
  /* @__PURE__ */ jsx(DialogActions, { children: /* @__PURE__ */ jsx(
22
22
  Button,
23
23
  {
@@ -1 +1 @@
1
- {"version":3,"file":"ReadmeDialog.esm.js","sources":["../../../src/components/ReadmeDialog/ReadmeDialog.tsx"],"sourcesContent":["import Button from '@mui/material/Button';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport Box from '@mui/material/Box';\nimport { FetchComponent } from '../FetchComponent';\n\ntype Props = {\n open: boolean;\n onClose: () => void;\n};\n\nexport const ReadmeDialog = ({ open, onClose }: Props) => {\n return (\n <Dialog\n maxWidth=\"md\"\n open={open}\n onClose={onClose}\n data-testid=\"content-dialog\"\n >\n <DialogTitle>README</DialogTitle>\n <DialogContent dividers>\n <Box minWidth={650}>\n <FetchComponent />\n </Box>\n </DialogContent>\n <DialogActions>\n <Button\n variant=\"contained\"\n color=\"primary\"\n aria-label=\"close\"\n onClick={onClose}\n >\n Close\n </Button>\n </DialogActions>\n </Dialog>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;AAaO,MAAM,YAAA,GAAe,CAAC,EAAE,IAAA,EAAM,SAAQ,KAAa;AACxD,EAAA,uBACE,IAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,QAAA,EAAS,IAAA;AAAA,MACT,IAAA;AAAA,MACA,OAAA;AAAA,MACA,aAAA,EAAY,gBAAA;AAAA,MAEZ,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,eAAY,QAAA,EAAA,QAAA,EAAM,CAAA;AAAA,wBACnB,GAAA,CAAC,aAAA,EAAA,EAAc,QAAA,EAAQ,IAAA,EACrB,QAAA,kBAAA,GAAA,CAAC,GAAA,EAAA,EAAI,QAAA,EAAU,GAAA,EACb,QAAA,kBAAA,GAAA,CAAC,cAAA,EAAA,EAAe,CAAA,EAClB,CAAA,EACF,CAAA;AAAA,4BACC,aAAA,EAAA,EACC,QAAA,kBAAA,GAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,OAAA,EAAQ,WAAA;AAAA,YACR,KAAA,EAAM,SAAA;AAAA,YACN,YAAA,EAAW,OAAA;AAAA,YACX,OAAA,EAAS,OAAA;AAAA,YACV,QAAA,EAAA;AAAA;AAAA,SAED,EACF;AAAA;AAAA;AAAA,GACF;AAEJ;;;;"}
1
+ {"version":3,"file":"ReadmeDialog.esm.js","sources":["../../../src/components/ReadmeDialog/ReadmeDialog.tsx"],"sourcesContent":["import Button from '@mui/material/Button';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport Box from '@mui/material/Box';\nimport { ReadmeContent } from '../ReadmeContent';\nimport type { ReadmeContentProps } from '../ReadmeContent/ReadmeContent';\n\ntype Props = {\n open: boolean;\n onClose: () => void;\n} & ReadmeContentProps;\n\nexport const ReadmeDialog = ({ open, onClose, ...contentProps }: Props) => {\n return (\n <Dialog\n maxWidth=\"md\"\n open={open}\n onClose={onClose}\n data-testid=\"content-dialog\"\n >\n <DialogTitle>README</DialogTitle>\n <DialogContent dividers>\n <Box minWidth={650}>\n <ReadmeContent {...contentProps} />\n </Box>\n </DialogContent>\n <DialogActions>\n <Button\n variant=\"contained\"\n color=\"primary\"\n aria-label=\"close\"\n onClick={onClose}\n >\n Close\n </Button>\n </DialogActions>\n </Dialog>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;AAcO,MAAM,eAAe,CAAC,EAAE,MAAM,OAAA,EAAS,GAAG,cAAa,KAAa;AACzE,EAAA,uBACE,IAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,QAAA,EAAS,IAAA;AAAA,MACT,IAAA;AAAA,MACA,OAAA;AAAA,MACA,aAAA,EAAY,gBAAA;AAAA,MAEZ,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,eAAY,QAAA,EAAA,QAAA,EAAM,CAAA;AAAA,wBACnB,GAAA,CAAC,aAAA,EAAA,EAAc,QAAA,EAAQ,IAAA,EACrB,QAAA,kBAAA,GAAA,CAAC,GAAA,EAAA,EAAI,QAAA,EAAU,GAAA,EACb,QAAA,kBAAA,GAAA,CAAC,aAAA,EAAA,EAAe,GAAG,YAAA,EAAc,GACnC,CAAA,EACF,CAAA;AAAA,4BACC,aAAA,EAAA,EACC,QAAA,kBAAA,GAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,OAAA,EAAQ,WAAA;AAAA,YACR,KAAA,EAAM,SAAA;AAAA,YACN,YAAA,EAAW,OAAA;AAAA,YACX,OAAA,EAAS,OAAA;AAAA,YACV,QAAA,EAAA;AAAA;AAAA,SAED,EACF;AAAA;AAAA;AAAA,GACF;AAEJ;;;;"}
@@ -1,22 +1,22 @@
1
1
  import { useLocation, useNavigate } from 'react-router-dom';
2
- import { useMemo } from 'react';
2
+ import { useCallback } from 'react';
3
3
 
4
4
  const useFullViewParam = () => {
5
5
  const location = useLocation();
6
6
  const navigate = useNavigate();
7
- const isFullView = useMemo(() => {
8
- const params = new URLSearchParams(location.search);
9
- return params.get("fullView") === "true";
10
- }, [location.search]);
11
- const setFullView = (open) => {
12
- const params = new URLSearchParams(location.search);
13
- if (open) {
14
- params.set("fullView", "true");
15
- } else {
16
- params.delete("fullView");
17
- }
18
- navigate({ search: params.toString() }, { replace: true });
19
- };
7
+ const isFullView = new URLSearchParams(location.search).get("fullView") === "true";
8
+ const setFullView = useCallback(
9
+ (open) => {
10
+ const params = new URLSearchParams(location.search);
11
+ if (open) {
12
+ params.set("fullView", "true");
13
+ } else {
14
+ params.delete("fullView");
15
+ }
16
+ navigate({ search: params.toString() }, { replace: true });
17
+ },
18
+ [location.search, navigate]
19
+ );
20
20
  return [isFullView, setFullView];
21
21
  };
22
22
 
@@ -1 +1 @@
1
- {"version":3,"file":"useFullViewParam.esm.js","sources":["../../src/hooks/useFullViewParam.ts"],"sourcesContent":["import { useLocation, useNavigate } from 'react-router-dom';\nimport { useMemo } from 'react';\n\nexport const useFullViewParam = (): [boolean, (open: boolean) => void] => {\n const location = useLocation();\n const navigate = useNavigate();\n\n const isFullView = useMemo(() => {\n const params = new URLSearchParams(location.search);\n return params.get('fullView') === 'true';\n }, [location.search]);\n\n const setFullView = (open: boolean) => {\n const params = new URLSearchParams(location.search);\n if (open) {\n params.set('fullView', 'true');\n } else {\n params.delete('fullView');\n }\n navigate({ search: params.toString() }, { replace: true });\n };\n\n return [isFullView, setFullView];\n};\n"],"names":[],"mappings":";;;AAGO,MAAM,mBAAmB,MAA0C;AACxE,EAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,EAAA,MAAM,WAAW,WAAA,EAAY;AAE7B,EAAA,MAAM,UAAA,GAAa,QAAQ,MAAM;AAC/B,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,QAAA,CAAS,MAAM,CAAA;AAClD,IAAA,OAAO,MAAA,CAAO,GAAA,CAAI,UAAU,CAAA,KAAM,MAAA;AAAA,EACpC,CAAA,EAAG,CAAC,QAAA,CAAS,MAAM,CAAC,CAAA;AAEpB,EAAA,MAAM,WAAA,GAAc,CAAC,IAAA,KAAkB;AACrC,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,QAAA,CAAS,MAAM,CAAA;AAClD,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,MAAA,CAAO,GAAA,CAAI,YAAY,MAAM,CAAA;AAAA,IAC/B,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,OAAO,UAAU,CAAA;AAAA,IAC1B;AACA,IAAA,QAAA,CAAS,EAAE,QAAQ,MAAA,CAAO,QAAA,IAAW,EAAG,EAAE,OAAA,EAAS,IAAA,EAAM,CAAA;AAAA,EAC3D,CAAA;AAEA,EAAA,OAAO,CAAC,YAAY,WAAW,CAAA;AACjC;;;;"}
1
+ {"version":3,"file":"useFullViewParam.esm.js","sources":["../../src/hooks/useFullViewParam.ts"],"sourcesContent":["import { useLocation, useNavigate } from 'react-router-dom';\nimport { useCallback } from 'react';\n\nexport const useFullViewParam = (): [boolean, (open: boolean) => void] => {\n const location = useLocation();\n const navigate = useNavigate();\n\n const isFullView =\n new URLSearchParams(location.search).get('fullView') === 'true';\n\n const setFullView = useCallback(\n (open: boolean) => {\n const params = new URLSearchParams(location.search);\n if (open) {\n params.set('fullView', 'true');\n } else {\n params.delete('fullView');\n }\n navigate({ search: params.toString() }, { replace: true });\n },\n [location.search, navigate],\n );\n\n return [isFullView, setFullView];\n};\n"],"names":[],"mappings":";;;AAGO,MAAM,mBAAmB,MAA0C;AACxE,EAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,EAAA,MAAM,WAAW,WAAA,EAAY;AAE7B,EAAA,MAAM,UAAA,GACJ,IAAI,eAAA,CAAgB,QAAA,CAAS,MAAM,CAAA,CAAE,GAAA,CAAI,UAAU,CAAA,KAAM,MAAA;AAE3D,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAClB,CAAC,IAAA,KAAkB;AACjB,MAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,QAAA,CAAS,MAAM,CAAA;AAClD,MAAA,IAAI,IAAA,EAAM;AACR,QAAA,MAAA,CAAO,GAAA,CAAI,YAAY,MAAM,CAAA;AAAA,MAC/B,CAAA,MAAO;AACL,QAAA,MAAA,CAAO,OAAO,UAAU,CAAA;AAAA,MAC1B;AACA,MAAA,QAAA,CAAS,EAAE,QAAQ,MAAA,CAAO,QAAA,IAAW,EAAG,EAAE,OAAA,EAAS,IAAA,EAAM,CAAA;AAAA,IAC3D,CAAA;AAAA,IACA,CAAC,QAAA,CAAS,MAAA,EAAQ,QAAQ;AAAA,GAC5B;AAEA,EAAA,OAAO,CAAC,YAAY,WAAW,CAAA;AACjC;;;;"}
@@ -0,0 +1,29 @@
1
+ import { useApi } from '@backstage/core-plugin-api';
2
+ import { useEntity } from '@backstage/plugin-catalog-react';
3
+ import { getEntitySourceLocation, stringifyEntityRef } from '@backstage/catalog-model';
4
+ import useAsync from 'react-use/lib/useAsync';
5
+ import { readmeApiRef } from '../api/ReadmeApi.esm.js';
6
+
7
+ const useReadmeContent = () => {
8
+ const { entity } = useEntity();
9
+ const readmeApi = useApi(readmeApiRef);
10
+ let location;
11
+ try {
12
+ const { target } = getEntitySourceLocation(entity);
13
+ location = target;
14
+ } catch {
15
+ location = ": Unknown";
16
+ }
17
+ const {
18
+ value: content,
19
+ loading,
20
+ error
21
+ } = useAsync(
22
+ async () => await readmeApi.getReadmeContent(stringifyEntityRef(entity)),
23
+ [entity]
24
+ );
25
+ return { content, loading, error, location };
26
+ };
27
+
28
+ export { useReadmeContent };
29
+ //# sourceMappingURL=useReadmeContent.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useReadmeContent.esm.js","sources":["../../src/hooks/useReadmeContent.ts"],"sourcesContent":["import { useApi } from '@backstage/core-plugin-api';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport {\n stringifyEntityRef,\n getEntitySourceLocation,\n} from '@backstage/catalog-model';\nimport useAsync from 'react-use/lib/useAsync';\nimport { readmeApiRef } from '../api/ReadmeApi';\n\nexport type UseReadmeContentResult = {\n content: string[] | undefined;\n loading: boolean;\n error: Error | undefined;\n location: string;\n};\n\nexport const useReadmeContent = (): UseReadmeContentResult => {\n const { entity } = useEntity();\n const readmeApi = useApi(readmeApiRef);\n\n let location: string;\n try {\n const { target } = getEntitySourceLocation(entity);\n location = target;\n } catch {\n location = ': Unknown';\n }\n\n const {\n value: content,\n loading,\n error,\n } = useAsync(\n async (): Promise<string[]> =>\n await readmeApi.getReadmeContent(stringifyEntityRef(entity)),\n [entity],\n );\n\n return { content, loading, error, location };\n};\n"],"names":[],"mappings":";;;;;;AAgBO,MAAM,mBAAmB,MAA8B;AAC5D,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,SAAA,EAAU;AAC7B,EAAA,MAAM,SAAA,GAAY,OAAO,YAAY,CAAA;AAErC,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,MAAA,EAAO,GAAI,uBAAA,CAAwB,MAAM,CAAA;AACjD,IAAA,QAAA,GAAW,MAAA;AAAA,EACb,CAAA,CAAA,MAAQ;AACN,IAAA,QAAA,GAAW,WAAA;AAAA,EACb;AAEA,EAAA,MAAM;AAAA,IACJ,KAAA,EAAO,OAAA;AAAA,IACP,OAAA;AAAA,IACA;AAAA,GACF,GAAI,QAAA;AAAA,IACF,YACE,MAAM,SAAA,CAAU,gBAAA,CAAiB,kBAAA,CAAmB,MAAM,CAAC,CAAA;AAAA,IAC7D,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,OAAO,EAAE,OAAA,EAAS,OAAA,EAAS,KAAA,EAAO,QAAA,EAAS;AAC7C;;;;"}
package/dist/index.d.ts CHANGED
@@ -13,16 +13,27 @@ declare const readmePlugin: _backstage_core_plugin_api.BackstagePlugin<{
13
13
  /**
14
14
  * Readme card exported from the Readme plugin
15
15
  * @public */
16
- declare const ReadmeCard: (props: ReadmeCardProps) => react_jsx_runtime.JSX.Element;
16
+ declare const ReadmeCard: (props: ReadmeCardProps | ReadmeCardLegacyProps) => react_jsx_runtime.JSX.Element;
17
17
 
18
18
  /**
19
- * ReadmeCardProps props.
20
- *
19
+ * Props for the ReadmeCard component.
21
20
  * @public
22
21
  */
23
22
  type ReadmeCardProps = {
23
+ hideIfNotFound?: boolean;
24
+ maxHeight?: string | number;
25
+ };
26
+ /**
27
+ * Props for the legacy MUI-based rendering of ReadmeCard.
28
+ * @deprecated Use {@link ReadmeCardProps} instead.
29
+ * @public
30
+ */
31
+ type ReadmeCardLegacyProps = {
32
+ /** @deprecated Use the new {@link ReadmeCardProps} instead. */
24
33
  variant?: InfoCardVariants;
34
+ /** @deprecated Use the new {@link ReadmeCardProps} instead. */
25
35
  maxHeight?: string | number;
36
+ hideIfNotFound?: boolean;
26
37
  };
27
38
 
28
39
  /**
@@ -37,4 +48,4 @@ declare const isReadmeAvailable: (entity: Entity, context: {
37
48
  apis: ApiHolder;
38
49
  }) => Promise<boolean>;
39
50
 
40
- export { ReadmeCard, type ReadmeCardProps, isReadmeAvailable, readmePlugin };
51
+ export { ReadmeCard, type ReadmeCardLegacyProps, type ReadmeCardProps, isReadmeAvailable, readmePlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axis-backstage/plugin-readme",
3
- "version": "0.18.2",
3
+ "version": "0.19.1",
4
4
  "exports": {
5
5
  ".": {
6
6
  "import": "./dist/index.esm.js",
@@ -66,7 +66,8 @@
66
66
  "@backstage/theme": "^0.7.3",
67
67
  "@mui/icons-material": "^5.15.7",
68
68
  "@mui/material": "^5.15.7",
69
- "react-use": "^17.2.4"
69
+ "react-use": "^17.2.4",
70
+ "zod": "^4.3.6"
70
71
  },
71
72
  "peerDependencies": {
72
73
  "@types/react": "^17.0.0 || ^18.0.0",
@@ -83,7 +84,7 @@
83
84
  "@testing-library/jest-dom": "^6.0.0",
84
85
  "@testing-library/react": "^16.0.0",
85
86
  "@testing-library/user-event": "^14.0.0",
86
- "msw": "^1.3.5"
87
+ "msw": "^2.7.3"
87
88
  },
88
89
  "files": [
89
90
  "dist"
@@ -1,65 +0,0 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { useEntity } from '@backstage/plugin-catalog-react';
3
- import { useApi } from '@backstage/core-plugin-api';
4
- import { Progress, Link, ResponseErrorPanel, ErrorPanel, MarkdownContent, CodeSnippet } from '@backstage/core-components';
5
- import useAsync from 'react-use/lib/useAsync';
6
- import { readmeApiRef } from '../../api/ReadmeApi.esm.js';
7
- import { getEntitySourceLocation, stringifyEntityRef } from '@backstage/catalog-model';
8
- import Box from '@mui/material/Box';
9
- import Typography from '@mui/material/Typography';
10
- import { ResponseError } from '@backstage/errors';
11
-
12
- const FetchComponent = () => {
13
- const { entity } = useEntity();
14
- const readmeApi = useApi(readmeApiRef);
15
- let location;
16
- try {
17
- const { target } = getEntitySourceLocation(entity);
18
- location = target ?? target;
19
- } catch (err) {
20
- location = ": Unknown";
21
- }
22
- const {
23
- value: content,
24
- loading,
25
- error
26
- } = useAsync(
27
- async () => await readmeApi.getReadmeContent(stringifyEntityRef(entity)),
28
- [entity]
29
- );
30
- if (loading) {
31
- return /* @__PURE__ */ jsx(Progress, {});
32
- }
33
- if (error instanceof ResponseError) {
34
- if (error.statusCode === 404) {
35
- return /* @__PURE__ */ jsxs(Box, { children: [
36
- /* @__PURE__ */ jsxs(Typography, { pb: 2, variant: "body2", children: [
37
- "No README.md file found at source location:",
38
- " ",
39
- location && /* @__PURE__ */ jsx("strong", { children: location })
40
- ] }),
41
- /* @__PURE__ */ jsxs(Typography, { variant: "body2", children: [
42
- "Need help? Go to our",
43
- " ",
44
- /* @__PURE__ */ jsx(Link, { to: "https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md", children: "documentation" })
45
- ] })
46
- ] });
47
- }
48
- return /* @__PURE__ */ jsx(ResponseErrorPanel, { error });
49
- }
50
- if (error) {
51
- return /* @__PURE__ */ jsx(ErrorPanel, { error });
52
- }
53
- if (!content) {
54
- return /* @__PURE__ */ jsx(ErrorPanel, { error: Error("Unknown error") });
55
- }
56
- if (!content[1] || content[1] === "error") {
57
- return /* @__PURE__ */ jsx(ErrorPanel, { error: Error(content[1] ?? "Unknown error") });
58
- } else if (content[1].startsWith("text/markdown")) {
59
- return /* @__PURE__ */ jsx(MarkdownContent, { content: content[0] });
60
- }
61
- return /* @__PURE__ */ jsx("div", { "data-testid": "readme-content", children: /* @__PURE__ */ jsx(CodeSnippet, { text: content[0], language: "plaintext" }) });
62
- };
63
-
64
- export { FetchComponent };
65
- //# sourceMappingURL=FetchComponent.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FetchComponent.esm.js","sources":["../../../src/components/FetchComponent/FetchComponent.tsx"],"sourcesContent":["import { useEntity } from '@backstage/plugin-catalog-react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n CodeSnippet,\n ErrorPanel,\n Link,\n MarkdownContent,\n Progress,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport useAsync from 'react-use/lib/useAsync';\nimport { readmeApiRef } from '../../api/ReadmeApi';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { getEntitySourceLocation } from '@backstage/catalog-model';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport { ResponseError } from '@backstage/errors';\n\nexport const FetchComponent = () => {\n const { entity } = useEntity();\n const readmeApi = useApi(readmeApiRef);\n let location;\n\n try {\n const { target } = getEntitySourceLocation(entity);\n location = target ?? target;\n } catch (err) {\n location = ': Unknown';\n }\n\n const {\n value: content,\n loading,\n error,\n } = useAsync(\n async (): Promise<string[]> =>\n await readmeApi.getReadmeContent(stringifyEntityRef(entity)),\n [entity],\n );\n\n if (loading) {\n return <Progress />;\n }\n\n if (error instanceof ResponseError) {\n if (error.statusCode === 404) {\n return (\n <Box>\n <Typography pb={2} variant=\"body2\">\n No README.md file found at source location:{' '}\n {location && <strong>{location}</strong>}\n </Typography>\n <Typography variant=\"body2\">\n Need help? Go to our{' '}\n <Link to=\"https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md\">\n documentation\n </Link>\n </Typography>\n </Box>\n );\n }\n return <ResponseErrorPanel error={error} />;\n }\n\n if (error) {\n return <ErrorPanel error={error} />;\n }\n if (!content) {\n return <ErrorPanel error={Error('Unknown error')} />;\n }\n if (!content[1] || content[1] === 'error') {\n return <ErrorPanel error={Error(content[1] ?? 'Unknown error')} />;\n } else if (content[1].startsWith('text/markdown')) {\n return <MarkdownContent content={content[0]} />;\n }\n // probably text/plain\n return (\n <div data-testid=\"readme-content\">\n <CodeSnippet text={content[0]} language=\"plaintext\" />\n </div>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;AAkBO,MAAM,iBAAiB,MAAM;AAClC,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,SAAA,EAAU;AAC7B,EAAA,MAAM,SAAA,GAAY,OAAO,YAAY,CAAA;AACrC,EAAA,IAAI,QAAA;AAEJ,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,MAAA,EAAO,GAAI,uBAAA,CAAwB,MAAM,CAAA;AACjD,IAAA,QAAA,GAAW,MAAA,IAAU,MAAA;AAAA,EACvB,SAAS,GAAA,EAAK;AACZ,IAAA,QAAA,GAAW,WAAA;AAAA,EACb;AAEA,EAAA,MAAM;AAAA,IACJ,KAAA,EAAO,OAAA;AAAA,IACP,OAAA;AAAA,IACA;AAAA,GACF,GAAI,QAAA;AAAA,IACF,YACE,MAAM,SAAA,CAAU,gBAAA,CAAiB,kBAAA,CAAmB,MAAM,CAAC,CAAA;AAAA,IAC7D,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,2BAAQ,QAAA,EAAA,EAAS,CAAA;AAAA,EACnB;AAEA,EAAA,IAAI,iBAAiB,aAAA,EAAe;AAClC,IAAA,IAAI,KAAA,CAAM,eAAe,GAAA,EAAK;AAC5B,MAAA,4BACG,GAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,UAAA,EAAA,EAAW,EAAA,EAAI,CAAA,EAAG,OAAA,EAAQ,OAAA,EAAQ,QAAA,EAAA;AAAA,UAAA,6CAAA;AAAA,UACW,GAAA;AAAA,UAC3C,QAAA,oBAAY,GAAA,CAAC,QAAA,EAAA,EAAQ,QAAA,EAAA,QAAA,EAAS;AAAA,SAAA,EACjC,CAAA;AAAA,wBACA,IAAA,CAAC,UAAA,EAAA,EAAW,OAAA,EAAQ,OAAA,EAAQ,QAAA,EAAA;AAAA,UAAA,sBAAA;AAAA,UACL,GAAA;AAAA,0BACrB,GAAA,CAAC,IAAA,EAAA,EAAK,EAAA,EAAG,4FAAA,EAA6F,QAAA,EAAA,eAAA,EAEtG;AAAA,SAAA,EACF;AAAA,OAAA,EACF,CAAA;AAAA,IAEJ;AACA,IAAA,uBAAO,GAAA,CAAC,sBAAmB,KAAA,EAAc,CAAA;AAAA,EAC3C;AAEA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,uBAAO,GAAA,CAAC,cAAW,KAAA,EAAc,CAAA;AAAA,EACnC;AACA,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,uBAAO,GAAA,CAAC,UAAA,EAAA,EAAW,KAAA,EAAO,KAAA,CAAM,eAAe,CAAA,EAAG,CAAA;AAAA,EACpD;AACA,EAAA,IAAI,CAAC,OAAA,CAAQ,CAAC,KAAK,OAAA,CAAQ,CAAC,MAAM,OAAA,EAAS;AACzC,IAAA,uBAAO,GAAA,CAAC,cAAW,KAAA,EAAO,KAAA,CAAM,QAAQ,CAAC,CAAA,IAAK,eAAe,CAAA,EAAG,CAAA;AAAA,EAClE,WAAW,OAAA,CAAQ,CAAC,CAAA,CAAE,UAAA,CAAW,eAAe,CAAA,EAAG;AACjD,IAAA,uBAAO,GAAA,CAAC,eAAA,EAAA,EAAgB,OAAA,EAAS,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA;AAAA,EAC/C;AAEA,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,aAAA,EAAY,gBAAA,EACf,QAAA,kBAAA,GAAA,CAAC,WAAA,EAAA,EAAY,IAAA,EAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,QAAA,EAAS,WAAA,EAAY,CAAA,EACtD,CAAA;AAEJ;;;;"}