@backstage/plugin-techdocs 0.12.13 → 0.13.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,143 @@
1
1
  # @backstage/plugin-techdocs
2
2
 
3
+ ## 0.13.1
4
+
5
+ ### Patch Changes
6
+
7
+ - bdc53553eb: chore(deps): bump `react-text-truncate` from 0.16.0 to 0.17.0
8
+ - a64f99f734: Code snippets now include a "copy to clipboard" button.
9
+ - Updated dependencies
10
+ - @backstage/core-components@0.8.6
11
+ - @backstage/plugin-search@0.6.0
12
+ - @backstage/plugin-catalog@0.7.10
13
+
14
+ ## 0.13.0
15
+
16
+ ### Minor Changes
17
+
18
+ - aecfe4f403: Make `TechDocsClient` and `TechDocsStorageClient` use the `FetchApi`. You now
19
+ need to pass in an instance of that API when constructing the client, if you
20
+ create a custom instance in your app.
21
+
22
+ If you are replacing the factory:
23
+
24
+ ```diff
25
+ +import { fetchApiRef } from '@backstage/core-plugin-api';
26
+
27
+ createApiFactory({
28
+ api: techdocsStorageApiRef,
29
+ deps: {
30
+ configApi: configApiRef,
31
+ discoveryApi: discoveryApiRef,
32
+ identityApi: identityApiRef,
33
+ + fetchApi: fetchApiRef,
34
+ },
35
+ factory: ({
36
+ configApi,
37
+ discoveryApi,
38
+ identityApi,
39
+ + fetchApi,
40
+ }) =>
41
+ new TechDocsStorageClient({
42
+ configApi,
43
+ discoveryApi,
44
+ identityApi,
45
+ + fetchApi,
46
+ }),
47
+ }),
48
+ createApiFactory({
49
+ api: techdocsApiRef,
50
+ deps: {
51
+ configApi: configApiRef,
52
+ discoveryApi: discoveryApiRef,
53
+ - identityApi: identityApiRef,
54
+ + fetchApi: fetchApiRef,
55
+ },
56
+ factory: ({
57
+ configApi,
58
+ discoveryApi,
59
+ - identityApi,
60
+ + fetchApi,
61
+ }) =>
62
+ new TechDocsClient({
63
+ configApi,
64
+ discoveryApi,
65
+ - identityApi,
66
+ + fetchApi,
67
+ }),
68
+ }),
69
+ ```
70
+
71
+ If instantiating directly:
72
+
73
+ ```diff
74
+ +import { fetchApiRef } from '@backstage/core-plugin-api';
75
+
76
+ +const fetchApi = useApi(fetchApiRef);
77
+ const storageClient = new TechDocsStorageClient({
78
+ configApi,
79
+ discoveryApi,
80
+ identityApi,
81
+ + fetchApi,
82
+ });
83
+ const techdocsClient = new TechDocsClient({
84
+ configApi,
85
+ discoveryApi,
86
+ - identityApi,
87
+ + fetchApi,
88
+ }),
89
+ ```
90
+
91
+ ### Patch Changes
92
+
93
+ - 51fbedc445: Migrated usage of deprecated `IdentityApi` methods.
94
+ - 29710c91c2: use lighter color for block quotes and horizontal rulers
95
+ - Updated dependencies
96
+ - @backstage/core-components@0.8.5
97
+ - @backstage/integration@0.7.2
98
+ - @backstage/plugin-search@0.5.6
99
+ - @backstage/core-plugin-api@0.6.0
100
+ - @backstage/plugin-catalog@0.7.9
101
+ - @backstage/plugin-catalog-react@0.6.12
102
+ - @backstage/config@0.1.13
103
+ - @backstage/catalog-model@0.9.10
104
+ - @backstage/integration-react@0.1.19
105
+
106
+ ## 0.12.15-next.0
107
+
108
+ ### Patch Changes
109
+
110
+ - 51fbedc445: Migrated usage of deprecated `IdentityApi` methods.
111
+ - 29710c91c2: use lighter color for block quotes and horizontal rulers
112
+ - Updated dependencies
113
+ - @backstage/core-components@0.8.5-next.0
114
+ - @backstage/core-plugin-api@0.6.0-next.0
115
+ - @backstage/plugin-catalog@0.7.9-next.0
116
+ - @backstage/config@0.1.13-next.0
117
+ - @backstage/plugin-catalog-react@0.6.12-next.0
118
+ - @backstage/plugin-search@0.5.6-next.0
119
+ - @backstage/catalog-model@0.9.10-next.0
120
+ - @backstage/integration-react@0.1.19-next.0
121
+ - @backstage/integration@0.7.2-next.0
122
+
123
+ ## 0.12.14
124
+
125
+ ### Patch Changes
126
+
127
+ - 5333451def: Cleaned up API exports
128
+ - 1628ca3f49: Fix an issue where the TechDocs sidebar is hidden when the Backstage sidebar is pinned at smaller screen sizes
129
+ - Updated dependencies
130
+ - @backstage/config@0.1.12
131
+ - @backstage/integration@0.7.1
132
+ - @backstage/core-components@0.8.4
133
+ - @backstage/core-plugin-api@0.5.0
134
+ - @backstage/plugin-catalog-react@0.6.11
135
+ - @backstage/errors@0.2.0
136
+ - @backstage/catalog-model@0.9.9
137
+ - @backstage/integration-react@0.1.18
138
+ - @backstage/plugin-catalog@0.7.8
139
+ - @backstage/plugin-search@0.5.5
140
+
3
141
  ## 0.12.13
4
142
 
5
143
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
3
- import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
3
+ import { DiscoveryApi, FetchApi, IdentityApi } from '@backstage/core-plugin-api';
4
4
  import * as _backstage_catalog_model from '@backstage/catalog-model';
5
5
  import { Entity, LocationSpec, EntityName } from '@backstage/catalog-model';
6
6
  import { Config } from '@backstage/config';
@@ -19,10 +19,33 @@ declare type TechDocsEntityMetadata = Entity & {
19
19
  locationMetadata?: LocationSpec;
20
20
  };
21
21
 
22
+ /**
23
+ * Utility API reference for the {@link TechDocsStorageApi}.
24
+ *
25
+ * @public
26
+ */
22
27
  declare const techdocsStorageApiRef: _backstage_core_plugin_api.ApiRef<TechDocsStorageApi>;
28
+ /**
29
+ * Utility API reference for the {@link TechDocsApi}.
30
+ *
31
+ * @public
32
+ */
23
33
  declare const techdocsApiRef: _backstage_core_plugin_api.ApiRef<TechDocsApi>;
34
+ /**
35
+ * The outcome of a docs sync operation.
36
+ *
37
+ * @public
38
+ */
24
39
  declare type SyncResult = 'cached' | 'updated';
40
+ /**
41
+ * API which talks to TechDocs storage to fetch files to render.
42
+ *
43
+ * @public
44
+ */
25
45
  interface TechDocsStorageApi {
46
+ /**
47
+ * Set to techdocs.requestUrl as the URL for techdocs-backend API.
48
+ */
26
49
  getApiOrigin(): Promise<string>;
27
50
  getStorageUrl(): Promise<string>;
28
51
  getBuilder(): Promise<string>;
@@ -30,25 +53,33 @@ interface TechDocsStorageApi {
30
53
  syncEntityDocs(entityId: EntityName, logHandler?: (line: string) => void): Promise<SyncResult>;
31
54
  getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): Promise<string>;
32
55
  }
56
+ /**
57
+ * API to talk to techdocs-backend.
58
+ *
59
+ * @public
60
+ */
33
61
  interface TechDocsApi {
62
+ /**
63
+ * Set to techdocs.requestUrl as the URL for techdocs-backend API.
64
+ */
34
65
  getApiOrigin(): Promise<string>;
35
66
  getTechDocsMetadata(entityId: EntityName): Promise<TechDocsMetadata>;
36
67
  getEntityMetadata(entityId: EntityName): Promise<TechDocsEntityMetadata>;
37
68
  }
38
69
 
39
70
  /**
40
- * API to talk to techdocs-backend.
71
+ * API to talk to `techdocs-backend`.
41
72
  *
42
- * @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API
73
+ * @public
43
74
  */
44
75
  declare class TechDocsClient implements TechDocsApi {
45
76
  configApi: Config;
46
77
  discoveryApi: DiscoveryApi;
47
- identityApi: IdentityApi;
48
- constructor({ configApi, discoveryApi, identityApi, }: {
78
+ private fetchApi;
79
+ constructor(options: {
49
80
  configApi: Config;
50
81
  discoveryApi: DiscoveryApi;
51
- identityApi: IdentityApi;
82
+ fetchApi: FetchApi;
52
83
  });
53
84
  getApiOrigin(): Promise<string>;
54
85
  /**
@@ -58,7 +89,7 @@ declare class TechDocsClient implements TechDocsApi {
58
89
  * static files. It includes necessary data about the docs site. This method requests techdocs-backend
59
90
  * which retrieves the TechDocs metadata.
60
91
  *
61
- * @param {EntityName} entityId Object containing entity data like name, namespace, etc.
92
+ * @param entityId - Object containing entity data like name, namespace, etc.
62
93
  */
63
94
  getTechDocsMetadata(entityId: EntityName): Promise<TechDocsMetadata>;
64
95
  /**
@@ -67,23 +98,25 @@ declare class TechDocsClient implements TechDocsApi {
67
98
  * This method requests techdocs-backend which uses the catalog APIs to respond with filtered
68
99
  * information required here.
69
100
  *
70
- * @param {EntityName} entityId Object containing entity data like name, namespace, etc.
101
+ * @param entityId - Object containing entity data like name, namespace, etc.
71
102
  */
72
103
  getEntityMetadata(entityId: EntityName): Promise<TechDocsEntityMetadata>;
73
104
  }
74
105
  /**
75
106
  * API which talks to TechDocs storage to fetch files to render.
76
107
  *
77
- * @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API
108
+ * @public
78
109
  */
79
110
  declare class TechDocsStorageClient implements TechDocsStorageApi {
80
111
  configApi: Config;
81
112
  discoveryApi: DiscoveryApi;
82
113
  identityApi: IdentityApi;
83
- constructor({ configApi, discoveryApi, identityApi, }: {
114
+ private fetchApi;
115
+ constructor(options: {
84
116
  configApi: Config;
85
117
  discoveryApi: DiscoveryApi;
86
118
  identityApi: IdentityApi;
119
+ fetchApi: FetchApi;
87
120
  });
88
121
  getApiOrigin(): Promise<string>;
89
122
  getStorageUrl(): Promise<string>;
@@ -91,19 +124,19 @@ declare class TechDocsStorageClient implements TechDocsStorageApi {
91
124
  /**
92
125
  * Fetch HTML content as text for an individual docs page in an entity's docs site.
93
126
  *
94
- * @param {EntityName} entityId Object containing entity data like name, namespace, etc.
95
- * @param {string} path The unique path to an individual docs page e.g. overview/what-is-new
96
- * @returns {string} HTML content of the docs page as string
97
- * @throws {Error} Throws error when the page is not found.
127
+ * @param entityId - Object containing entity data like name, namespace, etc.
128
+ * @param path - The unique path to an individual docs page e.g. overview/what-is-new
129
+ * @returns HTML content of the docs page as string
130
+ * @throws Throws error when the page is not found.
98
131
  */
99
132
  getEntityDocs(entityId: EntityName, path: string): Promise<string>;
100
133
  /**
101
134
  * Check if docs are on the latest version and trigger rebuild if not
102
135
  *
103
- * @param {EntityName} entityId Object containing entity data like name, namespace, etc.
104
- * @param {Function} logHandler Callback to receive log messages from the build process
105
- * @returns {SyncResult} Whether documents are currently synchronized to newest version
106
- * @throws {Error} Throws error on error from sync endpoint in Techdocs Backend
136
+ * @param entityId - Object containing entity data like name, namespace, etc.
137
+ * @param logHandler - Callback to receive log messages from the build process
138
+ * @returns Whether documents are currently synchronized to newest version
139
+ * @throws Throws error on error from sync endpoint in Techdocs Backend
107
140
  */
108
141
  syncEntityDocs(entityId: EntityName, logHandler?: (line: string) => void): Promise<SyncResult>;
109
142
  getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): Promise<string>;
package/dist/index.esm.js CHANGED
@@ -1,9 +1,9 @@
1
- import { createApiRef, createRouteRef, useRouteRef, useApi, configApiRef, createPlugin, createApiFactory, discoveryApiRef, identityApiRef, createRoutableExtension, createComponentExtension } from '@backstage/core-plugin-api';
1
+ import { createApiRef, createRouteRef, useRouteRef, useApi, configApiRef, createPlugin, createApiFactory, discoveryApiRef, identityApiRef, fetchApiRef, createRoutableExtension, createComponentExtension } from '@backstage/core-plugin-api';
2
2
  import { ResponseError, NotFoundError } from '@backstage/errors';
3
3
  import { EventSourcePolyfill } from 'event-source-polyfill';
4
4
  import React, { useEffect, useState, useReducer, useRef, useMemo, createContext, useContext, useCallback } from 'react';
5
5
  import { makeStyles, ListItemText, ListItem, Divider, Card, CardMedia, CardContent, CardActions, Grid, TextField, InputAdornment, IconButton, CircularProgress, createStyles, Button as Button$1, Drawer, Typography, useTheme } from '@material-ui/core';
6
- import { Link, SubvalueCell, Table, EmptyState, Button, WarningPanel, CodeSnippet, PageWithHeader, Content, ContentHeader, SupportButton, ItemCardGrid, ItemCardHeader, Progress, LogViewer, ErrorPage, HeaderLabel, Header, Page, HeaderTabs, MissingAnnotationEmptyState } from '@backstage/core-components';
6
+ import { Link, SubvalueCell, Table, EmptyState, Button, WarningPanel, CodeSnippet, PageWithHeader, Content, ContentHeader, SupportButton, ItemCardGrid, ItemCardHeader, Progress, LogViewer, ErrorPage, SidebarPinStateContext, HeaderLabel, Header, Page, HeaderTabs, MissingAnnotationEmptyState } from '@backstage/core-components';
7
7
  import TextTruncate from 'react-text-truncate';
8
8
  import { FilteredEntityLayout, FilterContainer, EntityListContainer } from '@backstage/plugin-catalog';
9
9
  import { favoriteEntityIcon, favoriteEntityTooltip, EntityRefLinks, getEntityRelations, formatEntityRefTitle, useEntityListProvider, useStarredEntities, CATALOG_FILTER_EXISTS, EntityListProvider, UserListPicker, EntityOwnerPicker, EntityTagPicker, EntityRefLink, catalogApiRef, useOwnUser, isOwnerOf, useEntity } from '@backstage/plugin-catalog-react';
@@ -37,14 +37,10 @@ const techdocsApiRef = createApiRef({
37
37
  });
38
38
 
39
39
  class TechDocsClient {
40
- constructor({
41
- configApi,
42
- discoveryApi,
43
- identityApi
44
- }) {
45
- this.configApi = configApi;
46
- this.discoveryApi = discoveryApi;
47
- this.identityApi = identityApi;
40
+ constructor(options) {
41
+ this.configApi = options.configApi;
42
+ this.discoveryApi = options.discoveryApi;
43
+ this.fetchApi = options.fetchApi;
48
44
  }
49
45
  async getApiOrigin() {
50
46
  var _a;
@@ -54,10 +50,7 @@ class TechDocsClient {
54
50
  const { kind, namespace, name } = entityId;
55
51
  const apiOrigin = await this.getApiOrigin();
56
52
  const requestUrl = `${apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`;
57
- const token = await this.identityApi.getIdToken();
58
- const request = await fetch(`${requestUrl}`, {
59
- headers: token ? { Authorization: `Bearer ${token}` } : {}
60
- });
53
+ const request = await this.fetchApi.fetch(`${requestUrl}`);
61
54
  if (!request.ok) {
62
55
  throw await ResponseError.fromResponse(request);
63
56
  }
@@ -67,10 +60,7 @@ class TechDocsClient {
67
60
  const { kind, namespace, name } = entityId;
68
61
  const apiOrigin = await this.getApiOrigin();
69
62
  const requestUrl = `${apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`;
70
- const token = await this.identityApi.getIdToken();
71
- const request = await fetch(`${requestUrl}`, {
72
- headers: token ? { Authorization: `Bearer ${token}` } : {}
73
- });
63
+ const request = await this.fetchApi.fetch(`${requestUrl}`);
74
64
  if (!request.ok) {
75
65
  throw await ResponseError.fromResponse(request);
76
66
  }
@@ -78,14 +68,11 @@ class TechDocsClient {
78
68
  }
79
69
  }
80
70
  class TechDocsStorageClient {
81
- constructor({
82
- configApi,
83
- discoveryApi,
84
- identityApi
85
- }) {
86
- this.configApi = configApi;
87
- this.discoveryApi = discoveryApi;
88
- this.identityApi = identityApi;
71
+ constructor(options) {
72
+ this.configApi = options.configApi;
73
+ this.discoveryApi = options.discoveryApi;
74
+ this.identityApi = options.identityApi;
75
+ this.fetchApi = options.fetchApi;
89
76
  }
90
77
  async getApiOrigin() {
91
78
  var _a;
@@ -102,10 +89,7 @@ class TechDocsStorageClient {
102
89
  const { kind, namespace, name } = entityId;
103
90
  const storageUrl = await this.getStorageUrl();
104
91
  const url = `${storageUrl}/${namespace}/${kind}/${name}/${path}`;
105
- const token = await this.identityApi.getIdToken();
106
- const request = await fetch(`${url.endsWith("/") ? url : `${url}/`}index.html`, {
107
- headers: token ? { Authorization: `Bearer ${token}` } : {}
108
- });
92
+ const request = await this.fetchApi.fetch(`${url.endsWith("/") ? url : `${url}/`}index.html`);
109
93
  let errorMessage = "";
110
94
  switch (request.status) {
111
95
  case 404:
@@ -125,7 +109,7 @@ class TechDocsStorageClient {
125
109
  const { kind, namespace, name } = entityId;
126
110
  const apiOrigin = await this.getApiOrigin();
127
111
  const url = `${apiOrigin}/sync/${namespace}/${kind}/${name}`;
128
- const token = await this.identityApi.getIdToken();
112
+ const { token } = await this.identityApi.getCredentials();
129
113
  return new Promise((resolve, reject) => {
130
114
  const source = new EventSourcePolyfill(url, {
131
115
  withCredentials: true,
@@ -495,12 +479,14 @@ const techdocsPlugin = createPlugin({
495
479
  deps: {
496
480
  configApi: configApiRef,
497
481
  discoveryApi: discoveryApiRef,
498
- identityApi: identityApiRef
482
+ identityApi: identityApiRef,
483
+ fetchApi: fetchApiRef
499
484
  },
500
- factory: ({ configApi, discoveryApi, identityApi }) => new TechDocsStorageClient({
485
+ factory: ({ configApi, discoveryApi, identityApi, fetchApi }) => new TechDocsStorageClient({
501
486
  configApi,
502
487
  discoveryApi,
503
- identityApi
488
+ identityApi,
489
+ fetchApi
504
490
  })
505
491
  }),
506
492
  createApiFactory({
@@ -508,12 +494,12 @@ const techdocsPlugin = createPlugin({
508
494
  deps: {
509
495
  configApi: configApiRef,
510
496
  discoveryApi: discoveryApiRef,
511
- identityApi: identityApiRef
497
+ fetchApi: fetchApiRef
512
498
  },
513
- factory: ({ configApi, discoveryApi, identityApi }) => new TechDocsClient({
499
+ factory: ({ configApi, discoveryApi, fetchApi }) => new TechDocsClient({
514
500
  configApi,
515
501
  discoveryApi,
516
- identityApi
502
+ fetchApi
517
503
  })
518
504
  })
519
505
  ],
@@ -696,6 +682,22 @@ const addLinkClickListener = ({
696
682
  };
697
683
  };
698
684
 
685
+ const copyToClipboard = () => {
686
+ return (dom) => {
687
+ Array.from(dom.querySelectorAll("code")).forEach((codeElem) => {
688
+ var _a;
689
+ const button = document.createElement("button");
690
+ const toBeCopied = codeElem.textContent || "";
691
+ button.className = "md-clipboard md-icon";
692
+ button.title = "Copy to clipboard";
693
+ 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>';
694
+ button.addEventListener("click", () => navigator.clipboard.writeText(toBeCopied));
695
+ (_a = codeElem == null ? void 0 : codeElem.parentElement) == null ? void 0 : _a.prepend(button);
696
+ });
697
+ return dom;
698
+ };
699
+ };
700
+
699
701
  const removeMkdocsHeader = () => {
700
702
  return (dom) => {
701
703
  var _a;
@@ -1272,6 +1274,7 @@ const useTechDocsReaderDom = (entityRef) => {
1272
1274
  const { state, path, content: rawPage } = useTechDocsReader();
1273
1275
  const [sidebars, setSidebars] = useState();
1274
1276
  const [dom, setDom] = useState(null);
1277
+ const { isPinned } = useContext(SidebarPinStateContext);
1275
1278
  const updateSidebarPosition = useCallback(() => {
1276
1279
  if (!dom || !sidebars)
1277
1280
  return;
@@ -1329,6 +1332,8 @@ const useTechDocsReaderDom = (entityRef) => {
1329
1332
 
1330
1333
  --md-code-fg-color: ${theme.palette.text.primary};
1331
1334
  --md-code-bg-color: ${theme.palette.background.paper};
1335
+ --md-accent-fg-color: ${theme.palette.primary.main};
1336
+ --md-default-fg-color--lightest: ${theme.palette.textVerySubtle};
1332
1337
  }
1333
1338
  .md-main__inner { margin-top: 0; }
1334
1339
  .md-sidebar { position: fixed; bottom: 100px; width: 20rem; }
@@ -1341,6 +1346,13 @@ const useTechDocsReaderDom = (entityRef) => {
1341
1346
  .md-typeset h1, .md-typeset h2, .md-typeset h3 { font-weight: bold; }
1342
1347
  .md-nav { font-size: 1rem; }
1343
1348
  .md-grid { max-width: 90vw; margin: 0 }
1349
+ .md-typeset blockquote {
1350
+ color: ${theme.palette.textSubtle};
1351
+ border-left: 0.2rem solid ${theme.palette.textVerySubtle};
1352
+ }
1353
+ .md-typeset hr {
1354
+ border-bottom: 0.05rem dotted ${theme.palette.textVerySubtle};
1355
+ }
1344
1356
  .md-typeset table:not([class]) {
1345
1357
  font-size: 1rem;
1346
1358
  border: 1px solid ${theme.palette.text.primary};
@@ -1370,7 +1382,7 @@ const useTechDocsReaderDom = (entityRef) => {
1370
1382
  transition: none !important
1371
1383
  }
1372
1384
  .md-sidebar--secondary { display: none; }
1373
- .md-sidebar--primary { left: 72px; width: 10rem }
1385
+ .md-sidebar--primary { left: ${isPinned ? "242px" : "72px"}; width: 10rem }
1374
1386
  .md-content { margin-left: 10rem; max-width: calc(100% - 10rem); }
1375
1387
  .md-content__inner { font-size: 0.9rem }
1376
1388
  .md-footer {
@@ -1451,10 +1463,14 @@ const useTechDocsReaderDom = (entityRef) => {
1451
1463
  theme.palette.primary.main,
1452
1464
  theme.palette.success.main,
1453
1465
  theme.palette.text.primary,
1454
- theme.typography.fontFamily
1466
+ theme.palette.textSubtle,
1467
+ theme.palette.textVerySubtle,
1468
+ theme.typography.fontFamily,
1469
+ isPinned
1455
1470
  ]);
1456
1471
  const postRender = useCallback(async (transformedElement) => transform(transformedElement, [
1457
1472
  scrollIntoAnchor(),
1473
+ copyToClipboard(),
1458
1474
  addLinkClickListener({
1459
1475
  baseUrl: window.location.origin,
1460
1476
  onClick: (event, url) => {