UNPKG

typedoc

Version:

Create api documentation for TypeScript projects.

484 lines (483 loc) 23.7 kB
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); var _, done = false; for (var i = decorators.length - 1; i >= 0; i--) { var context = {}; for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; for (var p in contextIn.access) context.access[p] = contextIn.access[p]; context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); if (kind === "accessor") { if (result === void 0) continue; if (result === null || typeof result !== "object") throw new TypeError("Object expected"); if (_ = accept(result.get)) descriptor.get = _; if (_ = accept(result.set)) descriptor.set = _; if (_ = accept(result.init)) initializers.unshift(_); } else if (_ = accept(result)) { if (kind === "field") initializers.unshift(_); else descriptor[key] = _; } } if (target) Object.defineProperty(target, contextIn.name, descriptor); done = true; }; var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { var useValue = arguments.length > 2; for (var i = 0; i < initializers.length; i++) { value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); } return useValue ? value : void 0; }; import { Theme } from "../../theme.js"; import { ReflectionKind, ProjectReflection, DeclarationReflection, SignatureReflection, ReflectionCategory, ReflectionGroup, TypeParameterReflection, ReferenceReflection, } from "../../../models/index.js"; import { UrlMapping } from "../../models/UrlMapping.js"; import { DefaultThemeRenderContext } from "./DefaultThemeRenderContext.js"; import { filterMap, JSX, Option } from "../../../utils/index.js"; import { classNames, getDisplayName, getHierarchyRoots, toStyleClass } from "../lib.js"; import { icons } from "./partials/icon.js"; import { Slugger } from "./Slugger.js"; import { createNormalizedUrl } from "../../../utils/html.js"; let DefaultTheme = (() => { let _classSuper = Theme; let _sluggerConfiguration_decorators; let _sluggerConfiguration_initializers = []; let _sluggerConfiguration_extraInitializers = []; return class DefaultTheme extends _classSuper { static { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0; _sluggerConfiguration_decorators = [Option("sluggerConfiguration")]; __esDecorate(this, null, _sluggerConfiguration_decorators, { kind: "accessor", name: "sluggerConfiguration", static: false, private: false, access: { has: obj => "sluggerConfiguration" in obj, get: obj => obj.sluggerConfiguration, set: (obj, value) => { obj.sluggerConfiguration = value; } }, metadata: _metadata }, _sluggerConfiguration_initializers, _sluggerConfiguration_extraInitializers); if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); } // Note: This will always contain lowercased names to avoid issues with // case-insensitive file systems. usedFileNames = new Set(); #sluggerConfiguration_accessor_storage = __runInitializers(this, _sluggerConfiguration_initializers, void 0); get sluggerConfiguration() { return this.#sluggerConfiguration_accessor_storage; } set sluggerConfiguration(value) { this.#sluggerConfiguration_accessor_storage = value; } /** @internal */ markedPlugin = __runInitializers(this, _sluggerConfiguration_extraInitializers); /** * The icons which will actually be rendered. The source of truth lives on the theme, and * the {@link DefaultThemeRenderContext.icons} member will produce references to these. * * These icons will be written twice. Once to an `icons.svg` file in the assets directory * which will be referenced by icons on the context, and once to an `icons.js` file so that * references to the icons can be dynamically embedded within the page for use by the search * dropdown and when loading the page on `file://` urls. * * Custom themes may overwrite this entire object or individual properties on it to customize * the icons used within the page, however TypeDoc currently assumes that all icons are svg * elements, so custom themes must also use svg elements. */ icons = { ...icons }; getRenderContext(pageEvent) { return new DefaultThemeRenderContext(this, pageEvent, this.application.options); } documentTemplate = (pageEvent) => { return this.getRenderContext(pageEvent).documentTemplate(pageEvent); }; reflectionTemplate = (pageEvent) => { return this.getRenderContext(pageEvent).reflectionTemplate(pageEvent); }; indexTemplate = (pageEvent) => { return this.getRenderContext(pageEvent).indexTemplate(pageEvent); }; hierarchyTemplate = (pageEvent) => { return this.getRenderContext(pageEvent).hierarchyTemplate(pageEvent); }; defaultLayoutTemplate = (pageEvent, template) => { return this.getRenderContext(pageEvent).defaultLayout(template, pageEvent); }; getReflectionClasses(reflection) { const filters = this.application.options.getValue("visibilityFilters"); return getReflectionClasses(reflection, filters); } /** * Mappings of reflections kinds to templates used by this theme. */ mappings = [ { kind: [ReflectionKind.Class], directory: "classes", template: this.reflectionTemplate, }, { kind: [ReflectionKind.Interface], directory: "interfaces", template: this.reflectionTemplate, }, { kind: [ReflectionKind.Enum], directory: "enums", template: this.reflectionTemplate, }, { kind: [ReflectionKind.Namespace, ReflectionKind.Module], directory: "modules", template: this.reflectionTemplate, }, { kind: [ReflectionKind.TypeAlias], directory: "types", template: this.reflectionTemplate, }, { kind: [ReflectionKind.Function], directory: "functions", template: this.reflectionTemplate, }, { kind: [ReflectionKind.Variable], directory: "variables", template: this.reflectionTemplate, }, { kind: [ReflectionKind.Document], directory: "documents", template: this.documentTemplate, }, ]; static URL_PREFIX = /^(http|ftp)s?:\/\//; /** * Create a new DefaultTheme instance. * * @param renderer The renderer this theme is attached to. */ constructor(renderer) { super(renderer); this.markedPlugin = renderer.markedPlugin; } /** * Map the models of the given project to the desired output files. * * @param project The project whose urls should be generated. * @returns A list of {@link UrlMapping} instances defining which models * should be rendered to which files. */ getUrls(project) { this.usedFileNames = new Set(); const urls = []; this.setSlugger(project, new Slugger(this.sluggerConfiguration)); if (!project.readme?.length) { project.url = "index.html"; urls.push(new UrlMapping("index.html", project, this.reflectionTemplate)); } else { project.url = "modules.html"; urls.push(new UrlMapping("modules.html", project, this.reflectionTemplate)); urls.push(new UrlMapping("index.html", project, this.indexTemplate)); } if (this.application.options.getValue("includeHierarchySummary") && getHierarchyRoots(project).length) { urls.push(new UrlMapping("hierarchy.html", project, this.hierarchyTemplate)); } project.childrenIncludingDocuments?.forEach((child) => this.buildUrls(child, urls)); return urls; } /** * @param reflection The reflection the url should be generated for. */ getFileName(reflection) { const parts = [createNormalizedUrl(reflection.name)]; while (reflection.parent && !reflection.parent.isProject()) { reflection = reflection.parent; parts.unshift(createNormalizedUrl(reflection.name)); } const baseName = parts.join("."); const lowerBaseName = baseName.toLocaleLowerCase(); if (this.usedFileNames.has(lowerBaseName)) { let index = 1; while (this.usedFileNames.has(`${lowerBaseName}-${index}`)) { ++index; } this.usedFileNames.add(`${lowerBaseName}-${index}`); return `${baseName}-${index}.html`; } this.usedFileNames.add(lowerBaseName); return `${baseName}.html`; } /** * Return the template mapping for the given reflection. * * @param reflection The reflection whose mapping should be resolved. * @returns The found mapping or undefined if no mapping could be found. */ getMapping(reflection) { return this.mappings.find((mapping) => reflection.kindOf(mapping.kind)); } /** * Build the url for the the given reflection and all of its children. * * @param reflection The reflection the url should be created for. * @param urls The array the url should be appended to. * @returns The altered urls array. */ buildUrls(reflection, urls) { const mapping = this.getMapping(reflection); if (mapping) { if (!reflection.url || !DefaultTheme.URL_PREFIX.test(reflection.url)) { const url = [mapping.directory, this.getFileName(reflection)].join("/"); urls.push(new UrlMapping(url, reflection, mapping.template)); this.setSlugger(reflection, new Slugger(this.sluggerConfiguration)); reflection.url = url; reflection.hasOwnDocument = true; } reflection.traverse((child) => { if (child.isDeclaration() || child.isDocument()) { this.buildUrls(child, urls); } else { this.applyAnchorUrl(child, reflection); } return true; }); } else if (reflection.parent) { this.applyAnchorUrl(reflection, reflection.parent); } return urls; } render(page, template) { const templateOutput = this.defaultLayoutTemplate(page, template); return "<!DOCTYPE html>" + JSX.renderElement(templateOutput) + "\n"; } _navigationCache; /** * If implementing a custom theme, it is recommended to override {@link buildNavigation} instead. */ getNavigation(project) { // This is ok because currently TypeDoc wipes out the theme after each render. // Might need to change in the future, but it's fine for now. if (this._navigationCache) { return this._navigationCache; } return (this._navigationCache = this.buildNavigation(project)); } buildNavigation(project) { // eslint-disable-next-line @typescript-eslint/no-this-alias const theme = this; const opts = this.application.options.getValue("navigation"); const leaves = this.application.options.getValue("navigationLeaves"); return getNavigationElements(project) || []; function toNavigation(element) { if (opts.excludeReferences && element instanceof ReferenceReflection) { return; } const children = getNavigationElements(element); if (element instanceof ReflectionCategory || element instanceof ReflectionGroup) { if (!children?.length) { return; } return { text: element.title, children, }; } return { text: getDisplayName(element), path: element.url, kind: element.kind & ReflectionKind.Project ? undefined : element.kind, class: classNames({ deprecated: element.isDeprecated() }, theme.getReflectionClasses(element)), children: children?.length ? children : undefined, }; } function getNavigationElements(parent) { if (parent instanceof ReflectionCategory) { return filterMap(parent.children, toNavigation); } if (parent instanceof ReflectionGroup) { if (shouldShowCategories(parent.owningReflection, opts) && parent.categories) { return filterMap(parent.categories, toNavigation); } return filterMap(parent.children, toNavigation); } if (leaves.includes(parent.getFullName())) { return; } if (!parent.kindOf(ReflectionKind.MayContainDocuments)) { return; } if (parent.isDocument()) { return filterMap(parent.children, toNavigation); } if (!parent.kindOf(ReflectionKind.SomeModule | ReflectionKind.Project)) { // Tricky: Non-module children don't show up in the navigation pane, // but any documents added by them should. return filterMap(parent.documents, toNavigation); } if (parent.categories && shouldShowCategories(parent, opts)) { return filterMapWithNoneCollection(parent.categories); } if (parent.groups && shouldShowGroups(parent, opts)) { return filterMapWithNoneCollection(parent.groups); } if (opts.includeFolders && parent.childrenIncludingDocuments?.some((child) => child.name.includes("/"))) { return deriveModuleFolders(parent.childrenIncludingDocuments); } return filterMap(parent.childrenIncludingDocuments, toNavigation); } function filterMapWithNoneCollection(reflection) { const none = reflection.find((x) => x.title.toLocaleLowerCase() === "none"); const others = reflection.filter((x) => x.title.toLocaleLowerCase() !== "none"); const mappedOthers = filterMap(others, toNavigation); if (none) { const noneMappedChildren = filterMap(none.children, toNavigation); return [...noneMappedChildren, ...mappedOthers]; } return mappedOthers; } function deriveModuleFolders(children) { const result = []; const resolveOrCreateParents = (path, root = result) => { if (path.length > 1) { const inner = root.find((el) => el.text === path[0]); if (inner) { inner.children ||= []; return resolveOrCreateParents(path.slice(1), inner.children); } else { root.push({ text: path[0], children: [], }); return resolveOrCreateParents(path.slice(1), root[root.length - 1].children); } } return root; }; // Note: This might end up putting a module within another module if we document // both foo/index.ts and foo/bar.ts. for (const child of children.filter((c) => c.hasOwnDocument)) { const nav = toNavigation(child); if (nav) { const parts = child.name.split("/"); const collection = resolveOrCreateParents(parts); nav.text = parts[parts.length - 1]; collection.push(nav); } } // Now merge single-possible-paths together so we don't have folders in our navigation // which contain only another single folder. if (opts.compactFolders) { const queue = [...result]; while (queue.length) { const review = queue.shift(); queue.push(...(review.children || [])); if (review.kind || review.path) continue; if (review.children?.length === 1) { const copyFrom = review.children[0]; const fullName = `${review.text}/${copyFrom.text}`; delete review.children; Object.assign(review, copyFrom); review.text = fullName; queue.push(review); } } } return result; } } /** * Generate an anchor url for the given reflection and all of its children. * * @param reflection The reflection an anchor url should be created for. * @param container The nearest reflection having an own document. */ applyAnchorUrl(reflection, container) { if (!(reflection instanceof DeclarationReflection) && !(reflection instanceof SignatureReflection) && !(reflection instanceof TypeParameterReflection)) { return; } // We support linking to reflections for types directly contained within an export // but not any deeper. This is because TypeDoc may or may not render the type details // for a property depending on whether or not it is deemed useful, and defining a link // which might not be used may result in a link being generated which isn't valid. #2808. // This should be kept in sync with the renderingChildIsUseful function. if (reflection.kindOf(ReflectionKind.TypeLiteral) && (!reflection.parent?.kindOf(ReflectionKind.SomeExport) || reflection.parent.type?.type !== "reflection")) { return; } if ((!reflection.url || !DefaultTheme.URL_PREFIX.test(reflection.url)) && !reflection.kindOf(ReflectionKind.TypeLiteral)) { let refl = reflection; const parts = [refl.name]; while (refl.parent && refl.parent !== container && !(reflection.parent instanceof ProjectReflection)) { refl = refl.parent; // Avoid duplicate names for signatures // BREAKING: In 0.28, also add !refl.kindOf(ReflectionKind.TypeLiteral) to this check to improve anchor // generation by omitting useless __type prefixes. if (parts[0] !== refl.name) { parts.unshift(refl.name); } } const anchor = this.getSlugger(reflection).slug(parts.join(".")); reflection.url = container.url + "#" + anchor; reflection.anchor = anchor; reflection.hasOwnDocument = false; } reflection.traverse((child) => { this.applyAnchorUrl(child, container); return true; }); } }; })(); export { DefaultTheme }; function getReflectionClasses(reflection, filters) { const classes = []; // Filter classes should match up with the settings function in // partials/navigation.tsx. for (const key of Object.keys(filters)) { if (key === "inherited") { if (reflection.flags.isInherited) { classes.push("tsd-is-inherited"); } } else if (key === "protected") { if (reflection.flags.isProtected) { classes.push("tsd-is-protected"); } } else if (key === "private") { if (reflection.flags.isPrivate) { classes.push("tsd-is-private"); } } else if (key === "external") { if (reflection.flags.isExternal) { classes.push("tsd-is-external"); } } else if (key.startsWith("@")) { if (key === "@deprecated") { if (reflection.isDeprecated()) { classes.push(toStyleClass(`tsd-is-${key.substring(1)}`)); } } else if (reflection.comment?.hasModifier(key) || reflection.comment?.getTag(key)) { classes.push(toStyleClass(`tsd-is-${key.substring(1)}`)); } } } return classes.join(" "); } function shouldShowCategories(reflection, opts) { if (opts.includeCategories) { return !reflection.comment?.hasModifier("@hideCategories"); } return reflection.comment?.hasModifier("@showCategories") === true; } function shouldShowGroups(reflection, opts) { if (opts.includeGroups) { return !reflection.comment?.hasModifier("@hideGroups"); } return reflection.comment?.hasModifier("@showGroups") === true; }