From 9b99207411bcc444bcaa6d5c79fc2e2344d94de1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 15 Jun 2014 02:28:24 +0200 Subject: [PATCH] Tidied up DefaultTheme, fixed #9 --- bin/typedoc.d.ts | 244 +++++++++++-- bin/typedoc.js | 546 +++++++++++++++++++---------- src/typedoc/output/DefaultTheme.ts | 384 +++++++++++++------- 3 files changed, 853 insertions(+), 321 deletions(-) diff --git a/bin/typedoc.d.ts b/bin/typedoc.d.ts index 1731bc675..e2ab5def2 100644 --- a/bin/typedoc.d.ts +++ b/bin/typedoc.d.ts @@ -1527,38 +1527,237 @@ declare module TypeDoc.Output { } } declare module TypeDoc.Output { + /** + * Base class of all themes. + * + * A theme defines the logical and graphical output of a documentation. Themes are + * directories containing a ```theme.js``` file defining a [[BaseTheme]] subclass and a + * series of subdirectories containing templates and assets. You can select a theme + * through the ```--theme ``` commandline argument. + * + * The theme class controls which files will be created through the [[BaseTheme.getUrls]] + * function. It returns an array of [[UrlMapping]] instances defining the target files, models + * and templates to use. Additionally themes can subscribe to the events emitted by + * [[Renderer]] to control and manipulate the output process. + * + * The default file structure of a theme looks like this: + * + * - ```/assets/```
+ * Contains static assets like stylesheets, images or javascript files used by the theme. + * The [[AssetsPlugin]] will deep copy this directory to the output directory. + * + * - ```/layouts/```
+ * Contains layout templates that the [[LayoutPlugin]] wraps around the output of the + * page template. Currently only one ```default.hbs``` layout is supported. Layout templates + * receive the current [[OutputPageEvent]] instance as their handlebars context. Place the + * ```{{{contents}}}``` variable to render the actual body of the document within this template. + * + * - ```/partials/```
+ * Contains partial templates that can be used by other templates using handlebars partial + * syntax ```{{> partial-name}}```. The [[PartialsPlugin]] loads all files in this directory + * and combines them with the partials of the default theme. + * + * - ```/templates/```
+ * Contains the main templates of the theme. The theme maps models to these templates through + * the [[BaseTheme.getUrls]] function. If the [[Renderer.getTemplate]] function cannot find a + * given template within this directory, it will try to find it in the default theme + * ```/templates/``` directory. Templates receive the current [[OutputPageEvent]] instance as + * their handlebars context. You can access the target model through the ```{{model}}``` variable. + * + * - ```/theme.js```
+ * A javascript file that returns the definition of a [[BaseTheme]] subclass. This file will + * be executed within the context of TypeDoc, one may directly access all classes and functions + * of TypeDoc. If this file is not present, an instance of [[DefaultTheme]] will be used to render + * this theme. + */ class BaseTheme { + /** + * The renderer this theme is attached to. + */ public renderer: Renderer; + /** + * The base path of this theme. + */ public basePath: string; + /** + * Create a new BaseTheme instance. + * + * @param renderer The renderer this theme is attached to. + * @param basePath The base path of this theme. + */ constructor(renderer: Renderer, basePath: string); - public initialize(): void; - public isOutputDirectory(dirname: string): boolean; + /** + * Test whether the given path contains a documentation generated by this theme. + * + * TypeDoc empties the output directory before rendering a project. This function + * is used to ensure that only previously generated documentations are deleted. + * When this function returns FALSE, the documentation will not be created and an + * error message will be displayed. + * + * Every theme must have an own implementation of this function, the default + * implementation always returns FALSE. + * + * @param path The path of the directory that should be tested. + * @returns TRUE if the given path seems to be a previous output directory, + * otherwise FALSE. + * + * @see [[Renderer.prepareOutputDirectory]] + */ + public isOutputDirectory(path: string): boolean; + /** + * Map the models of the given project to the desired output files. + * + * Every theme must have an own implementation of this function, the default + * implementation always returns an empty array. + * + * @param project The project whose urls should be generated. + * @returns A list of [[UrlMapping]] instances defining which models + * should be rendered to which files. + */ public getUrls(project: Models.ProjectReflection): Models.UrlMapping[]; + /** + * Create a navigation structure for the given project. + * + * A navigation is a tree structure consisting of [[NavigationItem]] nodes. This + * function should return the root node of the desired navigation tree. + * + * The [[NavigationPlugin]] will call this hook before a project will be rendered. + * The plugin will update the state of the navigation tree and pass it to the + * templates. + * + * @param project The project whose navigation should be generated. + * @returns The root navigation item. + */ public getNavigation(project: Models.ProjectReflection): Models.NavigationItem; } } declare module TypeDoc.Output { + /** + * Defines a mapping of a [[Models.Kind]] to a template file. + * + * Used by [[DefaultTheme]] to map reflections to output files. + */ interface ITemplateMapping { - kind: any; + /** + * [[DeclarationReflection.kind]] this rule applies to. + */ + kind: TypeScript.PullElementKind[]; + /** + * Can this mapping have children or should all further reflections be rendered + * to the defined output page? + */ isLeaf: boolean; - prefix: string; + /** + * The name of the directory the output files should be written to. + */ + directory: string; + /** + * The name of the template that should be used to render the reflection. + */ template: string; } + /** + * Default theme implementation of TypeDoc. If a theme does not provide a custom + * [[BaseTheme]] implementation, this theme class will be used. + */ class DefaultTheme extends BaseTheme { + /** + * Mappings of reflections kinds to templates used by this theme. + */ static MAPPINGS: ITemplateMapping[]; - public isOutputDirectory(dirname: string): boolean; - private getMapping(reflection); /** - * Build the urls for the current project. + * Create a new DefaultTheme instance. * - * @returns An array of url mappings. + * @param renderer The renderer this theme is attached to. + * @param basePath The base path of this theme. + */ + constructor(renderer: Renderer, basePath: string); + /** + * Test whether the given path contains a documentation generated by this theme. + * + * @param path The path of the directory that should be tested. + * @returns TRUE if the given path seems to be a previous output directory, + * otherwise FALSE. + */ + public isOutputDirectory(path: string): boolean; + /** + * 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 [[UrlMapping]] instances defining which models + * should be rendered to which files. */ public getUrls(project: Models.ProjectReflection): Models.UrlMapping[]; + /** + * Create a navigation structure for the given project. + * + * @param project The project whose navigation should be generated. + * @returns The root navigation item. + */ public getNavigation(project: Models.ProjectReflection): Models.NavigationItem; /** - * Transform a space separated string into a string suitable to be used as a css class. + * Triggered before the renderer starts rendering a project. + * + * @param event An event object describing the current render operation. */ - static classify(str: string): string; + private onRendererBegin(event); + /** + * Return a url for the given reflection. + * + * @param reflection The reflection the url should be generated for. + * @param relative The parent reflection the url generation should stop on. + * @param separator The separator used to generate the url. + * @returns The generated url. + */ + static getUrl(reflection: Models.BaseReflection, relative?: Models.BaseReflection, separator?: string): string; + /** + * Return the template mapping fore the given reflection. + * + * @param reflection The reflection whose mapping should be resolved. + * @returns The found mapping or NULL if no mapping could be found. + */ + static getMapping(reflection: Models.DeclarationReflection): ITemplateMapping; + /** + * Build the navigation nodes for the given reflection. + * + * @param reflection The reflection whose navigation node should be created. + * @param parent The parent navigation node. + */ + static buildNavigation(reflection: Models.DeclarationReflection, parent: Models.NavigationItem): void; + /** + * 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. + */ + static buildUrls(reflection: Models.DeclarationReflection, urls: Models.UrlMapping[]): Models.UrlMapping[]; + /** + * 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. + */ + static applyAnchorUrl(reflection: Models.DeclarationReflection, container: Models.BaseReflection): void; + /** + * Generate the css classes for the given reflection and apply them to the + * [[DeclarationReflection.cssClasses]] property. + * + * @param reflection The reflection whose cssClasses property should be generated. + */ + static applyReflectionClasses(reflection: Models.DeclarationReflection): void; + /** + * Generate the css classes for the given reflection group and apply them to the + * [[ReflectionGroup.cssClasses]] property. + * + * @param group The reflection group whose cssClasses property should be generated. + */ + static applyGroupClasses(group: Models.ReflectionGroup): void; + /** + * Transform a space separated string into a string suitable to be used as a + * css class, e.g. "constructor method" > "Constructor-method". + */ + static toStyleClass(str: string): string; } } declare module TypeDoc.Output { @@ -1747,7 +1946,7 @@ declare module TypeDoc.Output { * An event emitted by the [[Renderer]] class before and after the * markup of a page is rendered. * - * This object will be passed as the rendering context to the handlebars template. + * This object will be passed as the rendering context to handlebars templates. * * @see [[Renderer.EVENT_BEGIN_PAGE]] * @see [[Renderer.EVENT_END_PAGE]] @@ -1786,7 +1985,9 @@ declare module TypeDoc.Output { */ public secondary: Models.NavigationItem[]; /** - * The html content of this page. + * The final html content of this page. + * + * Should be rendered by layout templates and can be modifies by plugins. */ public contents: string; } @@ -1813,14 +2014,13 @@ declare module TypeDoc.Output { } declare module TypeDoc.Output { /** - * A plugin that wraps the generated output with a layout template. + * A plugin that exports an index of the project to a javascript file. * - * Currently only a default layout is supported. The layout must be stored - * as ´layouts/default.hbs´ in the theme directory. + * The resulting javascript file can be used to build a simple search function. */ - class LayoutPlugin extends BasePlugin { + class JavascriptIndexPlugin extends BasePlugin { /** - * Create a new LayoutPlugin instance. + * Create a new JavascriptIndexPlugin instance. * * @param renderer The renderer this plugin should be attached to. */ @@ -1828,9 +2028,9 @@ declare module TypeDoc.Output { /** * Triggered after a document has been rendered, just before it is written to disc. * - * @param page An event object describing the current render operation. + * @param event An event object describing the current render operation. */ - private onRendererEndPage(page); + private onRendererBegin(event); } } declare module TypeDoc.Output { @@ -1840,7 +2040,7 @@ declare module TypeDoc.Output { * Currently only a default layout is supported. The layout must be stored * as ´layouts/default.hbs´ in the theme directory. */ - class LunrPlugin extends BasePlugin { + class LayoutPlugin extends BasePlugin { /** * Create a new LayoutPlugin instance. * @@ -1850,9 +2050,9 @@ declare module TypeDoc.Output { /** * Triggered after a document has been rendered, just before it is written to disc. * - * @param event An event object describing the current render operation. + * @param page An event object describing the current render operation. */ - private onRendererBegin(event); + private onRendererEndPage(page); } } declare module TypeDoc.Output { diff --git a/bin/typedoc.js b/bin/typedoc.js index a75efdb8f..48a2aa5b9 100644 --- a/bin/typedoc.js +++ b/bin/typedoc.js @@ -4134,24 +4134,108 @@ var TypeDoc; var TypeDoc; (function (TypeDoc) { (function (Output) { + /** + * Base class of all themes. + * + * A theme defines the logical and graphical output of a documentation. Themes are + * directories containing a ```theme.js``` file defining a [[BaseTheme]] subclass and a + * series of subdirectories containing templates and assets. You can select a theme + * through the ```--theme ``` commandline argument. + * + * The theme class controls which files will be created through the [[BaseTheme.getUrls]] + * function. It returns an array of [[UrlMapping]] instances defining the target files, models + * and templates to use. Additionally themes can subscribe to the events emitted by + * [[Renderer]] to control and manipulate the output process. + * + * The default file structure of a theme looks like this: + * + * - ```/assets/```
+ * Contains static assets like stylesheets, images or javascript files used by the theme. + * The [[AssetsPlugin]] will deep copy this directory to the output directory. + * + * - ```/layouts/```
+ * Contains layout templates that the [[LayoutPlugin]] wraps around the output of the + * page template. Currently only one ```default.hbs``` layout is supported. Layout templates + * receive the current [[OutputPageEvent]] instance as their handlebars context. Place the + * ```{{{contents}}}``` variable to render the actual body of the document within this template. + * + * - ```/partials/```
+ * Contains partial templates that can be used by other templates using handlebars partial + * syntax ```{{> partial-name}}```. The [[PartialsPlugin]] loads all files in this directory + * and combines them with the partials of the default theme. + * + * - ```/templates/```
+ * Contains the main templates of the theme. The theme maps models to these templates through + * the [[BaseTheme.getUrls]] function. If the [[Renderer.getTemplate]] function cannot find a + * given template within this directory, it will try to find it in the default theme + * ```/templates/``` directory. Templates receive the current [[OutputPageEvent]] instance as + * their handlebars context. You can access the target model through the ```{{model}}``` variable. + * + * - ```/theme.js```
+ * A javascript file that returns the definition of a [[BaseTheme]] subclass. This file will + * be executed within the context of TypeDoc, one may directly access all classes and functions + * of TypeDoc. If this file is not present, an instance of [[DefaultTheme]] will be used to render + * this theme. + */ var BaseTheme = (function () { + /** + * Create a new BaseTheme instance. + * + * @param renderer The renderer this theme is attached to. + * @param basePath The base path of this theme. + */ function BaseTheme(renderer, basePath) { this.renderer = renderer; this.basePath = basePath; - - this.initialize(); } - BaseTheme.prototype.initialize = function () { - }; - - BaseTheme.prototype.isOutputDirectory = function (dirname) { + /** + * Test whether the given path contains a documentation generated by this theme. + * + * TypeDoc empties the output directory before rendering a project. This function + * is used to ensure that only previously generated documentations are deleted. + * When this function returns FALSE, the documentation will not be created and an + * error message will be displayed. + * + * Every theme must have an own implementation of this function, the default + * implementation always returns FALSE. + * + * @param path The path of the directory that should be tested. + * @returns TRUE if the given path seems to be a previous output directory, + * otherwise FALSE. + * + * @see [[Renderer.prepareOutputDirectory]] + */ + BaseTheme.prototype.isOutputDirectory = function (path) { return false; }; + /** + * Map the models of the given project to the desired output files. + * + * Every theme must have an own implementation of this function, the default + * implementation always returns an empty array. + * + * @param project The project whose urls should be generated. + * @returns A list of [[UrlMapping]] instances defining which models + * should be rendered to which files. + */ BaseTheme.prototype.getUrls = function (project) { return []; }; + /** + * Create a navigation structure for the given project. + * + * A navigation is a tree structure consisting of [[NavigationItem]] nodes. This + * function should return the root node of the desired navigation tree. + * + * The [[NavigationPlugin]] will call this hook before a project will be rendered. + * The plugin will update the state of the navigation tree and pass it to the + * templates. + * + * @param project The project whose navigation should be generated. + * @returns The root navigation item. + */ BaseTheme.prototype.getNavigation = function (project) { return null; }; @@ -4164,25 +4248,127 @@ var TypeDoc; var TypeDoc; (function (TypeDoc) { (function (Output) { + + + /** + * Default theme implementation of TypeDoc. If a theme does not provide a custom + * [[BaseTheme]] implementation, this theme class will be used. + */ var DefaultTheme = (function (_super) { __extends(DefaultTheme, _super); - function DefaultTheme() { - _super.apply(this, arguments); + /** + * Create a new DefaultTheme instance. + * + * @param renderer The renderer this theme is attached to. + * @param basePath The base path of this theme. + */ + function DefaultTheme(renderer, basePath) { + _super.call(this, renderer, basePath); + renderer.on(Output.Renderer.EVENT_BEGIN, this.onRendererBegin, this, 1024); } - DefaultTheme.prototype.isOutputDirectory = function (dirname) { - if (!FS.existsSync(Path.join(dirname, 'index.html'))) + /** + * Test whether the given path contains a documentation generated by this theme. + * + * @param path The path of the directory that should be tested. + * @returns TRUE if the given path seems to be a previous output directory, + * otherwise FALSE. + */ + DefaultTheme.prototype.isOutputDirectory = function (path) { + if (!FS.existsSync(Path.join(path, 'index.html'))) return false; - if (!FS.existsSync(Path.join(dirname, 'assets'))) + if (!FS.existsSync(Path.join(path, 'assets'))) return false; - if (!FS.existsSync(Path.join(dirname, 'assets', 'js', 'main.js'))) + if (!FS.existsSync(Path.join(path, 'assets', 'js', 'main.js'))) return false; - if (!FS.existsSync(Path.join(dirname, 'assets', 'images', 'icons.png'))) + if (!FS.existsSync(Path.join(path, 'assets', 'images', 'icons.png'))) return false; return true; }; - DefaultTheme.prototype.getMapping = function (reflection) { + /** + * 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 [[UrlMapping]] instances defining which models + * should be rendered to which files. + */ + DefaultTheme.prototype.getUrls = function (project) { + var urls = []; + + project.url = 'globals.html'; + urls.push(new TypeDoc.Models.UrlMapping('globals.html', project, 'reflection.hbs')); + urls.push(new TypeDoc.Models.UrlMapping('index.html', project, 'index.hbs')); + + project.children.forEach(function (child) { + DefaultTheme.buildUrls(child, urls); + }); + + return urls; + }; + + /** + * Create a navigation structure for the given project. + * + * @param project The project whose navigation should be generated. + * @returns The root navigation item. + */ + DefaultTheme.prototype.getNavigation = function (project) { + var root = new TypeDoc.Models.NavigationItem('Index', 'index.html'); + new TypeDoc.Models.NavigationItem('Globals', 'globals.html', root); + + var modules = project.getReflectionsByKind(TypeDoc.Models.Kind.SomeContainer); + modules.sort(function (a, b) { + return a.getFullName() < b.getFullName() ? -1 : 1; + }); + + modules.forEach(function (container) { + DefaultTheme.buildNavigation(container, root); + }); + + return root; + }; + + /** + * Triggered before the renderer starts rendering a project. + * + * @param event An event object describing the current render operation. + */ + DefaultTheme.prototype.onRendererBegin = function (event) { + event.project.reflections.forEach(function (reflection) { + DefaultTheme.applyReflectionClasses(reflection); + + if (reflection.groups) { + reflection.groups.forEach(DefaultTheme.applyGroupClasses); + } + }); + }; + + /** + * Return a url for the given reflection. + * + * @param reflection The reflection the url should be generated for. + * @param relative The parent reflection the url generation should stop on. + * @param separator The separator used to generate the url. + * @returns The generated url. + */ + DefaultTheme.getUrl = function (reflection, relative, separator) { + if (typeof separator === "undefined") { separator = '.'; } + var url = reflection.getAlias(); + + if (reflection.parent && reflection.parent != relative && !(reflection.parent instanceof TypeDoc.Models.ProjectReflection)) + url = DefaultTheme.getUrl(reflection.parent, relative, separator) + separator + url; + + return url; + }; + + /** + * Return the template mapping fore the given reflection. + * + * @param reflection The reflection whose mapping should be resolved. + * @returns The found mapping or NULL if no mapping could be found. + */ + DefaultTheme.getMapping = function (reflection) { for (var i = 0, c = DefaultTheme.MAPPINGS.length; i < c; i++) { var mapping = DefaultTheme.MAPPINGS[i]; if (reflection.kindOf(mapping.kind)) { @@ -4194,163 +4380,168 @@ var TypeDoc; }; /** - * Build the urls for the current project. + * Build the navigation nodes for the given reflection. * - * @returns An array of url mappings. + * @param reflection The reflection whose navigation node should be created. + * @param parent The parent navigation node. */ - DefaultTheme.prototype.getUrls = function (project) { - var _this = this; - var urls = []; + DefaultTheme.buildNavigation = function (reflection, parent) { + var name, isPrimary; + if (parent.parent) { + name = reflection.name; + isPrimary = false; + } else { + name = reflection.getFullName(); + isPrimary = true; + } - var createUrl = function (reflection, to, separator) { - if (typeof separator === "undefined") { separator = '.'; } - var url = reflection.getAlias(); - if (reflection.parent && reflection.parent != to && !(reflection.parent instanceof TypeDoc.Models.ProjectReflection)) { - url = createUrl(reflection.parent, to, separator) + separator + url; - } - return url; - }; + var item = new TypeDoc.Models.NavigationItem(name, reflection.url, parent); + item.isPrimary = isPrimary; + item.cssClasses = reflection.cssClasses; - var walkLeaf = function (reflection, container) { - reflection.children.forEach(function (child) { - if (child.kindOf(TypeDoc.Models.Kind.Parameter)) { - return; - } + reflection.children.forEach(function (child) { + if (child.kindOf(TypeDoc.Models.Kind.SomeContainer)) + return; + DefaultTheme.buildNavigation(child, item); + }); + }; - child.anchor = (child.isStatic ? 'static-' : '') + createUrl(child, container, '.'); - child.url = container.url + '#' + child.anchor; - walkLeaf(child, container); - }); - }; + /** + * 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. + */ + DefaultTheme.buildUrls = function (reflection, urls) { + var mapping = DefaultTheme.getMapping(reflection); + if (mapping) { + var url = Path.join(mapping.directory, DefaultTheme.getUrl(reflection) + '.html'); + urls.push(new TypeDoc.Models.UrlMapping(url, reflection, mapping.template)); + + reflection.url = url; + reflection.anchor = null; + reflection.hasOwnDocument = true; - var walkReflection = function (reflection, container) { reflection.children.forEach(function (child) { - var mapping = _this.getMapping(child); - if (mapping) { - child.url = Path.join(mapping.prefix, createUrl(child) + '.html'); - child.hasOwnDocument = true; - urls.push(new TypeDoc.Models.UrlMapping(child.url, child, mapping.template)); - - if (mapping.isLeaf) { - walkLeaf(child, child); - } else { - walkReflection(child, child); - } + if (mapping.isLeaf) { + DefaultTheme.applyAnchorUrl(child, child); } else { - child.anchor = (child.isStatic ? 'static-' : '') + createUrl(child, container, '.'); - child.url = container.url + '#' + child.anchor; - walkLeaf(child, container); + DefaultTheme.buildUrls(child, urls); } }); - }; - - project.url = 'globals.html'; - urls.push(new TypeDoc.Models.UrlMapping('globals.html', project, 'reflection.hbs')); - urls.push(new TypeDoc.Models.UrlMapping('index.html', project, 'index.hbs')); - - walkReflection(project, project); - - project.reflections.forEach(function (reflection) { - var classes = []; - var kind = TypeDoc.Models.Kind[reflection.kind]; - classes.push(DefaultTheme.classify('tsd-kind-' + kind)); - - if (reflection.parent && reflection.parent instanceof TypeDoc.Models.DeclarationReflection) { - kind = TypeDoc.Models.Kind[reflection.parent.kind]; - classes.push(DefaultTheme.classify('tsd-parent-kind-' + kind)); - } - - if (reflection.overwrites) - classes.push('tsd-is-overwrite'); - if (reflection.inheritedFrom) - classes.push('tsd-is-inherited'); - if (reflection.isPrivate) - classes.push('tsd-is-private'); - if (reflection.isStatic) - classes.push('tsd-is-static'); - if (!reflection.isExported) - classes.push('tsd-is-not-exported'); - reflection.cssClasses = classes.join(' '); - - if (reflection.groups) { - reflection.groups.forEach(function (group) { - var classes = []; - if (group.allChildrenAreInherited) - classes.push('tsd-is-inherited'); - if (group.allChildrenArePrivate) - classes.push('tsd-is-private'); - if (!group.someChildrenAreExported) - classes.push('tsd-is-not-exported'); - group.cssClasses = classes.join(' '); - }); - } - }); + } else { + DefaultTheme.applyAnchorUrl(reflection, reflection); + } return urls; }; - DefaultTheme.prototype.getNavigation = function (project) { - function walkReflection(reflection, parent) { - var name = parent == root ? reflection.getFullName() : reflection.name; - var item = new TypeDoc.Models.NavigationItem(name, reflection.url, parent); - item.isPrimary = (parent == root); - item.cssClasses = reflection.cssClasses; - - reflection.children.forEach(function (child) { - if (child.kindOf(TypeDoc.Models.Kind.SomeContainer)) - return; - walkReflection(child, item); - }); + /** + * 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. + */ + DefaultTheme.applyAnchorUrl = function (reflection, container) { + var anchor = DefaultTheme.getUrl(reflection, reflection, '.'); + if (reflection.isStatic) { + anchor = 'static-' + anchor; } - var root = new TypeDoc.Models.NavigationItem('Index', 'index.html'); - new TypeDoc.Models.NavigationItem('Globals', 'globals.html', root); + reflection.url = container.url + '#' + anchor; + reflection.anchor = anchor; + reflection.hasOwnDocument = false; - var modules = project.getReflectionsByKind(TypeDoc.Models.Kind.SomeContainer); - modules.sort(function (a, b) { - return a.getFullName() < b.getFullName() ? -1 : 1; + reflection.children.forEach(function (child) { + if (!child.kindOf(TypeDoc.Models.Kind.Parameter)) { + DefaultTheme.applyAnchorUrl(child, container); + } }); + }; - modules.forEach(function (container) { - return walkReflection(container, root); - }); + /** + * Generate the css classes for the given reflection and apply them to the + * [[DeclarationReflection.cssClasses]] property. + * + * @param reflection The reflection whose cssClasses property should be generated. + */ + DefaultTheme.applyReflectionClasses = function (reflection) { + var classes = []; + var kind = TypeDoc.Models.Kind[reflection.kind]; + classes.push(DefaultTheme.toStyleClass('tsd-kind-' + kind)); + + if (reflection.parent && reflection.parent instanceof TypeDoc.Models.DeclarationReflection) { + kind = TypeDoc.Models.Kind[reflection.parent.kind]; + classes.push(DefaultTheme.toStyleClass('tsd-parent-kind-' + kind)); + } + + if (reflection.overwrites) + classes.push('tsd-is-overwrite'); + if (reflection.inheritedFrom) + classes.push('tsd-is-inherited'); + if (reflection.isPrivate) + classes.push('tsd-is-private'); + if (reflection.isStatic) + classes.push('tsd-is-static'); + if (!reflection.isExported) + classes.push('tsd-is-not-exported'); + + reflection.cssClasses = classes.join(' '); + }; - return root; + /** + * Generate the css classes for the given reflection group and apply them to the + * [[ReflectionGroup.cssClasses]] property. + * + * @param group The reflection group whose cssClasses property should be generated. + */ + DefaultTheme.applyGroupClasses = function (group) { + var classes = []; + if (group.allChildrenAreInherited) + classes.push('tsd-is-inherited'); + if (group.allChildrenArePrivate) + classes.push('tsd-is-private'); + if (!group.someChildrenAreExported) + classes.push('tsd-is-not-exported'); + + group.cssClasses = classes.join(' '); }; /** - * Transform a space separated string into a string suitable to be used as a css class. + * Transform a space separated string into a string suitable to be used as a + * css class, e.g. "constructor method" > "Constructor-method". */ - DefaultTheme.classify = function (str) { + DefaultTheme.toStyleClass = function (str) { return str.replace(/(\w)([A-Z])/g, function (m, m1, m2) { return m1 + '-' + m2; }).toLowerCase(); }; DefaultTheme.MAPPINGS = [ { - kind: TypeDoc.Models.Kind.Class, + kind: [TypeDoc.Models.Kind.Class], isLeaf: true, - prefix: 'classes', + directory: 'classes', template: 'reflection.hbs' }, { - kind: TypeDoc.Models.Kind.Interface, + kind: [TypeDoc.Models.Kind.Interface], isLeaf: true, - prefix: 'interfaces', + directory: 'interfaces', template: 'reflection.hbs' }, { - kind: TypeDoc.Models.Kind.Enum, + kind: [TypeDoc.Models.Kind.Enum], isLeaf: true, - prefix: 'enums', + directory: 'enums', template: 'reflection.hbs' }, { kind: [TypeDoc.Models.Kind.Container, TypeDoc.Models.Kind.DynamicModule], isLeaf: false, - prefix: 'modules', + directory: 'modules', template: 'reflection.hbs' }, { kind: [TypeDoc.Models.Kind.Script], isLeaf: false, - prefix: 'scripts', + directory: 'scripts', template: 'reflection.hbs' }]; return DefaultTheme; @@ -4682,7 +4873,7 @@ var TypeDoc; * An event emitted by the [[Renderer]] class before and after the * markup of a page is rendered. * - * This object will be passed as the rendering context to the handlebars template. + * This object will be passed as the rendering context to handlebars templates. * * @see [[Renderer.EVENT_BEGIN_PAGE]] * @see [[Renderer.EVENT_END_PAGE]] @@ -4745,59 +4936,18 @@ var TypeDoc; (function (TypeDoc) { (function (Output) { /** - * A plugin that wraps the generated output with a layout template. + * A plugin that exports an index of the project to a javascript file. * - * Currently only a default layout is supported. The layout must be stored - * as ´layouts/default.hbs´ in the theme directory. + * The resulting javascript file can be used to build a simple search function. */ - var LayoutPlugin = (function (_super) { - __extends(LayoutPlugin, _super); + var JavascriptIndexPlugin = (function (_super) { + __extends(JavascriptIndexPlugin, _super); /** - * Create a new LayoutPlugin instance. + * Create a new JavascriptIndexPlugin instance. * * @param renderer The renderer this plugin should be attached to. */ - function LayoutPlugin(renderer) { - _super.call(this, renderer); - renderer.on(Output.Renderer.EVENT_END_PAGE, this.onRendererEndPage, this); - } - /** - * Triggered after a document has been rendered, just before it is written to disc. - * - * @param page An event object describing the current render operation. - */ - LayoutPlugin.prototype.onRendererEndPage = function (page) { - var layout = this.renderer.getTemplate('layouts/default.hbs'); - page.contents = layout(page); - }; - return LayoutPlugin; - })(Output.BasePlugin); - Output.LayoutPlugin = LayoutPlugin; - - /** - * Register this plugin. - */ - Output.Renderer.PLUGIN_CLASSES.push(LayoutPlugin); - })(TypeDoc.Output || (TypeDoc.Output = {})); - var Output = TypeDoc.Output; -})(TypeDoc || (TypeDoc = {})); -var TypeDoc; -(function (TypeDoc) { - (function (Output) { - /** - * A plugin that wraps the generated output with a layout template. - * - * Currently only a default layout is supported. The layout must be stored - * as ´layouts/default.hbs´ in the theme directory. - */ - var LunrPlugin = (function (_super) { - __extends(LunrPlugin, _super); - /** - * Create a new LayoutPlugin instance. - * - * @param renderer The renderer this plugin should be attached to. - */ - function LunrPlugin(renderer) { + function JavascriptIndexPlugin(renderer) { _super.call(this, renderer); renderer.on(Output.Renderer.EVENT_BEGIN, this.onRendererBegin, this); } @@ -4806,7 +4956,7 @@ var TypeDoc; * * @param event An event object describing the current render operation. */ - LunrPlugin.prototype.onRendererBegin = function (event) { + JavascriptIndexPlugin.prototype.onRendererBegin = function (event) { var rows = []; var kinds = {}; @@ -4845,14 +4995,54 @@ var TypeDoc; TypeScript.IOUtils.writeFileAndFolderStructure(TypeScript.IO, fileName, data, true); }; - return LunrPlugin; + return JavascriptIndexPlugin; })(Output.BasePlugin); - Output.LunrPlugin = LunrPlugin; + Output.JavascriptIndexPlugin = JavascriptIndexPlugin; /** * Register this plugin. */ - Output.Renderer.PLUGIN_CLASSES.push(LunrPlugin); + Output.Renderer.PLUGIN_CLASSES.push(JavascriptIndexPlugin); + })(TypeDoc.Output || (TypeDoc.Output = {})); + var Output = TypeDoc.Output; +})(TypeDoc || (TypeDoc = {})); +var TypeDoc; +(function (TypeDoc) { + (function (Output) { + /** + * A plugin that wraps the generated output with a layout template. + * + * Currently only a default layout is supported. The layout must be stored + * as ´layouts/default.hbs´ in the theme directory. + */ + var LayoutPlugin = (function (_super) { + __extends(LayoutPlugin, _super); + /** + * Create a new LayoutPlugin instance. + * + * @param renderer The renderer this plugin should be attached to. + */ + function LayoutPlugin(renderer) { + _super.call(this, renderer); + renderer.on(Output.Renderer.EVENT_END_PAGE, this.onRendererEndPage, this); + } + /** + * Triggered after a document has been rendered, just before it is written to disc. + * + * @param page An event object describing the current render operation. + */ + LayoutPlugin.prototype.onRendererEndPage = function (page) { + var layout = this.renderer.getTemplate('layouts/default.hbs'); + page.contents = layout(page); + }; + return LayoutPlugin; + })(Output.BasePlugin); + Output.LayoutPlugin = LayoutPlugin; + + /** + * Register this plugin. + */ + Output.Renderer.PLUGIN_CLASSES.push(LayoutPlugin); })(TypeDoc.Output || (TypeDoc.Output = {})); var Output = TypeDoc.Output; })(TypeDoc || (TypeDoc = {})); diff --git a/src/typedoc/output/DefaultTheme.ts b/src/typedoc/output/DefaultTheme.ts index 671e54874..7879df1f0 100644 --- a/src/typedoc/output/DefaultTheme.ts +++ b/src/typedoc/output/DefaultTheme.ts @@ -1,58 +1,191 @@ module TypeDoc.Output { + /** + * Defines a mapping of a [[Models.Kind]] to a template file. + * + * Used by [[DefaultTheme]] to map reflections to output files. + */ + export interface ITemplateMapping + { + /** + * [[DeclarationReflection.kind]] this rule applies to. + */ + kind:TypeScript.PullElementKind[]; - export interface ITemplateMapping { - kind:any; + /** + * Can this mapping have children or should all further reflections be rendered + * to the defined output page? + */ isLeaf:boolean; - prefix:string; + + /** + * The name of the directory the output files should be written to. + */ + directory:string; + + /** + * The name of the template that should be used to render the reflection. + */ template:string; } + /** + * Default theme implementation of TypeDoc. If a theme does not provide a custom + * [[BaseTheme]] implementation, this theme class will be used. + */ export class DefaultTheme extends BaseTheme { + /** + * Mappings of reflections kinds to templates used by this theme. + */ static MAPPINGS:ITemplateMapping[] = [{ - kind: TypeDoc.Models.Kind.Class, - isLeaf: true, - prefix: 'classes', - template: 'reflection.hbs' + kind: [Models.Kind.Class], + isLeaf: true, + directory: 'classes', + template: 'reflection.hbs' },{ - kind: TypeDoc.Models.Kind.Interface, - isLeaf: true, - prefix: 'interfaces', - template: 'reflection.hbs' + kind: [Models.Kind.Interface], + isLeaf: true, + directory: 'interfaces', + template: 'reflection.hbs' },{ - kind: TypeDoc.Models.Kind.Enum, - isLeaf: true, - prefix: 'enums', - template: 'reflection.hbs' + kind: [Models.Kind.Enum], + isLeaf: true, + directory: 'enums', + template: 'reflection.hbs' },{ - kind: [TypeDoc.Models.Kind.Container, TypeDoc.Models.Kind.DynamicModule], - isLeaf: false, - prefix: 'modules', - template: 'reflection.hbs' + kind: [Models.Kind.Container, Models.Kind.DynamicModule], + isLeaf: false, + directory: 'modules', + template: 'reflection.hbs' },{ - kind: [TypeDoc.Models.Kind.Script], - isLeaf: false, - prefix: 'scripts', - template: 'reflection.hbs' + kind: [Models.Kind.Script], + isLeaf: false, + directory: 'scripts', + template: 'reflection.hbs' }]; - isOutputDirectory(dirname:string):boolean { - if (!FS.existsSync(Path.join(dirname, 'index.html'))) return false; - if (!FS.existsSync(Path.join(dirname, 'assets'))) return false; - if (!FS.existsSync(Path.join(dirname, 'assets', 'js', 'main.js'))) return false; - if (!FS.existsSync(Path.join(dirname, 'assets', 'images', 'icons.png'))) return false; + /** + * Create a new DefaultTheme instance. + * + * @param renderer The renderer this theme is attached to. + * @param basePath The base path of this theme. + */ + constructor(renderer:Renderer, basePath:string) { + super(renderer, basePath); + renderer.on(Renderer.EVENT_BEGIN, this.onRendererBegin, this, 1024); + } + + + /** + * Test whether the given path contains a documentation generated by this theme. + * + * @param path The path of the directory that should be tested. + * @returns TRUE if the given path seems to be a previous output directory, + * otherwise FALSE. + */ + isOutputDirectory(path:string):boolean { + if (!FS.existsSync(Path.join(path, 'index.html'))) return false; + if (!FS.existsSync(Path.join(path, 'assets'))) return false; + if (!FS.existsSync(Path.join(path, 'assets', 'js', 'main.js'))) return false; + if (!FS.existsSync(Path.join(path, 'assets', 'images', 'icons.png'))) return false; return true; } - private getMapping(reflection:TypeDoc.Models.DeclarationReflection):ITemplateMapping { + /** + * 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 [[UrlMapping]] instances defining which models + * should be rendered to which files. + */ + getUrls(project:Models.ProjectReflection):Models.UrlMapping[] { + var urls = []; + + project.url = 'globals.html'; + urls.push(new Models.UrlMapping('globals.html', project, 'reflection.hbs')); + urls.push(new Models.UrlMapping('index.html', project, 'index.hbs')); + + project.children.forEach((child) => { + DefaultTheme.buildUrls(child, urls); + }); + + return urls; + } + + + /** + * Create a navigation structure for the given project. + * + * @param project The project whose navigation should be generated. + * @returns The root navigation item. + */ + getNavigation(project:Models.ProjectReflection):Models.NavigationItem { + var root = new Models.NavigationItem('Index', 'index.html'); + new Models.NavigationItem('Globals', 'globals.html', root); + + var modules = project.getReflectionsByKind(Models.Kind.SomeContainer); + modules.sort((a:Models.DeclarationReflection, b:Models.DeclarationReflection) => { + return a.getFullName() < b.getFullName() ? -1 : 1; + }); + + modules.forEach((container) => { + DefaultTheme.buildNavigation(container, root); + }); + + return root; + } + + + /** + * Triggered before the renderer starts rendering a project. + * + * @param event An event object describing the current render operation. + */ + private onRendererBegin(event:OutputEvent) { + event.project.reflections.forEach((reflection) => { + DefaultTheme.applyReflectionClasses(reflection); + + if (reflection.groups) { + reflection.groups.forEach(DefaultTheme.applyGroupClasses); + } + }); + } + + + /** + * Return a url for the given reflection. + * + * @param reflection The reflection the url should be generated for. + * @param relative The parent reflection the url generation should stop on. + * @param separator The separator used to generate the url. + * @returns The generated url. + */ + static getUrl(reflection:Models.BaseReflection, relative?:Models.BaseReflection, separator:string = '.') { + var url = reflection.getAlias(); + + if (reflection.parent && reflection.parent != relative && + !(reflection.parent instanceof Models.ProjectReflection)) + url = DefaultTheme.getUrl(reflection.parent, relative, separator) + separator + url; + + return url; + } + + + /** + * Return the template mapping fore the given reflection. + * + * @param reflection The reflection whose mapping should be resolved. + * @returns The found mapping or NULL if no mapping could be found. + */ + static getMapping(reflection:Models.DeclarationReflection):ITemplateMapping { for (var i = 0, c = DefaultTheme.MAPPINGS.length; i < c; i++) { - var mapping = DefaultTheme.MAPPINGS[i]; + var mapping = DefaultTheme.MAPPINGS[i]; if (reflection.kindOf(mapping.kind)) { return mapping; } @@ -63,127 +196,136 @@ module TypeDoc.Output /** - * Build the urls for the current project. + * Build the navigation nodes for the given reflection. * - * @returns An array of url mappings. + * @param reflection The reflection whose navigation node should be created. + * @param parent The parent navigation node. */ - getUrls(project:TypeDoc.Models.ProjectReflection):TypeDoc.Models.UrlMapping[] - { - var urls = []; + static buildNavigation(reflection:Models.DeclarationReflection, parent:Models.NavigationItem) { + var name, isPrimary; + if (parent.parent) { + name = reflection.name; + isPrimary = false; + } else { + name = reflection.getFullName(); + isPrimary = true; + } - var createUrl = (reflection:TypeDoc.Models.BaseReflection, to?:TypeDoc.Models.BaseReflection, separator:string = '.') => { - var url = reflection.getAlias(); - if (reflection.parent && reflection.parent != to && !(reflection.parent instanceof TypeDoc.Models.ProjectReflection)) { - url = createUrl(reflection.parent, to, separator) + separator + url; - } - return url; - }; + var item = new Models.NavigationItem(name, reflection.url, parent); + item.isPrimary = isPrimary; + item.cssClasses = reflection.cssClasses; - var walkLeaf = (reflection:TypeDoc.Models.BaseReflection, container:TypeDoc.Models.BaseReflection) => { - reflection.children.forEach((child) => { - if (child.kindOf(TypeDoc.Models.Kind.Parameter)) { - return; - } + reflection.children.forEach((child) => { + if (child.kindOf(Models.Kind.SomeContainer)) return; + DefaultTheme.buildNavigation(child, item); + }); + } - child.anchor = (child.isStatic ? 'static-' : '') + createUrl(child, container, '.'); - child.url = container.url + '#' + child.anchor; - walkLeaf(child, container); - }); - }; - var walkReflection = (reflection:TypeDoc.Models.BaseReflection, container:TypeDoc.Models.BaseReflection) => { + /** + * 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. + */ + static buildUrls(reflection:Models.DeclarationReflection, urls:Models.UrlMapping[]):Models.UrlMapping[] { + var mapping = DefaultTheme.getMapping(reflection); + if (mapping) { + var url = Path.join(mapping.directory, DefaultTheme.getUrl(reflection) + '.html'); + urls.push(new Models.UrlMapping(url, reflection, mapping.template)); + + reflection.url = url; + reflection.anchor = null; + reflection.hasOwnDocument = true; + reflection.children.forEach((child) => { - var mapping = this.getMapping(child); - if (mapping) { - child.url = Path.join(mapping.prefix, createUrl(child) + '.html'); - child.hasOwnDocument = true; - urls.push(new TypeDoc.Models.UrlMapping(child.url, child, mapping.template)); - - if (mapping.isLeaf) { - walkLeaf(child, child); - } else { - walkReflection(child, child); - } + if (mapping.isLeaf) { + DefaultTheme.applyAnchorUrl(child, child); } else { - child.anchor = (child.isStatic ? 'static-' : '') + createUrl(child, container, '.'); - child.url = container.url + '#' + child.anchor; - walkLeaf(child, container); + DefaultTheme.buildUrls(child, urls); } }); - }; - - project.url = 'globals.html'; - urls.push(new TypeDoc.Models.UrlMapping('globals.html', project, 'reflection.hbs')); - urls.push(new TypeDoc.Models.UrlMapping('index.html', project, 'index.hbs')); + } else { + DefaultTheme.applyAnchorUrl(reflection, reflection); + } - walkReflection(project, project); + return urls; + } - project.reflections.forEach((reflection:TypeDoc.Models.DeclarationReflection) => { - var classes = []; - var kind = TypeDoc.Models.Kind[reflection.kind]; - classes.push(DefaultTheme.classify('tsd-kind-' + kind)); - if (reflection.parent && reflection.parent instanceof TypeDoc.Models.DeclarationReflection) { - kind = TypeDoc.Models.Kind[(reflection.parent).kind]; - classes.push(DefaultTheme.classify('tsd-parent-kind-'+ kind)); - } + /** + * 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. + */ + static applyAnchorUrl(reflection:Models.DeclarationReflection, container:Models.BaseReflection) { + var anchor = DefaultTheme.getUrl(reflection, reflection, '.'); + if (reflection.isStatic) { + anchor = 'static-' + anchor; + } - if (reflection.overwrites) classes.push('tsd-is-overwrite'); - if (reflection.inheritedFrom) classes.push('tsd-is-inherited'); - if (reflection.isPrivate) classes.push('tsd-is-private'); - if (reflection.isStatic) classes.push('tsd-is-static'); - if (!reflection.isExported) classes.push('tsd-is-not-exported'); - reflection.cssClasses = classes.join(' '); + reflection.url = container.url + '#' + anchor; + reflection.anchor = anchor; + reflection.hasOwnDocument = false; - if (reflection.groups) { - reflection.groups.forEach((group:TypeDoc.Models.ReflectionGroup) => { - var classes = []; - if (group.allChildrenAreInherited) classes.push('tsd-is-inherited'); - if (group.allChildrenArePrivate) classes.push('tsd-is-private'); - if (!group.someChildrenAreExported) classes.push('tsd-is-not-exported'); - group.cssClasses = classes.join(' '); - }); + reflection.children.forEach((child) => { + if (!child.kindOf(Models.Kind.Parameter)) { + DefaultTheme.applyAnchorUrl(child, container); } }); - - return urls; } - - getNavigation(project:TypeDoc.Models.ProjectReflection):TypeDoc.Models.NavigationItem - { - function walkReflection(reflection:TypeDoc.Models.DeclarationReflection, parent:TypeDoc.Models.NavigationItem) { - var name = parent == root ? reflection.getFullName() : reflection.name; - var item = new TypeDoc.Models.NavigationItem(name, reflection.url, parent); - item.isPrimary = (parent == root); - item.cssClasses = reflection.cssClasses; - - reflection.children.forEach((child) => { - if (child.kindOf(TypeDoc.Models.Kind.SomeContainer)) return; - walkReflection(child, item); - }); + + /** + * Generate the css classes for the given reflection and apply them to the + * [[DeclarationReflection.cssClasses]] property. + * + * @param reflection The reflection whose cssClasses property should be generated. + */ + static applyReflectionClasses(reflection:Models.DeclarationReflection) { + var classes = []; + var kind = Models.Kind[reflection.kind]; + classes.push(DefaultTheme.toStyleClass('tsd-kind-' + kind)); + + if (reflection.parent && reflection.parent instanceof Models.DeclarationReflection) { + kind = Models.Kind[(reflection.parent).kind]; + classes.push(DefaultTheme.toStyleClass('tsd-parent-kind-'+ kind)); } + if (reflection.overwrites) classes.push('tsd-is-overwrite'); + if (reflection.inheritedFrom) classes.push('tsd-is-inherited'); + if (reflection.isPrivate) classes.push('tsd-is-private'); + if (reflection.isStatic) classes.push('tsd-is-static'); + if (!reflection.isExported) classes.push('tsd-is-not-exported'); - var root = new TypeDoc.Models.NavigationItem('Index', 'index.html'); - new TypeDoc.Models.NavigationItem('Globals', 'globals.html', root); + reflection.cssClasses = classes.join(' '); + } - var modules = project.getReflectionsByKind(TypeDoc.Models.Kind.SomeContainer); - modules.sort((a:TypeDoc.Models.DeclarationReflection, b:TypeDoc.Models.DeclarationReflection) => { - return a.getFullName() < b.getFullName() ? -1 : 1; - }); - modules.forEach((container) => walkReflection(container, root)); + /** + * Generate the css classes for the given reflection group and apply them to the + * [[ReflectionGroup.cssClasses]] property. + * + * @param group The reflection group whose cssClasses property should be generated. + */ + static applyGroupClasses(group:Models.ReflectionGroup) { + var classes = []; + if (group.allChildrenAreInherited) classes.push('tsd-is-inherited'); + if (group.allChildrenArePrivate) classes.push('tsd-is-private'); + if (!group.someChildrenAreExported) classes.push('tsd-is-not-exported'); - return root; + group.cssClasses = classes.join(' '); } /** - * Transform a space separated string into a string suitable to be used as a css class. + * Transform a space separated string into a string suitable to be used as a + * css class, e.g. "constructor method" > "Constructor-method". */ - static classify(str:string) { + static toStyleClass(str:string) { return str.replace(/(\w)([A-Z])/g, (m, m1, m2) => m1 + '-' + m2).toLowerCase(); } } -} +} \ No newline at end of file