@backstage/plugin-techdocs 0.12.12 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,146 @@
1
1
  # @backstage/plugin-techdocs
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - aecfe4f403: Make `TechDocsClient` and `TechDocsStorageClient` use the `FetchApi`. You now
8
+ need to pass in an instance of that API when constructing the client, if you
9
+ create a custom instance in your app.
10
+
11
+ If you are replacing the factory:
12
+
13
+ ```diff
14
+ +import { fetchApiRef } from '@backstage/core-plugin-api';
15
+
16
+ createApiFactory({
17
+ api: techdocsStorageApiRef,
18
+ deps: {
19
+ configApi: configApiRef,
20
+ discoveryApi: discoveryApiRef,
21
+ identityApi: identityApiRef,
22
+ + fetchApi: fetchApiRef,
23
+ },
24
+ factory: ({
25
+ configApi,
26
+ discoveryApi,
27
+ identityApi,
28
+ + fetchApi,
29
+ }) =>
30
+ new TechDocsStorageClient({
31
+ configApi,
32
+ discoveryApi,
33
+ identityApi,
34
+ + fetchApi,
35
+ }),
36
+ }),
37
+ createApiFactory({
38
+ api: techdocsApiRef,
39
+ deps: {
40
+ configApi: configApiRef,
41
+ discoveryApi: discoveryApiRef,
42
+ - identityApi: identityApiRef,
43
+ + fetchApi: fetchApiRef,
44
+ },
45
+ factory: ({
46
+ configApi,
47
+ discoveryApi,
48
+ - identityApi,
49
+ + fetchApi,
50
+ }) =>
51
+ new TechDocsClient({
52
+ configApi,
53
+ discoveryApi,
54
+ - identityApi,
55
+ + fetchApi,
56
+ }),
57
+ }),
58
+ ```
59
+
60
+ If instantiating directly:
61
+
62
+ ```diff
63
+ +import { fetchApiRef } from '@backstage/core-plugin-api';
64
+
65
+ +const fetchApi = useApi(fetchApiRef);
66
+ const storageClient = new TechDocsStorageClient({
67
+ configApi,
68
+ discoveryApi,
69
+ identityApi,
70
+ + fetchApi,
71
+ });
72
+ const techdocsClient = new TechDocsClient({
73
+ configApi,
74
+ discoveryApi,
75
+ - identityApi,
76
+ + fetchApi,
77
+ }),
78
+ ```
79
+
80
+ ### Patch Changes
81
+
82
+ - 51fbedc445: Migrated usage of deprecated `IdentityApi` methods.
83
+ - 29710c91c2: use lighter color for block quotes and horizontal rulers
84
+ - Updated dependencies
85
+ - @backstage/core-components@0.8.5
86
+ - @backstage/integration@0.7.2
87
+ - @backstage/plugin-search@0.5.6
88
+ - @backstage/core-plugin-api@0.6.0
89
+ - @backstage/plugin-catalog@0.7.9
90
+ - @backstage/plugin-catalog-react@0.6.12
91
+ - @backstage/config@0.1.13
92
+ - @backstage/catalog-model@0.9.10
93
+ - @backstage/integration-react@0.1.19
94
+
95
+ ## 0.12.15-next.0
96
+
97
+ ### Patch Changes
98
+
99
+ - 51fbedc445: Migrated usage of deprecated `IdentityApi` methods.
100
+ - 29710c91c2: use lighter color for block quotes and horizontal rulers
101
+ - Updated dependencies
102
+ - @backstage/core-components@0.8.5-next.0
103
+ - @backstage/core-plugin-api@0.6.0-next.0
104
+ - @backstage/plugin-catalog@0.7.9-next.0
105
+ - @backstage/config@0.1.13-next.0
106
+ - @backstage/plugin-catalog-react@0.6.12-next.0
107
+ - @backstage/plugin-search@0.5.6-next.0
108
+ - @backstage/catalog-model@0.9.10-next.0
109
+ - @backstage/integration-react@0.1.19-next.0
110
+ - @backstage/integration@0.7.2-next.0
111
+
112
+ ## 0.12.14
113
+
114
+ ### Patch Changes
115
+
116
+ - 5333451def: Cleaned up API exports
117
+ - 1628ca3f49: Fix an issue where the TechDocs sidebar is hidden when the Backstage sidebar is pinned at smaller screen sizes
118
+ - Updated dependencies
119
+ - @backstage/config@0.1.12
120
+ - @backstage/integration@0.7.1
121
+ - @backstage/core-components@0.8.4
122
+ - @backstage/core-plugin-api@0.5.0
123
+ - @backstage/plugin-catalog-react@0.6.11
124
+ - @backstage/errors@0.2.0
125
+ - @backstage/catalog-model@0.9.9
126
+ - @backstage/integration-react@0.1.18
127
+ - @backstage/plugin-catalog@0.7.8
128
+ - @backstage/plugin-search@0.5.5
129
+
130
+ ## 0.12.13
131
+
132
+ ### Patch Changes
133
+
134
+ - fe9de6c25b: Adds support for opening internal Techdocs links in a new tab with CTRL+Click or CMD+Click
135
+ - 4ce51ab0f1: Internal refactor of the `react-use` imports to use `react-use/lib/*` instead.
136
+ - e0271456d8: Updated Techdocs footer navigation to dynamically resize to the width of the dom, resolving an issue where a pinned sidebar causes navigation to go off of the screen
137
+ - Updated dependencies
138
+ - @backstage/plugin-search@0.5.4
139
+ - @backstage/core-plugin-api@0.4.1
140
+ - @backstage/plugin-catalog-react@0.6.10
141
+ - @backstage/core-components@0.8.3
142
+ - @backstage/plugin-catalog@0.7.7
143
+
3
144
  ## 0.12.12
4
145
 
5
146
  ### 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,13 +1,13 @@
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';
10
- import { useCopyToClipboard, useDebounce, useAsyncRetry, useAsync } from 'react-use';
10
+ import useCopyToClipboard from 'react-use/lib/useCopyToClipboard';
11
11
  import { capitalize } from 'lodash';
12
12
  import { RELATION_OWNED_BY } from '@backstage/catalog-model';
13
13
  import ShareIcon from '@material-ui/icons/Share';
@@ -22,8 +22,11 @@ import { SearchContextProvider, useSearch } from '@backstage/plugin-search';
22
22
  import SearchIcon from '@material-ui/icons/Search';
23
23
  import Autocomplete from '@material-ui/lab/Autocomplete';
24
24
  import { useNavigate, useOutlet } from 'react-router';
25
+ import useDebounce from 'react-use/lib/useDebounce';
25
26
  import { Alert } from '@material-ui/lab';
26
27
  import Close from '@material-ui/icons/Close';
28
+ import useAsync from 'react-use/lib/useAsync';
29
+ import useAsyncRetry from 'react-use/lib/useAsyncRetry';
27
30
  import CodeIcon from '@material-ui/icons/Code';
28
31
 
29
32
  const techdocsStorageApiRef = createApiRef({
@@ -34,14 +37,10 @@ const techdocsApiRef = createApiRef({
34
37
  });
35
38
 
36
39
  class TechDocsClient {
37
- constructor({
38
- configApi,
39
- discoveryApi,
40
- identityApi
41
- }) {
42
- this.configApi = configApi;
43
- this.discoveryApi = discoveryApi;
44
- this.identityApi = identityApi;
40
+ constructor(options) {
41
+ this.configApi = options.configApi;
42
+ this.discoveryApi = options.discoveryApi;
43
+ this.fetchApi = options.fetchApi;
45
44
  }
46
45
  async getApiOrigin() {
47
46
  var _a;
@@ -51,10 +50,7 @@ class TechDocsClient {
51
50
  const { kind, namespace, name } = entityId;
52
51
  const apiOrigin = await this.getApiOrigin();
53
52
  const requestUrl = `${apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`;
54
- const token = await this.identityApi.getIdToken();
55
- const request = await fetch(`${requestUrl}`, {
56
- headers: token ? { Authorization: `Bearer ${token}` } : {}
57
- });
53
+ const request = await this.fetchApi.fetch(`${requestUrl}`);
58
54
  if (!request.ok) {
59
55
  throw await ResponseError.fromResponse(request);
60
56
  }
@@ -64,10 +60,7 @@ class TechDocsClient {
64
60
  const { kind, namespace, name } = entityId;
65
61
  const apiOrigin = await this.getApiOrigin();
66
62
  const requestUrl = `${apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`;
67
- const token = await this.identityApi.getIdToken();
68
- const request = await fetch(`${requestUrl}`, {
69
- headers: token ? { Authorization: `Bearer ${token}` } : {}
70
- });
63
+ const request = await this.fetchApi.fetch(`${requestUrl}`);
71
64
  if (!request.ok) {
72
65
  throw await ResponseError.fromResponse(request);
73
66
  }
@@ -75,14 +68,11 @@ class TechDocsClient {
75
68
  }
76
69
  }
77
70
  class TechDocsStorageClient {
78
- constructor({
79
- configApi,
80
- discoveryApi,
81
- identityApi
82
- }) {
83
- this.configApi = configApi;
84
- this.discoveryApi = discoveryApi;
85
- 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;
86
76
  }
87
77
  async getApiOrigin() {
88
78
  var _a;
@@ -99,10 +89,7 @@ class TechDocsStorageClient {
99
89
  const { kind, namespace, name } = entityId;
100
90
  const storageUrl = await this.getStorageUrl();
101
91
  const url = `${storageUrl}/${namespace}/${kind}/${name}/${path}`;
102
- const token = await this.identityApi.getIdToken();
103
- const request = await fetch(`${url.endsWith("/") ? url : `${url}/`}index.html`, {
104
- headers: token ? { Authorization: `Bearer ${token}` } : {}
105
- });
92
+ const request = await this.fetchApi.fetch(`${url.endsWith("/") ? url : `${url}/`}index.html`);
106
93
  let errorMessage = "";
107
94
  switch (request.status) {
108
95
  case 404:
@@ -122,7 +109,7 @@ class TechDocsStorageClient {
122
109
  const { kind, namespace, name } = entityId;
123
110
  const apiOrigin = await this.getApiOrigin();
124
111
  const url = `${apiOrigin}/sync/${namespace}/${kind}/${name}`;
125
- const token = await this.identityApi.getIdToken();
112
+ const { token } = await this.identityApi.getCredentials();
126
113
  return new Promise((resolve, reject) => {
127
114
  const source = new EventSourcePolyfill(url, {
128
115
  withCredentials: true,
@@ -492,12 +479,14 @@ const techdocsPlugin = createPlugin({
492
479
  deps: {
493
480
  configApi: configApiRef,
494
481
  discoveryApi: discoveryApiRef,
495
- identityApi: identityApiRef
482
+ identityApi: identityApiRef,
483
+ fetchApi: fetchApiRef
496
484
  },
497
- factory: ({ configApi, discoveryApi, identityApi }) => new TechDocsStorageClient({
485
+ factory: ({ configApi, discoveryApi, identityApi, fetchApi }) => new TechDocsStorageClient({
498
486
  configApi,
499
487
  discoveryApi,
500
- identityApi
488
+ identityApi,
489
+ fetchApi
501
490
  })
502
491
  }),
503
492
  createApiFactory({
@@ -505,12 +494,12 @@ const techdocsPlugin = createPlugin({
505
494
  deps: {
506
495
  configApi: configApiRef,
507
496
  discoveryApi: discoveryApiRef,
508
- identityApi: identityApiRef
497
+ fetchApi: fetchApiRef
509
498
  },
510
- factory: ({ configApi, discoveryApi, identityApi }) => new TechDocsClient({
499
+ factory: ({ configApi, discoveryApi, fetchApi }) => new TechDocsClient({
511
500
  configApi,
512
501
  discoveryApi,
513
- identityApi
502
+ fetchApi
514
503
  })
515
504
  })
516
505
  ],
@@ -1269,6 +1258,7 @@ const useTechDocsReaderDom = (entityRef) => {
1269
1258
  const { state, path, content: rawPage } = useTechDocsReader();
1270
1259
  const [sidebars, setSidebars] = useState();
1271
1260
  const [dom, setDom] = useState(null);
1261
+ const { isPinned } = useContext(SidebarPinStateContext);
1272
1262
  const updateSidebarPosition = useCallback(() => {
1273
1263
  if (!dom || !sidebars)
1274
1264
  return;
@@ -1287,6 +1277,21 @@ const useTechDocsReaderDom = (entityRef) => {
1287
1277
  window.removeEventListener("resize", updateSidebarPosition);
1288
1278
  };
1289
1279
  }, [updateSidebarPosition, state]);
1280
+ const updateFooterWidth = useCallback(() => {
1281
+ if (!dom)
1282
+ return;
1283
+ const footer = dom.querySelector(".md-footer");
1284
+ if (footer) {
1285
+ footer.style.width = `${dom.getBoundingClientRect().width}px`;
1286
+ }
1287
+ }, [dom]);
1288
+ useEffect(() => {
1289
+ updateFooterWidth();
1290
+ window.addEventListener("resize", updateFooterWidth);
1291
+ return () => {
1292
+ window.removeEventListener("resize", updateFooterWidth);
1293
+ };
1294
+ });
1290
1295
  const preRender = useCallback((rawContent, contentPath) => transform(rawContent, [
1291
1296
  sanitizeDOM(techdocsSanitizer.getOptionalConfig("techdocs.sanitizer")),
1292
1297
  addBaseUrl({
@@ -1316,13 +1321,20 @@ const useTechDocsReaderDom = (entityRef) => {
1316
1321
  .md-sidebar { position: fixed; bottom: 100px; width: 20rem; }
1317
1322
  .md-sidebar--secondary { right: 2rem; }
1318
1323
  .md-content { margin-bottom: 50px }
1319
- .md-footer { position: fixed; bottom: 0px; width: 100vw; }
1324
+ .md-footer { position: fixed; bottom: 0px; }
1320
1325
  .md-footer-nav__link { width: 20rem;}
1321
1326
  .md-content { margin-left: 20rem; max-width: calc(100% - 20rem * 2 - 3rem); }
1322
1327
  .md-typeset { font-size: 1rem; }
1323
1328
  .md-typeset h1, .md-typeset h2, .md-typeset h3 { font-weight: bold; }
1324
1329
  .md-nav { font-size: 1rem; }
1325
1330
  .md-grid { max-width: 90vw; margin: 0 }
1331
+ .md-typeset blockquote {
1332
+ color: ${theme.palette.textSubtle};
1333
+ border-left: 0.2rem solid ${theme.palette.textVerySubtle};
1334
+ }
1335
+ .md-typeset hr {
1336
+ border-bottom: 0.05rem dotted ${theme.palette.textVerySubtle};
1337
+ }
1326
1338
  .md-typeset table:not([class]) {
1327
1339
  font-size: 1rem;
1328
1340
  border: 1px solid ${theme.palette.text.primary};
@@ -1352,13 +1364,16 @@ const useTechDocsReaderDom = (entityRef) => {
1352
1364
  transition: none !important
1353
1365
  }
1354
1366
  .md-sidebar--secondary { display: none; }
1355
- .md-sidebar--primary { left: 72px; width: 10rem }
1367
+ .md-sidebar--primary { left: ${isPinned ? "242px" : "72px"}; width: 10rem }
1356
1368
  .md-content { margin-left: 10rem; max-width: calc(100% - 10rem); }
1357
1369
  .md-content__inner { font-size: 0.9rem }
1358
1370
  .md-footer {
1359
1371
  position: static;
1360
- margin-left: 10rem;
1361
- width: calc(100% - 10rem);
1372
+ padding-left: 10rem;
1373
+ }
1374
+ .md-footer-nav__link {
1375
+ /* footer links begin to overlap at small sizes without setting width */
1376
+ width: 50%;
1362
1377
  }
1363
1378
  .md-nav--primary .md-nav__title {
1364
1379
  white-space: normal;
@@ -1430,21 +1445,33 @@ const useTechDocsReaderDom = (entityRef) => {
1430
1445
  theme.palette.primary.main,
1431
1446
  theme.palette.success.main,
1432
1447
  theme.palette.text.primary,
1433
- theme.typography.fontFamily
1448
+ theme.palette.textSubtle,
1449
+ theme.palette.textVerySubtle,
1450
+ theme.typography.fontFamily,
1451
+ isPinned
1434
1452
  ]);
1435
1453
  const postRender = useCallback(async (transformedElement) => transform(transformedElement, [
1436
1454
  scrollIntoAnchor(),
1437
1455
  addLinkClickListener({
1438
1456
  baseUrl: window.location.origin,
1439
- onClick: (_, url) => {
1457
+ onClick: (event, url) => {
1440
1458
  var _a, _b;
1459
+ const modifierActive = event.ctrlKey || event.metaKey;
1441
1460
  const parsedUrl = new URL(url);
1442
1461
  if (parsedUrl.hash) {
1443
- navigate(`${parsedUrl.pathname}${parsedUrl.hash}`);
1444
- (_a = transformedElement == null ? void 0 : transformedElement.querySelector(`#${parsedUrl.hash.slice(1)}`)) == null ? void 0 : _a.scrollIntoView();
1462
+ if (modifierActive) {
1463
+ window.open(`${parsedUrl.pathname}${parsedUrl.hash}`, "_blank");
1464
+ } else {
1465
+ navigate(`${parsedUrl.pathname}${parsedUrl.hash}`);
1466
+ (_a = transformedElement == null ? void 0 : transformedElement.querySelector(`#${parsedUrl.hash.slice(1)}`)) == null ? void 0 : _a.scrollIntoView();
1467
+ }
1445
1468
  } else {
1446
- navigate(parsedUrl.pathname);
1447
- (_b = transformedElement == null ? void 0 : transformedElement.querySelector(".md-content__inner")) == null ? void 0 : _b.scrollIntoView();
1469
+ if (modifierActive) {
1470
+ window.open(parsedUrl.pathname, "_blank");
1471
+ } else {
1472
+ navigate(parsedUrl.pathname);
1473
+ (_b = transformedElement == null ? void 0 : transformedElement.querySelector(".md-content__inner")) == null ? void 0 : _b.scrollIntoView();
1474
+ }
1448
1475
  }
1449
1476
  }
1450
1477
  }),