typedoc 0.23.16 → 0.23.17

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.
@@ -6,6 +6,7 @@ export declare class SourcePlugin extends ConverterComponent {
6
6
  readonly disableSources: boolean;
7
7
  readonly gitRevision: string;
8
8
  readonly gitRemote: string;
9
+ readonly sourceLinkTemplate: string;
9
10
  readonly basePath: string;
10
11
  /**
11
12
  * All file names to find the base path from.
@@ -100,10 +100,7 @@ let SourcePlugin = class SourcePlugin extends components_1.ConverterComponent {
100
100
  for (const source of refl.sources || []) {
101
101
  if (repository_1.gitIsInstalled) {
102
102
  const repo = this.getRepository(source.fullFileName);
103
- source.url = repo?.getURL(source.fullFileName);
104
- if (source.url) {
105
- source.url += `#${repo.getLineNumberAnchor(source.line)}`;
106
- }
103
+ source.url = repo?.getURL(source.fullFileName, source.line);
107
104
  }
108
105
  source.fileName = (0, fs_1.normalizePath)((0, path_1.relative)(basePath, source.fullFileName));
109
106
  }
@@ -131,7 +128,7 @@ let SourcePlugin = class SourcePlugin extends components_1.ConverterComponent {
131
128
  }
132
129
  }
133
130
  // Try to create a new repository
134
- const repository = repository_1.Repository.tryCreateRepository(dirName, this.gitRevision, this.gitRemote, this.application.logger);
131
+ const repository = repository_1.Repository.tryCreateRepository(dirName, this.sourceLinkTemplate, this.gitRevision, this.gitRemote, this.application.logger);
135
132
  if (repository) {
136
133
  this.repositories[repository.path.toLowerCase()] = repository;
137
134
  return repository;
@@ -149,6 +146,9 @@ __decorate([
149
146
  __decorate([
150
147
  (0, utils_1.BindOption)("gitRemote")
151
148
  ], SourcePlugin.prototype, "gitRemote", void 0);
149
+ __decorate([
150
+ (0, utils_1.BindOption)("sourceLinkTemplate")
151
+ ], SourcePlugin.prototype, "sourceLinkTemplate", void 0);
152
152
  __decorate([
153
153
  (0, utils_1.BindOption)("basePath")
154
154
  ], SourcePlugin.prototype, "basePath", void 0);
@@ -12,28 +12,21 @@ export declare class Repository {
12
12
  * All files tracked by the repository.
13
13
  */
14
14
  files: Set<string>;
15
- /**
16
- * The base url for link creation.
17
- */
18
- baseUrl: string;
19
- /**
20
- * The anchor prefix used to select lines, usually `L`
21
- */
22
- anchorPrefix: string;
15
+ urlTemplate: string;
16
+ gitRevision: string;
23
17
  /**
24
18
  * Create a new Repository instance.
25
19
  *
26
20
  * @param path The root path of the repository.
27
21
  */
28
- constructor(path: string, baseUrl: string);
22
+ constructor(path: string, gitRevision: string, urlTemplate: string);
29
23
  /**
30
24
  * Get the URL of the given file on GitHub or Bitbucket.
31
25
  *
32
26
  * @param fileName The file whose URL should be determined.
33
27
  * @returns A URL pointing to the web preview of the given file or undefined.
34
28
  */
35
- getURL(fileName: string): string | undefined;
36
- getLineNumberAnchor(lineNumber: number): string;
29
+ getURL(fileName: string, line: number): string | undefined;
37
30
  /**
38
31
  * Try to create a new repository instance.
39
32
  *
@@ -43,6 +36,6 @@ export declare class Repository {
43
36
  * @param path The potential repository root.
44
37
  * @returns A new instance of {@link Repository} or undefined.
45
38
  */
46
- static tryCreateRepository(path: string, gitRevision: string, gitRemote: string, logger: Logger): Repository | undefined;
39
+ static tryCreateRepository(path: string, sourceLinkTemplate: string, gitRevision: string, gitRemote: string, logger: Logger): Repository | undefined;
47
40
  }
48
- export declare function guessBaseUrl(gitRevision: string, remotes: string[]): string | undefined;
41
+ export declare function guessSourceUrlTemplate(remotes: string[]): string | undefined;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.guessBaseUrl = exports.Repository = exports.gitIsInstalled = void 0;
3
+ exports.guessSourceUrlTemplate = exports.Repository = exports.gitIsInstalled = void 0;
4
4
  const child_process_1 = require("child_process");
5
5
  const base_path_1 = require("../utils/base-path");
6
6
  const TEN_MEGABYTES = 1024 * 10000;
@@ -21,14 +21,14 @@ class Repository {
21
21
  *
22
22
  * @param path The root path of the repository.
23
23
  */
24
- constructor(path, baseUrl) {
24
+ constructor(path, gitRevision, urlTemplate) {
25
25
  /**
26
26
  * All files tracked by the repository.
27
27
  */
28
28
  this.files = new Set();
29
29
  this.path = path;
30
- this.baseUrl = baseUrl;
31
- this.anchorPrefix = guessAnchorPrefix(this.baseUrl);
30
+ this.gitRevision = gitRevision;
31
+ this.urlTemplate = urlTemplate;
32
32
  const out = git("-C", path, "ls-files");
33
33
  if (out.status === 0) {
34
34
  out.stdout.split("\n").forEach((file) => {
@@ -44,14 +44,16 @@ class Repository {
44
44
  * @param fileName The file whose URL should be determined.
45
45
  * @returns A URL pointing to the web preview of the given file or undefined.
46
46
  */
47
- getURL(fileName) {
47
+ getURL(fileName, line) {
48
48
  if (!this.files.has(fileName)) {
49
49
  return;
50
50
  }
51
- return `${this.baseUrl}/${fileName.substring(this.path.length + 1)}`;
52
- }
53
- getLineNumberAnchor(lineNumber) {
54
- return `${this.anchorPrefix}${lineNumber}`;
51
+ const replacements = {
52
+ gitRevision: this.gitRevision,
53
+ path: fileName.substring(this.path.length + 1),
54
+ line,
55
+ };
56
+ return this.urlTemplate.replace(/\{(gitRevision|path|line)\}/g, (_, key) => replacements[key]);
55
57
  }
56
58
  /**
57
59
  * Try to create a new repository instance.
@@ -62,29 +64,33 @@ class Repository {
62
64
  * @param path The potential repository root.
63
65
  * @returns A new instance of {@link Repository} or undefined.
64
66
  */
65
- static tryCreateRepository(path, gitRevision, gitRemote, logger) {
67
+ static tryCreateRepository(path, sourceLinkTemplate, gitRevision, gitRemote, logger) {
66
68
  const topLevel = git("-C", path, "rev-parse", "--show-toplevel");
67
69
  if (topLevel.status !== 0)
68
70
  return;
69
71
  gitRevision || (gitRevision = git("-C", path, "rev-parse", "--short", "HEAD").stdout.trim());
70
72
  if (!gitRevision)
71
73
  return; // Will only happen in a repo with no commits.
72
- let baseUrl;
73
- if (/^https?:\/\//.test(gitRemote)) {
74
- baseUrl = `${gitRemote}/${gitRevision}`;
74
+ let urlTemplate;
75
+ if (sourceLinkTemplate) {
76
+ urlTemplate = sourceLinkTemplate;
77
+ }
78
+ else if (/^https?:\/\//.test(gitRemote)) {
79
+ logger.warn("Using a link as the gitRemote is deprecated and will be removed in 0.24.");
80
+ urlTemplate = `${gitRemote}/{gitRevision}`;
75
81
  }
76
82
  else {
77
83
  const remotesOut = git("-C", path, "remote", "get-url", gitRemote);
78
84
  if (remotesOut.status === 0) {
79
- baseUrl = guessBaseUrl(gitRevision, remotesOut.stdout.split("\n"));
85
+ urlTemplate = guessSourceUrlTemplate(remotesOut.stdout.split("\n"));
80
86
  }
81
87
  else {
82
88
  logger.warn(`The provided git remote "${gitRemote}" was not valid. Source links will be broken.`);
83
89
  }
84
90
  }
85
- if (!baseUrl)
91
+ if (!urlTemplate)
86
92
  return;
87
- return new Repository(base_path_1.BasePath.normalize(topLevel.stdout.replace("\n", "")), baseUrl);
93
+ return new Repository(base_path_1.BasePath.normalize(topLevel.stdout.replace("\n", "")), gitRevision, urlTemplate);
88
94
  }
89
95
  }
90
96
  exports.Repository = Repository;
@@ -100,7 +106,7 @@ const repoExpressions = [
100
106
  /(bitbucket.org)[:/]([^/]+)\/(.*)/,
101
107
  /(gitlab.com)[:/]([^/]+)\/(.*)/,
102
108
  ];
103
- function guessBaseUrl(gitRevision, remotes) {
109
+ function guessSourceUrlTemplate(remotes) {
104
110
  let hostname = "";
105
111
  let user = "";
106
112
  let project = "";
@@ -121,18 +127,14 @@ function guessBaseUrl(gitRevision, remotes) {
121
127
  project = project.slice(0, -4);
122
128
  }
123
129
  let sourcePath = "blob";
130
+ let anchorPrefix = "L";
124
131
  if (hostname.includes("gitlab")) {
125
132
  sourcePath = "-/blob";
126
133
  }
127
134
  else if (hostname.includes("bitbucket")) {
128
135
  sourcePath = "src";
136
+ anchorPrefix = "lines-";
129
137
  }
130
- return `https://${hostname}/${user}/${project}/${sourcePath}/${gitRevision}`;
131
- }
132
- exports.guessBaseUrl = guessBaseUrl;
133
- function guessAnchorPrefix(url) {
134
- if (url.includes("bitbucket")) {
135
- return "lines-";
136
- }
137
- return "L";
138
+ return `https://${hostname}/${user}/${project}/${sourcePath}/{gitRevision}/{path}#${anchorPrefix}{line}`;
138
139
  }
140
+ exports.guessSourceUrlTemplate = guessSourceUrlTemplate;
@@ -44,6 +44,7 @@ export declare class DefaultThemeRenderContext {
44
44
  members: (props: import("../../../models").ContainerReflection) => import("../../../utils/jsx.elements").JsxElement;
45
45
  membersGroup: (group: import("../../../models").ReflectionGroup) => import("../../../utils/jsx.elements").JsxElement;
46
46
  navigation: (props: import("../..").PageEvent<Reflection>) => import("../../../utils/jsx.elements").JsxElement;
47
+ sidebarLinks: () => import("../../../utils/jsx.elements").JsxElement | null;
47
48
  settings: () => import("../../../utils/jsx.elements").JsxElement;
48
49
  primaryNavigation: (props: import("../..").PageEvent<Reflection>) => import("../../../utils/jsx.elements").JsxElement;
49
50
  secondaryNavigation: (props: import("../..").PageEvent<Reflection>) => import("../../../utils/jsx.elements").JsxElement | undefined;
@@ -77,6 +77,7 @@ class DefaultThemeRenderContext {
77
77
  this.members = bind(members_1.members, this);
78
78
  this.membersGroup = bind(members_group_1.membersGroup, this);
79
79
  this.navigation = bind(navigation_1.navigation, this);
80
+ this.sidebarLinks = bind(navigation_1.sidebarLinks, this);
80
81
  this.settings = bind(navigation_1.settings, this);
81
82
  this.primaryNavigation = bind(navigation_1.primaryNavigation, this);
82
83
  this.secondaryNavigation = bind(navigation_1.secondaryNavigation, this);
@@ -3,6 +3,7 @@ import { JSX } from "../../../../utils";
3
3
  import type { PageEvent } from "../../../events";
4
4
  import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext";
5
5
  export declare function navigation(context: DefaultThemeRenderContext, props: PageEvent<Reflection>): JSX.Element;
6
+ export declare function sidebarLinks(context: DefaultThemeRenderContext): JSX.Element | null;
6
7
  export declare function settings(context: DefaultThemeRenderContext): JSX.Element;
7
8
  export declare function primaryNavigation(context: DefaultThemeRenderContext, props: PageEvent<Reflection>): JSX.Element;
8
9
  export declare function secondaryNavigation(context: DefaultThemeRenderContext, props: PageEvent<Reflection>): JSX.Element | undefined;
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.secondaryNavigation = exports.primaryNavigation = exports.settings = exports.navigation = void 0;
3
+ exports.secondaryNavigation = exports.primaryNavigation = exports.settings = exports.sidebarLinks = exports.navigation = void 0;
4
4
  const models_1 = require("../../../../models");
5
5
  const utils_1 = require("../../../../utils");
6
6
  const lib_1 = require("../../lib");
7
7
  function navigation(context, props) {
8
8
  return (utils_1.JSX.createElement(utils_1.JSX.Fragment, null,
9
+ context.sidebarLinks(),
9
10
  context.settings(),
10
11
  context.primaryNavigation(props),
11
12
  context.secondaryNavigation(props)));
@@ -18,6 +19,13 @@ function buildFilterItem(context, name, displayName, defaultValue) {
18
19
  context.icons.checkbox(),
19
20
  utils_1.JSX.createElement("span", null, displayName))));
20
21
  }
22
+ function sidebarLinks(context) {
23
+ const links = Object.entries(context.options.getValue("sidebarLinks"));
24
+ if (!links.length)
25
+ return null;
26
+ return (utils_1.JSX.createElement("nav", { id: "tsd-sidebar-links", class: "tsd-navigation" }, links.map(([label, url]) => (utils_1.JSX.createElement("a", { href: url, target: "_blank" }, label)))));
27
+ }
28
+ exports.sidebarLinks = sidebarLinks;
21
29
  function settings(context) {
22
30
  const defaultFilters = context.options.getValue("visibilityFilters");
23
31
  const visibilityOptions = [];
@@ -6,12 +6,14 @@ const toolbar = (context, props) => (utils_1.JSX.createElement("header", { class
6
6
  utils_1.JSX.createElement("div", { class: "tsd-toolbar-contents container" },
7
7
  utils_1.JSX.createElement("div", { class: "table-cell", id: "tsd-search", "data-base": context.relativeURL("./") },
8
8
  utils_1.JSX.createElement("div", { class: "field" },
9
- utils_1.JSX.createElement("label", { for: "tsd-search-field", class: "tsd-widget search no-caption" }, context.icons.search()),
9
+ utils_1.JSX.createElement("label", { for: "tsd-search-field", class: "tsd-widget tsd-toolbar-icon search no-caption" }, context.icons.search()),
10
10
  utils_1.JSX.createElement("input", { type: "text", id: "tsd-search-field", "aria-label": "Search" })),
11
+ utils_1.JSX.createElement("div", { class: "field" },
12
+ utils_1.JSX.createElement("div", { id: "tsd-toolbar-links" }, Object.entries(context.options.getValue("navigationLinks")).map(([label, url]) => (utils_1.JSX.createElement("a", { href: url }, label))))),
11
13
  utils_1.JSX.createElement("ul", { class: "results" },
12
14
  utils_1.JSX.createElement("li", { class: "state loading" }, "Preparing search index..."),
13
15
  utils_1.JSX.createElement("li", { class: "state failure" }, "The search index is not available")),
14
- utils_1.JSX.createElement("a", { href: context.relativeURL("index.html"), class: "title" }, props.project.name)),
16
+ utils_1.JSX.createElement("a", { href: context.options.getValue("titleLink") ?? context.relativeURL("index.html"), class: "title" }, props.project.name)),
15
17
  utils_1.JSX.createElement("div", { class: "table-cell", id: "tsd-widgets" },
16
- utils_1.JSX.createElement("a", { href: "#", class: "tsd-widget menu no-caption", "data-toggle": "menu", "aria-label": "Menu" }, context.icons.menu())))));
18
+ utils_1.JSX.createElement("a", { href: "#", class: "tsd-widget tsd-toolbar-icon menu no-caption", "data-toggle": "menu", "aria-label": "Menu" }, context.icons.menu())))));
17
19
  exports.toolbar = toolbar;
@@ -74,6 +74,7 @@ export interface TypeDocOptionMap {
74
74
  excludeTags: `@${string}`[];
75
75
  readme: string;
76
76
  cname: string;
77
+ sourceLinkTemplate: string;
77
78
  gitRevision: string;
78
79
  gitRemote: string;
79
80
  htmlLang: string;
@@ -82,6 +83,9 @@ export interface TypeDocOptionMap {
82
83
  hideGenerator: boolean;
83
84
  searchInComments: boolean;
84
85
  cleanOutputDir: boolean;
86
+ titleLink: string;
87
+ navigationLinks: ManuallyValidatedOption<Record<string, string>>;
88
+ sidebarLinks: ManuallyValidatedOption<Record<string, string>>;
85
89
  commentStyle: typeof CommentStyle;
86
90
  blockTags: `@${string}`[];
87
91
  inlineTags: `@${string}`[];
@@ -235,6 +235,10 @@ function addTypeDocOptions(options) {
235
235
  name: "cname",
236
236
  help: "Set the CNAME file text, it's useful for custom domains on GitHub Pages.",
237
237
  });
238
+ options.addDeclaration({
239
+ name: "sourceLinkTemplate",
240
+ help: "Specify a link template to be used when generating source urls. If not set, will be automatically created using the git remote. Supports {path}, {line}, {gitRevision} placeholders.",
241
+ });
238
242
  options.addDeclaration({
239
243
  name: "gitRevision",
240
244
  help: "Use specified revision instead of the last revision for linking to GitHub/Bitbucket source files.",
@@ -276,6 +280,39 @@ function addTypeDocOptions(options) {
276
280
  type: declaration_1.ParameterType.Boolean,
277
281
  defaultValue: true,
278
282
  });
283
+ options.addDeclaration({
284
+ name: "titleLink",
285
+ help: "Set the link the title in the header points to. Defaults to the documentation homepage.",
286
+ type: declaration_1.ParameterType.String,
287
+ });
288
+ options.addDeclaration({
289
+ name: "navigationLinks",
290
+ help: "Defines links to be included in the header.",
291
+ type: declaration_1.ParameterType.Mixed,
292
+ defaultValue: {},
293
+ validate(value) {
294
+ if (!isObject(value)) {
295
+ throw new Error(`navigationLinks must be an object with string labels as keys and URL values.`);
296
+ }
297
+ if (Object.values(value).some((x) => typeof x !== "string")) {
298
+ throw new Error(`All values of navigationLinks must be string URLs.`);
299
+ }
300
+ },
301
+ });
302
+ options.addDeclaration({
303
+ name: "sidebarLinks",
304
+ help: "Defines links to be included in the sidebar.",
305
+ type: declaration_1.ParameterType.Mixed,
306
+ defaultValue: {},
307
+ validate(value) {
308
+ if (!isObject(value)) {
309
+ throw new Error(`sidebarLinks must be an object with string labels as keys and URL values.`);
310
+ }
311
+ if (Object.values(value).some((x) => typeof x !== "string")) {
312
+ throw new Error(`All values of sidebarLinks must be string URLs.`);
313
+ }
314
+ },
315
+ });
279
316
  ///////////////////////////
280
317
  ///// Comment Options /////
281
318
  ///////////////////////////
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "typedoc",
3
3
  "description": "Create api documentation for TypeScript projects.",
4
- "version": "0.23.16",
4
+ "version": "0.23.17",
5
5
  "homepage": "https://typedoc.org",
6
6
  "main": "./dist/index.js",
7
7
  "exports": {
package/static/style.css CHANGED
@@ -825,6 +825,15 @@ input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark {
825
825
  padding-left: 5.5rem;
826
826
  }
827
827
 
828
+ #tsd-sidebar-links a {
829
+ margin-top: 0;
830
+ margin-bottom: 0.5rem;
831
+ line-height: 1.25rem;
832
+ }
833
+ #tsd-sidebar-links a:last-of-type {
834
+ margin-bottom: 0;
835
+ }
836
+
828
837
  a.tsd-index-link {
829
838
  margin: 0.25rem 0;
830
839
  font-size: 1rem;
@@ -978,7 +987,8 @@ a.tsd-index-link {
978
987
  right: -40px;
979
988
  }
980
989
  #tsd-search .field input,
981
- #tsd-search .title {
990
+ #tsd-search .title,
991
+ #tsd-toolbar-links a {
982
992
  transition: opacity 0.2s;
983
993
  }
984
994
  #tsd-search .results {
@@ -1022,7 +1032,8 @@ a.tsd-index-link {
1022
1032
  top: 0;
1023
1033
  opacity: 1;
1024
1034
  }
1025
- #tsd-search.has-focus .title {
1035
+ #tsd-search.has-focus .title,
1036
+ #tsd-search.has-focus #tsd-toolbar-links a {
1026
1037
  z-index: 0;
1027
1038
  opacity: 0;
1028
1039
  }
@@ -1036,6 +1047,22 @@ a.tsd-index-link {
1036
1047
  display: block;
1037
1048
  }
1038
1049
 
1050
+ #tsd-toolbar-links {
1051
+ position: absolute;
1052
+ top: 0;
1053
+ right: 2rem;
1054
+ height: 100%;
1055
+ display: flex;
1056
+ align-items: center;
1057
+ justify-content: flex-end;
1058
+ }
1059
+ #tsd-toolbar-links a {
1060
+ margin-left: 1.5rem;
1061
+ }
1062
+ #tsd-toolbar-links a:hover {
1063
+ text-decoration: underline;
1064
+ }
1065
+
1039
1066
  .tsd-signature {
1040
1067
  margin: 0 0 1rem 0;
1041
1068
  padding: 1rem 0.5rem;
@@ -1134,6 +1161,11 @@ ul.tsd-type-parameter-list h5 {
1134
1161
  .tsd-page-toolbar .table-cell:first-child {
1135
1162
  width: 100%;
1136
1163
  }
1164
+ .tsd-page-toolbar .tsd-toolbar-icon {
1165
+ box-sizing: border-box;
1166
+ line-height: 0;
1167
+ padding: 12px 0;
1168
+ }
1137
1169
 
1138
1170
  .tsd-page-toolbar--hide {
1139
1171
  transform: translateY(-100%);