Skip to content

Commit

Permalink
Merge pull request #2078 from futurGH/configurable-links
Browse files Browse the repository at this point in the history
Allow user-configurable links in header & sidebar
  • Loading branch information
Gerrit0 committed Oct 18, 2022
2 parents 2e8af77 + c0b4420 commit 2eaa476
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 5 deletions.
7 changes: 7 additions & 0 deletions example/typedoc.json
Expand Up @@ -10,5 +10,12 @@
},
"searchGroupBoosts": {
"Classes": 1.5
},
"navigationLinks": {
"Docs": "https://typedoc.org/guides/overview",
"GitHub": "https://github.com/TypeStrong/typedoc"
},
"sidebarLinks": {
"API": "https://typedoc.org/api"
}
}
2 changes: 2 additions & 0 deletions src/lib/output/themes/default/DefaultThemeRenderContext.ts
Expand Up @@ -31,6 +31,7 @@ import {
primaryNavigation,
secondaryNavigation,
settings,
sidebarLinks,
} from "./partials/navigation";
import { parameter } from "./partials/parameter";
import { toolbar } from "./partials/toolbar";
Expand Down Expand Up @@ -105,6 +106,7 @@ export class DefaultThemeRenderContext {
members = bind(members, this);
membersGroup = bind(membersGroup, this);
navigation = bind(navigation, this);
sidebarLinks = bind(sidebarLinks, this);
settings = bind(settings, this);
primaryNavigation = bind(primaryNavigation, this);
secondaryNavigation = bind(secondaryNavigation, this);
Expand Down
15 changes: 15 additions & 0 deletions src/lib/output/themes/default/partials/navigation.tsx
Expand Up @@ -7,6 +7,7 @@ import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext";
export function navigation(context: DefaultThemeRenderContext, props: PageEvent<Reflection>) {
return (
<>
{context.sidebarLinks()}
{context.settings()}
{context.primaryNavigation(props)}
{context.secondaryNavigation(props)}
Expand All @@ -26,6 +27,20 @@ function buildFilterItem(context: DefaultThemeRenderContext, name: string, displ
);
}

export function sidebarLinks(context: DefaultThemeRenderContext) {
const links = Object.entries(context.options.getValue("sidebarLinks"));
if (!links.length) return null;
return (
<nav id="tsd-sidebar-links" class="tsd-navigation">
{links.map(([label, url]) => (
<a href={url} target="_blank">
{label}
</a>
))}
</nav>
);
}

export function settings(context: DefaultThemeRenderContext) {
const defaultFilters = context.options.getValue("visibilityFilters") as Record<string, boolean>;

Expand Down
14 changes: 11 additions & 3 deletions src/lib/output/themes/default/partials/toolbar.tsx
Expand Up @@ -8,24 +8,32 @@ export const toolbar = (context: DefaultThemeRenderContext, props: PageEvent<Ref
<div class="tsd-toolbar-contents container">
<div class="table-cell" id="tsd-search" data-base={context.relativeURL("./")}>
<div class="field">
<label for="tsd-search-field" class="tsd-widget search no-caption">
<label for="tsd-search-field" class="tsd-widget tsd-toolbar-icon search no-caption">
{context.icons.search()}
</label>
<input type="text" id="tsd-search-field" aria-label="Search" />
</div>

<div class="field">
<div id="tsd-toolbar-links">
{Object.entries(context.options.getValue("navigationLinks")).map(([label, url]) => (
<a href={url}>{label}</a>
))}
</div>
</div>

<ul class="results">
<li class="state loading">Preparing search index...</li>
<li class="state failure">The search index is not available</li>
</ul>

<a href={context.relativeURL("index.html")} class="title">
<a href={context.options.getValue("titleLink") ?? context.relativeURL("index.html")} class="title">
{props.project.name}
</a>
</div>

<div class="table-cell" id="tsd-widgets">
<a href="#" class="tsd-widget menu no-caption" data-toggle="menu" aria-label="Menu">
<a href="#" class="tsd-widget tsd-toolbar-icon menu no-caption" data-toggle="menu" aria-label="Menu">
{context.icons.menu()}
</a>
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/lib/utils/options/declaration.ts
Expand Up @@ -119,6 +119,9 @@ export interface TypeDocOptionMap {
hideGenerator: boolean;
searchInComments: boolean;
cleanOutputDir: boolean;
titleLink: string;
navigationLinks: ManuallyValidatedOption<Record<string, string>>;
sidebarLinks: ManuallyValidatedOption<Record<string, string>>;

commentStyle: typeof CommentStyle;
blockTags: `@${string}`[];
Expand Down
43 changes: 43 additions & 0 deletions src/lib/utils/options/sources/typedoc.ts
Expand Up @@ -307,6 +307,49 @@ export function addTypeDocOptions(options: Pick<Options, "addDeclaration">) {
type: ParameterType.Boolean,
defaultValue: true,
});
options.addDeclaration({
name: "titleLink",
help: "Set the link the title in the header points to. Defaults to the documentation homepage.",
type: ParameterType.String,
});
options.addDeclaration({
name: "navigationLinks",
help: "Defines links to be included in the header.",
type: ParameterType.Mixed,
defaultValue: {},
validate(value) {
if (!isObject(value)) {
throw new Error(
`navigationLinks must be an object with string labels as keys and URL values.`
);
}

if (Object.values(value).some((x) => typeof x !== "string")) {
throw new Error(
`All values of navigationLinks must be string URLs.`
);
}
},
});
options.addDeclaration({
name: "sidebarLinks",
help: "Defines links to be included in the sidebar.",
type: ParameterType.Mixed,
defaultValue: {},
validate(value) {
if (!isObject(value)) {
throw new Error(
`sidebarLinks must be an object with string labels as keys and URL values.`
);
}

if (Object.values(value).some((x) => typeof x !== "string")) {
throw new Error(
`All values of sidebarLinks must be string URLs.`
);
}
},
});

///////////////////////////
///// Comment Options /////
Expand Down
28 changes: 28 additions & 0 deletions src/test/utils/options/default-options.test.ts
Expand Up @@ -108,4 +108,32 @@ describe("Default Options", () => {
doesNotThrow(() => opts.setValue("searchGroupBoosts", { Enum: 5 }));
});
});

describe("headerLinks", () => {
it("Should disallow non-objects", () => {
throws(() => opts.setValue("navigationLinks", null as never));
});

it("Should disallow non-strings", () => {
throws(() =>
opts.setValue("navigationLinks", {
Home: true as any as string,
})
);
});
});

describe("sidebarLinks", () => {
it("Should disallow non-objects", () => {
throws(() => opts.setValue("sidebarLinks", null as never));
});

it("Should disallow non-strings", () => {
throws(() =>
opts.setValue("sidebarLinks", {
Home: true as any as string,
})
);
});
});
});
36 changes: 34 additions & 2 deletions static/style.css
Expand Up @@ -825,6 +825,15 @@ input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark {
padding-left: 5.5rem;
}

#tsd-sidebar-links a {
margin-top: 0;
margin-bottom: 0.5rem;
line-height: 1.25rem;
}
#tsd-sidebar-links a:last-of-type {
margin-bottom: 0;
}

a.tsd-index-link {
margin: 0.25rem 0;
font-size: 1rem;
Expand Down Expand Up @@ -978,7 +987,8 @@ a.tsd-index-link {
right: -40px;
}
#tsd-search .field input,
#tsd-search .title {
#tsd-search .title,
#tsd-toolbar-links a {
transition: opacity 0.2s;
}
#tsd-search .results {
Expand Down Expand Up @@ -1022,7 +1032,8 @@ a.tsd-index-link {
top: 0;
opacity: 1;
}
#tsd-search.has-focus .title {
#tsd-search.has-focus .title,
#tsd-search.has-focus #tsd-toolbar-links a {
z-index: 0;
opacity: 0;
}
Expand All @@ -1036,6 +1047,22 @@ a.tsd-index-link {
display: block;
}

#tsd-toolbar-links {
position: absolute;
top: 0;
right: 2rem;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
}
#tsd-toolbar-links a {
margin-left: 1.5rem;
}
#tsd-toolbar-links a:hover {
text-decoration: underline;
}

.tsd-signature {
margin: 0 0 1rem 0;
padding: 1rem 0.5rem;
Expand Down Expand Up @@ -1134,6 +1161,11 @@ ul.tsd-type-parameter-list h5 {
.tsd-page-toolbar .table-cell:first-child {
width: 100%;
}
.tsd-page-toolbar .tsd-toolbar-icon {
box-sizing: border-box;
line-height: 0;
padding: 12px 0;
}

.tsd-page-toolbar--hide {
transform: translateY(-100%);
Expand Down

0 comments on commit 2eaa476

Please sign in to comment.