@backstage/plugin-home 0.5.9-next.0 → 0.5.9-next.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index-e7416f2d.esm.js","sources":["../../src/components/HomepageCompositionRoot.tsx"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { ReactNode } from 'react';\nimport { useOutlet } from 'react-router-dom';\n\nexport const HomepageCompositionRoot = (props: {\n title?: string;\n children?: ReactNode;\n}) => {\n const outlet = useOutlet();\n const children = props.children ?? outlet;\n return <>{children}</>;\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBa,MAAA,uBAAA,GAA0B,CAAC,KAGlC,KAAA;AAtBN,EAAA,IAAA,EAAA,CAAA;AAuBE,EAAA,MAAM,SAAS,SAAU,EAAA,CAAA;AACzB,EAAM,MAAA,QAAA,GAAA,CAAW,EAAM,GAAA,KAAA,CAAA,QAAA,KAAN,IAAkB,GAAA,EAAA,GAAA,MAAA,CAAA;AACnC,EAAA,iEAAU,QAAS,CAAA,CAAA;AACrB;;;;"}
1
+ {"version":3,"file":"index-20932a73.esm.js","sources":["../../src/components/HomepageCompositionRoot.tsx"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { ReactNode } from 'react';\nimport { useOutlet } from 'react-router-dom';\n\nexport const HomepageCompositionRoot = (props: {\n title?: string;\n children?: ReactNode;\n}) => {\n const outlet = useOutlet();\n const children = props.children ?? outlet;\n return <>{children}</>;\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBa,MAAA,uBAAA,GAA0B,CAAC,KAGlC,KAAA;AAtBN,EAAA,IAAA,EAAA,CAAA;AAuBE,EAAA,MAAM,SAAS,SAAU,EAAA,CAAA;AACzB,EAAM,MAAA,QAAA,GAAA,CAAW,EAAM,GAAA,KAAA,CAAA,QAAA,KAAN,IAAkB,GAAA,EAAA,GAAA,MAAA,CAAA;AACnC,EAAA,iEAAU,QAAS,CAAA,CAAA;AACrB;;;;"}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { createCardExtension as createCardExtension$1, CardExtensionProps as Car
4
4
  import * as React from 'react';
5
5
  import React__default, { ReactNode, ReactElement } from 'react';
6
6
  import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
7
+ import { StorageApi, IdentityApi, ErrorApi } from '@backstage/core-plugin-api';
7
8
 
8
9
  /** @public */
9
10
  type Tool = {
@@ -32,6 +33,114 @@ type WelcomeTitleLanguageProps = {
32
33
  language?: string[];
33
34
  };
34
35
 
36
+ /**
37
+ * @public
38
+ * Model for a visit entity.
39
+ */
40
+ type Visit = {
41
+ /**
42
+ * The auto-generated visit identification.
43
+ */
44
+ id: string;
45
+ /**
46
+ * The visited entity, usually an entity id.
47
+ */
48
+ name: string;
49
+ /**
50
+ * The visited url pathname, usually the entity route.
51
+ */
52
+ pathname: string;
53
+ /**
54
+ * An individual view count.
55
+ */
56
+ hits: number;
57
+ /**
58
+ * Last date and time of visit. Format: unix epoch in ms.
59
+ */
60
+ timestamp: number;
61
+ /**
62
+ * Optional entity reference. See stringifyEntityRef from catalog-model.
63
+ */
64
+ entityRef?: string;
65
+ };
66
+ /**
67
+ * @public
68
+ * This data structure represents the parameters associated with search queries for visits.
69
+ */
70
+ type VisitsApiQueryParams = {
71
+ /**
72
+ * Limits the number of results returned. The default is 8.
73
+ */
74
+ limit?: number;
75
+ /**
76
+ * Allows ordering visits on entity properties.
77
+ * @example
78
+ * Sort ascending by the timestamp field.
79
+ * ```
80
+ * { orderBy: [{ field: 'timestamp', direction: 'asc' }] }
81
+ * ```
82
+ */
83
+ orderBy?: Array<{
84
+ field: keyof Visit;
85
+ direction: 'asc' | 'desc';
86
+ }>;
87
+ /**
88
+ * Allows filtering visits on entity properties.
89
+ * @example
90
+ * Most popular docs on the past 7 days
91
+ * ```
92
+ * {
93
+ * orderBy: [{ field: 'hits', direction: 'desc' }],
94
+ * filterBy: [
95
+ * { field: 'timestamp', operator: '>=', value: <date> },
96
+ * { field: 'entityRef', operator: 'contains', value: 'docs' }
97
+ * ]
98
+ * }
99
+ * ```
100
+ */
101
+ filterBy?: Array<{
102
+ field: keyof Visit;
103
+ operator: '<' | '<=' | '==' | '!=' | '>' | '>=' | 'contains';
104
+ value: string | number;
105
+ }>;
106
+ };
107
+ /**
108
+ * @public
109
+ * This data structure represents the parameters associated with saving visits.
110
+ */
111
+ type VisitsApiSaveParams = {
112
+ visit: Omit<Visit, 'id' | 'hits' | 'timestamp'>;
113
+ };
114
+ /**
115
+ * @public
116
+ * Visits API public contract.
117
+ */
118
+ interface VisitsApi {
119
+ /**
120
+ * Persist a new visit.
121
+ * @param pageVisit - a new visit data
122
+ */
123
+ save(saveParams: VisitsApiSaveParams): Promise<Visit>;
124
+ /**
125
+ * Get user visits.
126
+ * @param queryParams - optional search query params.
127
+ */
128
+ list(queryParams?: VisitsApiQueryParams): Promise<Visit[]>;
129
+ }
130
+ /** @public */
131
+ declare const visitsApiRef: _backstage_core_plugin_api.ApiRef<VisitsApi>;
132
+
133
+ /** @public */
134
+ type VisitedByTypeKind = 'recent' | 'top';
135
+ /** @public */
136
+ type VisitedByTypeProps = {
137
+ visits?: Array<Visit>;
138
+ numVisitsOpen?: number;
139
+ numVisitsTotal?: number;
140
+ loading?: boolean;
141
+ kind: VisitedByTypeKind;
142
+ };
143
+
35
144
  /** @public */
36
145
  declare const homePlugin: _backstage_core_plugin_api.BackstagePlugin<{
37
146
  root: _backstage_core_plugin_api.RouteRef<undefined>;
@@ -104,6 +213,16 @@ declare const HeaderWorldClock: (props: {
104
213
  clockConfigs: ClockConfig[];
105
214
  customTimeFormat?: Intl.DateTimeFormatOptions | undefined;
106
215
  }) => React.JSX.Element | null;
216
+ /**
217
+ * Display top visited pages for the homepage
218
+ * @public
219
+ */
220
+ declare const HomePageTopVisited: (props: _backstage_plugin_home_react.CardExtensionProps<Partial<VisitedByTypeProps>>) => React.JSX.Element;
221
+ /**
222
+ * Display recently visited pages for the homepage
223
+ * @public
224
+ */
225
+ declare const HomePageRecentlyVisited: (props: _backstage_plugin_home_react.CardExtensionProps<Partial<VisitedByTypeProps>>) => React.JSX.Element;
107
226
 
108
227
  /**
109
228
  * Breakpoint options for <CustomHomepageGridProps/>
@@ -202,6 +321,21 @@ type LayoutConfiguration = {
202
321
  */
203
322
  declare const CustomHomepageGrid: (props: CustomHomepageGridProps) => React__default.JSX.Element;
204
323
 
324
+ /**
325
+ * @public
326
+ * Component responsible for listening to location changes and calling
327
+ * the visitsApi to save visits.
328
+ */
329
+ declare const VisitListener: ({ children, toEntityRef, visitName, }: {
330
+ children?: React__default.ReactNode;
331
+ toEntityRef?: (({ pathname }: {
332
+ pathname: string;
333
+ }) => string | undefined) | undefined;
334
+ visitName?: (({ pathname }: {
335
+ pathname: string;
336
+ }) => string) | undefined;
337
+ }) => JSX.Element;
338
+
205
339
  /** @public */
206
340
  declare const TemplateBackstageLogo: (props: {
207
341
  classes: {
@@ -264,4 +398,50 @@ declare const SettingsModal: (props: {
264
398
  children: JSX.Element;
265
399
  }) => React.JSX.Element;
266
400
 
267
- export { Breakpoint, CardConfig, CardExtensionProps, CardLayout, CardSettings, ClockConfig, ComponentAccordion, ComponentParts, ComponentRenderer, ComponentTab, ComponentTabs, CustomHomepageGrid, CustomHomepageGridProps, HeaderWorldClock, HomePageCompanyLogo, HomePageRandomJoke, HomePageStarredEntities, HomePageToolkit, HomepageCompositionRoot, LayoutConfiguration, RendererProps, SettingsModal, TemplateBackstageLogo, TemplateBackstageLogoIcon, Tool, ToolkitContentProps, WelcomeTitle, WelcomeTitleLanguageProps, createCardExtension, homePlugin };
401
+ /** @public */
402
+ type VisitsStorageApiOptions = {
403
+ limit?: number;
404
+ storageApi: StorageApi;
405
+ identityApi: IdentityApi;
406
+ };
407
+ /**
408
+ * @public
409
+ * This is an implementation of VisitsApi that relies on a StorageApi.
410
+ * Beware that filtering and ordering are done in memory therefore it is
411
+ * prudent to keep limit to a reasonable size.
412
+ */
413
+ declare class VisitsStorageApi implements VisitsApi {
414
+ private readonly limit;
415
+ private readonly storageApi;
416
+ private readonly storageKeyPrefix;
417
+ private readonly identityApi;
418
+ static create(options: VisitsStorageApiOptions): VisitsStorageApi;
419
+ private constructor();
420
+ /**
421
+ * Returns a list of visits through the visitsApi
422
+ */
423
+ list(queryParams?: VisitsApiQueryParams): Promise<Visit[]>;
424
+ /**
425
+ * Saves a visit through the visitsApi
426
+ */
427
+ save(saveParams: VisitsApiSaveParams): Promise<Visit>;
428
+ private persistAll;
429
+ private retrieveAll;
430
+ private compare;
431
+ }
432
+
433
+ /** @public */
434
+ type VisitsWebStorageApiOptions = {
435
+ limit?: number;
436
+ identityApi: IdentityApi;
437
+ errorApi: ErrorApi;
438
+ };
439
+ /**
440
+ * @public
441
+ * This is a reference implementation of VisitsApi using WebStorage.
442
+ */
443
+ declare class VisitsWebStorageApi {
444
+ static create(options: VisitsWebStorageApiOptions): VisitsStorageApi;
445
+ }
446
+
447
+ export { Breakpoint, CardConfig, CardExtensionProps, CardLayout, CardSettings, ClockConfig, ComponentAccordion, ComponentParts, ComponentRenderer, ComponentTab, ComponentTabs, CustomHomepageGrid, CustomHomepageGridProps, HeaderWorldClock, HomePageCompanyLogo, HomePageRandomJoke, HomePageRecentlyVisited, HomePageStarredEntities, HomePageToolkit, HomePageTopVisited, HomepageCompositionRoot, LayoutConfiguration, RendererProps, SettingsModal, TemplateBackstageLogo, TemplateBackstageLogoIcon, Tool, ToolkitContentProps, Visit, VisitListener, VisitedByTypeKind, VisitedByTypeProps, VisitsApi, VisitsApiQueryParams, VisitsApiSaveParams, VisitsStorageApi, VisitsStorageApiOptions, VisitsWebStorageApi, VisitsWebStorageApiOptions, WelcomeTitle, WelcomeTitleLanguageProps, createCardExtension, homePlugin, visitsApiRef };
package/dist/index.esm.js CHANGED
@@ -1,7 +1,8 @@
1
- import { createRouteRef, createPlugin, createRoutableExtension, createComponentExtension, useElementFilter, useApi, storageApiRef, getComponentData } from '@backstage/core-plugin-api';
1
+ import { createRouteRef, createApiRef, createPlugin, createApiFactory, storageApiRef, identityApiRef, createRoutableExtension, createComponentExtension, useElementFilter, useApi, getComponentData } from '@backstage/core-plugin-api';
2
2
  import { createCardExtension as createCardExtension$1, SettingsModal as SettingsModal$1 } from '@backstage/plugin-home-react';
3
- import React, { useCallback, useMemo } from 'react';
4
- import 'react-router-dom';
3
+ import { WebStorage } from '@backstage/core-app-api';
4
+ import React, { useCallback, useMemo, useEffect } from 'react';
5
+ import { useLocation } from 'react-router-dom';
5
6
  import { WidthProvider, Responsive } from 'react-grid-layout';
6
7
  import 'react-grid-layout/css/styles.css';
7
8
  import 'react-resizable/css/styles.css';
@@ -24,13 +25,140 @@ import SaveIcon from '@material-ui/icons/Save';
24
25
  import EditIcon from '@material-ui/icons/Edit';
25
26
  import CancelIcon from '@material-ui/icons/Cancel';
26
27
  import { z } from 'zod';
28
+ import { stringifyEntityRef } from '@backstage/catalog-model';
27
29
 
28
30
  const rootRouteRef = createRouteRef({
29
31
  id: "home"
30
32
  });
31
33
 
34
+ var __defProp = Object.defineProperty;
35
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
36
+ var __publicField = (obj, key, value) => {
37
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
38
+ return value;
39
+ };
40
+ class VisitsStorageApi {
41
+ constructor(options) {
42
+ __publicField(this, "limit");
43
+ __publicField(this, "storageApi");
44
+ __publicField(this, "storageKeyPrefix", "@backstage/plugin-home:visits");
45
+ __publicField(this, "identityApi");
46
+ var _a;
47
+ this.limit = Math.abs((_a = options.limit) != null ? _a : 100);
48
+ this.storageApi = options.storageApi;
49
+ this.identityApi = options.identityApi;
50
+ }
51
+ static create(options) {
52
+ return new VisitsStorageApi(options);
53
+ }
54
+ /**
55
+ * Returns a list of visits through the visitsApi
56
+ */
57
+ async list(queryParams) {
58
+ var _a, _b;
59
+ let visits = [...await this.retrieveAll()];
60
+ ((_a = queryParams == null ? void 0 : queryParams.orderBy) != null ? _a : []).reverse().forEach((order) => {
61
+ if (order.direction === "asc") {
62
+ visits.sort((a, b) => this.compare(order, a, b));
63
+ } else {
64
+ visits.sort((a, b) => this.compare(order, b, a));
65
+ }
66
+ });
67
+ ((_b = queryParams == null ? void 0 : queryParams.filterBy) != null ? _b : []).reverse().forEach((filter) => {
68
+ visits = visits.filter((visit) => {
69
+ const field = visit[filter.field];
70
+ if (filter.operator === ">")
71
+ return field > filter.value;
72
+ if (filter.operator === ">=")
73
+ return field >= filter.value;
74
+ if (filter.operator === "<")
75
+ return field < filter.value;
76
+ if (filter.operator === "<=")
77
+ return field <= filter.value;
78
+ if (filter.operator === "==")
79
+ return field === filter.value;
80
+ if (filter.operator === "!=")
81
+ return field !== filter.value;
82
+ if (filter.operator === "contains")
83
+ return `${field}`.includes(`${filter.value}`);
84
+ return false;
85
+ });
86
+ });
87
+ return visits;
88
+ }
89
+ /**
90
+ * Saves a visit through the visitsApi
91
+ */
92
+ async save(saveParams) {
93
+ const visits = [...await this.retrieveAll()];
94
+ const visit = {
95
+ ...saveParams.visit,
96
+ id: window.crypto.randomUUID(),
97
+ hits: 1,
98
+ timestamp: Date.now()
99
+ };
100
+ const visitIndex = visits.findIndex((e) => e.pathname === visit.pathname);
101
+ if (visitIndex >= 0) {
102
+ visit.id = visits[visitIndex].id;
103
+ visit.hits = visits[visitIndex].hits + 1;
104
+ visits[visitIndex] = visit;
105
+ } else {
106
+ visits.push(visit);
107
+ }
108
+ visits.sort((a, b) => b.timestamp - a.timestamp);
109
+ await this.persistAll(visits.splice(0, this.limit));
110
+ return visit;
111
+ }
112
+ async persistAll(visits) {
113
+ const { userEntityRef } = await this.identityApi.getBackstageIdentity();
114
+ const storageKey = `${this.storageKeyPrefix}:${userEntityRef}`;
115
+ return this.storageApi.set(storageKey, visits);
116
+ }
117
+ async retrieveAll() {
118
+ var _a;
119
+ const { userEntityRef } = await this.identityApi.getBackstageIdentity();
120
+ const storageKey = `${this.storageKeyPrefix}:${userEntityRef}`;
121
+ let visits;
122
+ try {
123
+ visits = (_a = this.storageApi.snapshot(storageKey).value) != null ? _a : [];
124
+ } catch {
125
+ visits = [];
126
+ }
127
+ return visits;
128
+ }
129
+ // This assumes Visit fields are either numbers or strings
130
+ compare(order, a, b) {
131
+ const isNumber = typeof a[order.field] === "number";
132
+ return isNumber ? a[order.field] - b[order.field] : `${a[order.field]}`.localeCompare(`${b[order.field]}`);
133
+ }
134
+ }
135
+
136
+ class VisitsWebStorageApi {
137
+ static create(options) {
138
+ return VisitsStorageApi.create({
139
+ limit: options.limit,
140
+ identityApi: options.identityApi,
141
+ storageApi: WebStorage.create({ errorApi: options.errorApi })
142
+ });
143
+ }
144
+ }
145
+
146
+ const visitsApiRef = createApiRef({
147
+ id: "homepage.visits"
148
+ });
149
+
32
150
  const homePlugin = createPlugin({
33
151
  id: "home",
152
+ apis: [
153
+ createApiFactory({
154
+ api: visitsApiRef,
155
+ deps: {
156
+ storageApi: storageApiRef,
157
+ identityApi: identityApiRef
158
+ },
159
+ factory: ({ storageApi, identityApi }) => VisitsStorageApi.create({ storageApi, identityApi })
160
+ })
161
+ ],
34
162
  routes: {
35
163
  root: rootRouteRef
36
164
  }
@@ -38,7 +166,7 @@ const homePlugin = createPlugin({
38
166
  const HomepageCompositionRoot = homePlugin.provide(
39
167
  createRoutableExtension({
40
168
  name: "HomepageCompositionRoot",
41
- component: () => import('./esm/index-e7416f2d.esm.js').then((m) => m.HomepageCompositionRoot),
169
+ component: () => import('./esm/index-20932a73.esm.js').then((m) => m.HomepageCompositionRoot),
42
170
  mountPoint: rootRouteRef
43
171
  })
44
172
  );
@@ -132,6 +260,18 @@ const HeaderWorldClock = homePlugin.provide(
132
260
  }
133
261
  })
134
262
  );
263
+ const HomePageTopVisited = homePlugin.provide(
264
+ createCardExtension$1({
265
+ name: "HomePageTopVisited",
266
+ components: () => import('./esm/TopVisited-918c6b5f.esm.js')
267
+ })
268
+ );
269
+ const HomePageRecentlyVisited = homePlugin.provide(
270
+ createCardExtension$1({
271
+ name: "HomePageRecentlyVisited",
272
+ components: () => import('./esm/RecentlyVisited-618bf2be.esm.js')
273
+ })
274
+ );
135
275
 
136
276
  const Form = withTheme(require("@rjsf/material-ui-v5").Theme);
137
277
  const useStyles$3 = makeStyles(
@@ -658,6 +798,59 @@ const CustomHomepageGrid = (props) => {
658
798
  ));
659
799
  };
660
800
 
801
+ const getToEntityRef = ({
802
+ rootPath = "catalog",
803
+ stringifyEntityRefImpl = stringifyEntityRef
804
+ } = {}) => ({ pathname }) => {
805
+ const regex = new RegExp(
806
+ `^/${rootPath}/(?<namespace>[^/]+)/(?<kind>[^/]+)/(?<name>[^/]+)`
807
+ );
808
+ const result = regex.exec(pathname);
809
+ if (!result || !(result == null ? void 0 : result.groups))
810
+ return void 0;
811
+ const entity = {
812
+ namespace: result.groups.namespace,
813
+ kind: result.groups.kind,
814
+ name: result.groups.name
815
+ };
816
+ return stringifyEntityRefImpl(entity);
817
+ };
818
+ const getVisitName = ({ rootPath = "catalog", document = global.document } = {}) => ({ pathname }) => {
819
+ const regex = new RegExp(
820
+ `^/${rootPath}/(?<namespace>[^/]+)/(?<kind>[^/]+)/(?<name>[^/]+)`
821
+ );
822
+ let result = regex.exec(pathname);
823
+ if (result && (result == null ? void 0 : result.groups))
824
+ return result.groups.name;
825
+ result = /^\/(?<name>[^\/]+)$/.exec(pathname);
826
+ if (result && (result == null ? void 0 : result.groups))
827
+ return result.groups.name;
828
+ return document.title;
829
+ };
830
+ const VisitListener = ({
831
+ children,
832
+ toEntityRef,
833
+ visitName
834
+ }) => {
835
+ const visitsApi = useApi(visitsApiRef);
836
+ const { pathname } = useLocation();
837
+ const toEntityRefImpl = toEntityRef != null ? toEntityRef : getToEntityRef();
838
+ const visitNameImpl = visitName != null ? visitName : getVisitName();
839
+ useEffect(() => {
840
+ const requestId = requestAnimationFrame(() => {
841
+ visitsApi.save({
842
+ visit: {
843
+ name: visitNameImpl({ pathname }),
844
+ pathname,
845
+ entityRef: toEntityRefImpl({ pathname })
846
+ }
847
+ });
848
+ });
849
+ return () => cancelAnimationFrame(requestId);
850
+ }, [visitsApi, pathname, toEntityRefImpl, visitNameImpl]);
851
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
852
+ };
853
+
661
854
  const TemplateBackstageLogo = (props) => {
662
855
  return /* @__PURE__ */ React.createElement(
663
856
  "svg",
@@ -707,5 +900,5 @@ const TemplateBackstageLogoIcon = () => {
707
900
  const createCardExtension = createCardExtension$1;
708
901
  const SettingsModal = SettingsModal$1;
709
902
 
710
- export { ComponentAccordion, ComponentTab, ComponentTabs, CustomHomepageGrid, HeaderWorldClock, HomePageCompanyLogo, HomePageRandomJoke, HomePageStarredEntities, HomePageToolkit, HomepageCompositionRoot, SettingsModal, TemplateBackstageLogo, TemplateBackstageLogoIcon, WelcomeTitle, createCardExtension, homePlugin };
903
+ export { ComponentAccordion, ComponentTab, ComponentTabs, CustomHomepageGrid, HeaderWorldClock, HomePageCompanyLogo, HomePageRandomJoke, HomePageRecentlyVisited, HomePageStarredEntities, HomePageToolkit, HomePageTopVisited, HomepageCompositionRoot, SettingsModal, TemplateBackstageLogo, TemplateBackstageLogoIcon, VisitListener, VisitsStorageApi, VisitsWebStorageApi, WelcomeTitle, createCardExtension, homePlugin, visitsApiRef };
711
904
  //# sourceMappingURL=index.esm.js.map