Skip to content

Commit

Permalink
Add hierarchy.html page to docs
Browse files Browse the repository at this point in the history
Resolves #182
  • Loading branch information
Gerrit0 committed Jan 1, 2024
1 parent 0117c99 commit 2ce3796
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Features

- Added a new hierarchy.html page to HTML output which displays the full inheritance hierarchy for classes included in the documentation, #182.
- Added a `--navigation.includeFolders` (default: `true`) option to create nested navigation for projects which include many entry points, #2388.
- Type parameters on functions/classes can will now link to the "Type Parameters" section, #2322.
Type parameters have also been changed to have a distinct color from type aliases when rendering, which can be changed with custom CSS.
Expand Down
23 changes: 22 additions & 1 deletion src/lib/output/themes/default/DefaultTheme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export class DefaultTheme extends Theme {
indexTemplate = (pageEvent: PageEvent<ProjectReflection>) => {
return this.getRenderContext(pageEvent).indexTemplate(pageEvent);
};
hierarchyTemplate = (pageEvent: PageEvent<ProjectReflection>) => {
return this.getRenderContext(pageEvent).hierarchyTemplate(pageEvent);
};
defaultLayoutTemplate = (pageEvent: PageEvent<Reflection>, template: RenderTemplate<PageEvent<Reflection>>) => {
return this.getRenderContext(pageEvent).defaultLayout(template, pageEvent);
};
Expand Down Expand Up @@ -148,10 +151,14 @@ export class DefaultTheme extends Theme {
urls.push(new UrlMapping("index.html", project, this.indexTemplate));
} else {
project.url = "modules.html";
urls.push(new UrlMapping<ContainerReflection>("modules.html", project, this.reflectionTemplate));
urls.push(new UrlMapping("modules.html", project, this.reflectionTemplate));
urls.push(new UrlMapping("index.html", project, this.indexTemplate));
}

if (includeHierarchyPage(project)) {
urls.push(new UrlMapping("hierarchy.html", project, this.hierarchyTemplate));
}

project.children?.forEach((child: Reflection) => {
if (child instanceof DeclarationReflection) {
this.buildUrls(child, urls);
Expand Down Expand Up @@ -458,3 +465,17 @@ function shouldShowGroups(reflection: Reflection, opts: { includeCategories: boo
}
return reflection.comment?.hasModifier("@showGroups") === true;
}

function includeHierarchyPage(project: ProjectReflection) {
for (const id in project.reflections) {
const refl = project.reflections[id] as DeclarationReflection;

if (refl.kindOf(ReflectionKind.ClassOrInterface)) {
// Keep this condition in sync with the one in hierarchy.tsx for determining roots
if (!(refl.implementedTypes || refl.extendedTypes) && (refl.implementedBy || refl.extendedBy)) {
return true;
}
}
}
return false;
}
2 changes: 2 additions & 0 deletions src/lib/output/themes/default/DefaultThemeRenderContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { type } from "./partials/type";
import { typeAndParent } from "./partials/typeAndParent";
import { typeParameters } from "./partials/typeParameters";
import { indexTemplate } from "./templates";
import { hierarchyTemplate } from "./templates/hierarchy";
import { reflectionTemplate } from "./templates/reflection";

function bind<F, L extends any[], R>(fn: (f: F, ...a: L) => R, first: F) {
Expand Down Expand Up @@ -116,6 +117,7 @@ export class DefaultThemeRenderContext {

reflectionTemplate = bind(reflectionTemplate, this);
indexTemplate = bind(indexTemplate, this);
hierarchyTemplate = bind(hierarchyTemplate, this);
defaultLayout = bind(defaultLayout, this);

/**
Expand Down
30 changes: 28 additions & 2 deletions src/lib/output/themes/default/partials/hierarchy.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext";
import { JSX } from "../../../../utils";
import type { DeclarationHierarchy } from "../../../../models";
import type { DeclarationHierarchy, Type } from "../../../../models";

const isLinkedReferenceType = (type: Type) =>
type.visit({
reference: (ref) => ref.reflection !== undefined,
}) ?? false;

function hasAnyLinkedReferenceType(h: DeclarationHierarchy | undefined): boolean {
if (!h) return false;

if (!h.isTarget && h.types.some(isLinkedReferenceType)) return true;

return hasAnyLinkedReferenceType(h.next);
}

export function hierarchy(context: DefaultThemeRenderContext, props: DeclarationHierarchy | undefined) {
if (!props) return;

const fullLink = hasAnyLinkedReferenceType(props) ? (
<>
{" "}
(
<a class="link" href={context.relativeURL("hierarchy.html") + "#" + context.page.model.getFullName()}>
view full
</a>
)
</>
) : (
<></>
);

return (
<section class="tsd-panel tsd-hierarchy">
<h4>Hierarchy</h4>
<h4>Hierarchy{fullLink}</h4>
{hierarchyList(context, props)}
</section>
);
Expand Down
43 changes: 43 additions & 0 deletions src/lib/output/themes/default/templates/hierarchy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { DefaultThemeRenderContext } from "../DefaultThemeRenderContext";
import type { PageEvent } from "../../../events";
import { JSX } from "../../../../utils";
import { ReflectionKind, type ProjectReflection, DeclarationReflection } from "../../../../models";

function fullHierarchy(context: DefaultThemeRenderContext, root: DeclarationReflection) {
// Note: We don't use root.anchor for the anchor, because those are built on a per page basis.
// And classes/interfaces get their own page, so all the anchors will be empty anyways.
// Full name should be safe here, since this list only includes classes/interfaces.
return (
<li>
<a id={root.getFullName()} class="tsd-anchor"></a>
<a href={context.urlTo(root)}>
{context.icons[root.kind]()}
{root.name}
</a>
<ul>
{root.implementedBy?.map((child) => {
return child.reflection && fullHierarchy(context, child.reflection as DeclarationReflection);
})}
{root.extendedBy?.map((child) => {
return child.reflection && fullHierarchy(context, child.reflection as DeclarationReflection);
})}
</ul>
</li>
);
}

export function hierarchyTemplate(context: DefaultThemeRenderContext, props: PageEvent<ProjectReflection>) {
// Keep this condition in sync with the one in DefaultTheme.tsx
const roots = (props.project.getReflectionsByKind(ReflectionKind.ClassOrInterface) as DeclarationReflection[])
.filter((refl) => !(refl.implementedTypes || refl.extendedTypes) && (refl.implementedBy || refl.extendedBy))
.sort((a, b) => a.name.localeCompare(b.name));

return (
<>
<h2>Class Hierarchy</h2>
{roots.map((root) => (
<ul class="tsd-full-hierarchy">{fullHierarchy(context, root)}</ul>
))}
</>
);
}
34 changes: 28 additions & 6 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,12 @@ h6 {
line-height: 1.2;
}

h1 > a,
h2 > a,
h3 > a,
h4 > a,
h5 > a,
h6 > a {
h1 > a:not(.link),
h2 > a:not(.link),
h3 > a:not(.link),
h4 > a:not(.link),
h5 > a:not(.link),
h6 > a:not(.link) {
text-decoration: none;
color: var(--color-text);
}
Expand Down Expand Up @@ -649,6 +649,28 @@ input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark {
font-weight: bold;
}

.tsd-full-hierarchy:not(:last-child) {
margin-bottom: 1em;
padding-bottom: 1em;
border-bottom: 1px solid var(--color-accent);
}
.tsd-full-hierarchy,
.tsd-full-hierarchy ul {
list-style: none;
margin: 0;
padding: 0;
}
.tsd-full-hierarchy ul {
padding-left: 1.5rem;
}
.tsd-full-hierarchy a {
padding: 0.25rem 0 !important;
font-size: 1rem;
display: inline-flex;
align-items: center;
color: var(--color-text);
}

.tsd-panel-group.tsd-index-group {
margin-bottom: 0;
}
Expand Down

0 comments on commit 2ce3796

Please sign in to comment.