diff --git a/dev-packages/helpers/src/constants.ts b/dev-packages/helpers/src/constants.ts index aeb1e6938..606f106b3 100644 --- a/dev-packages/helpers/src/constants.ts +++ b/dev-packages/helpers/src/constants.ts @@ -1,47 +1,52 @@ import { DocsConfig } from './models'; -export const DECLARATIONS_PATH = `${process.cwd()}/src/options/declarations.ts`; - export const PRESETS_PATH = `${process.cwd()}/src/options/presets.ts`; export const DOCS_CONFIG: Record = { ['typedoc-plugin-markdown']: { + declarationsPath: `${process.cwd()}/src/plugin/options/declarations.ts`, optionsPath: 'options', docsPath: '', declarations: true, presets: false, }, ['typedoc-plugin-frontmatter']: { + declarationsPath: `${process.cwd()}/src/options/declarations.ts`, optionsPath: 'utilities/frontmatter', docsPath: '/utilities/frontmatter', declarations: true, presets: false, }, ['typedoc-plugin-remark']: { + declarationsPath: `${process.cwd()}/src/options/declarations.ts`, optionsPath: 'utilities/remark', docsPath: '/utilities/remark', declarations: true, presets: false, }, ['typedoc-github-wiki-theme']: { + declarationsPath: `${process.cwd()}/src/options/declarations.ts`, optionsPath: 'themes/github-wiki', docsPath: '/themes/github-wiki', declarations: true, presets: true, }, ['typedoc-gitlab-wiki-theme']: { + declarationsPath: `${process.cwd()}/src/options/declarations.ts`, optionsPath: 'themes/gitlab-wiki', docsPath: '/themes/gitlab-wiki', declarations: true, presets: true, }, ['typedoc-vitepress-theme']: { + declarationsPath: `${process.cwd()}/src/options/declarations.ts`, optionsPath: 'themes/vitepress', docsPath: '/themes/vitepress', declarations: true, presets: false, }, ['docusaurus-plugin-typedoc']: { + declarationsPath: `${process.cwd()}/src/options/declarations.ts`, optionsPath: '/integrations/docusaurus/options', docsPath: '/integrations/docusaurus', declarations: false, diff --git a/dev-packages/helpers/src/models.ts b/dev-packages/helpers/src/models.ts index 7866a5a80..e6717d93e 100644 --- a/dev-packages/helpers/src/models.ts +++ b/dev-packages/helpers/src/models.ts @@ -1,4 +1,5 @@ export interface DocsConfig { + declarationsPath: string; optionsPath: string; docsPath: string; declarations: boolean; diff --git a/dev-packages/prebuild-options/cli.ts b/dev-packages/prebuild-options/cli.ts index 8cba95140..68cccceb7 100755 --- a/dev-packages/prebuild-options/cli.ts +++ b/dev-packages/prebuild-options/cli.ts @@ -10,7 +10,7 @@ main(); async function main() { const docsConfig: DocsConfig = DOCS_CONFIG[getPackageName()]; if (docsConfig.declarations) { - await generateModels(); + await generateModels(docsConfig.declarationsPath); } await generateDocs(docsConfig); consola.success(`[${getPackageName()}] Prebuild options complete`); diff --git a/dev-packages/prebuild-options/tasks/generate-docs.ts b/dev-packages/prebuild-options/tasks/generate-docs.ts index 0043b7fd6..87b6a365f 100644 --- a/dev-packages/prebuild-options/tasks/generate-docs.ts +++ b/dev-packages/prebuild-options/tasks/generate-docs.ts @@ -1,4 +1,3 @@ -import { DECLARATIONS_PATH } from '@dev-packages/helpers'; import { DocsConfig } from '@dev-packages/helpers/src/models'; import * as fs from 'fs'; import * as path from 'path'; @@ -21,8 +20,8 @@ export async function generateDocs(docsConfig: DocsConfig) { // DECLARATIONS if (docsConfig.declarations) { - const declarationsConfig: any = await import(DECLARATIONS_PATH); - const configFileTs = project.getSourceFile(DECLARATIONS_PATH); + const declarationsConfig: any = await import(docsConfig.declarationsPath); + const configFileTs = project.getSourceFile(docsConfig.declarationsPath); const optionsVariableStatements = configFileTs?.getVariableStatements() as VariableStatement[]; diff --git a/dev-packages/prebuild-options/tasks/generate-models.ts b/dev-packages/prebuild-options/tasks/generate-models.ts index d7544729b..97df0f4e8 100644 --- a/dev-packages/prebuild-options/tasks/generate-models.ts +++ b/dev-packages/prebuild-options/tasks/generate-models.ts @@ -1,4 +1,3 @@ -import { DECLARATIONS_PATH } from '@dev-packages/helpers'; import * as fs from 'fs'; import * as path from 'path'; import * as prettier from 'prettier'; @@ -8,8 +7,8 @@ import { DeclarationOption, ParameterType } from 'typedoc'; * Creates models for plugin options */ -export async function generateModels() { - const optionsConfig = await import(DECLARATIONS_PATH); +export async function generateModels(declarationsPath: string) { + const optionsConfig = await import(declarationsPath); const mixedTypes = (Object.entries(optionsConfig) as any).filter( ([name, option]) => @@ -59,9 +58,7 @@ export async function generateModels() { `; const optionsModelFile = path.join( - process.cwd(), - 'src', - 'options', + path.dirname(declarationsPath), 'models.ts', ); diff --git a/docs/pages/quick-start.mdx b/docs/pages/quick-start.mdx index 62f03ce26..f4f8a8e25 100644 --- a/docs/pages/quick-start.mdx +++ b/docs/pages/quick-start.mdx @@ -31,7 +31,6 @@ The majority of [TypeDoc's options](https://typedoc.org/options/) are supported, Some typical (and frequently asked about) TypeDoc options that might be useful are: -- Using [githubPages](https://typedoc.org/options/output/#githubpages) to remove the generated `.nojekyll` file. - Using [hideGenerator](https://typedoc.org/options/output/#hidegenerator) to prevent outputting links in the footer. - Using [disableSources](https://typedoc.org/options/output/#disablesources) to prevent outputting source references. - Setting [readme](https://typedoc.org/options/input/#readme) to `none` to exclude the project readme from the docs. diff --git a/examples/docusaurus3/docusaurus.config.js b/examples/docusaurus3/docusaurus.config.js index 5aedbed74..65a5c4bf6 100644 --- a/examples/docusaurus3/docusaurus.config.js +++ b/examples/docusaurus3/docusaurus.config.js @@ -52,6 +52,7 @@ const config = { readme: 'none', sidebar: { pretty: true }, outputFileStrategy: 'members', + cleanOutputDir: true, }, ], [ @@ -61,6 +62,7 @@ const config = { out: './docs/api-2', ...require(path.join(__dirname, '../../stubs/typedoc.cjs')), entryPoints: '../../stubs/src/groups/**/*.ts', + cleanOutputDir: true, }, ], [ @@ -73,6 +75,7 @@ const config = { readme: 'none', outputFileStrategy: 'modules', entryModule: 'index', + cleanOutputDir: true, }, ], [ diff --git a/packages/docusaurus-plugin-typedoc/package.json b/packages/docusaurus-plugin-typedoc/package.json index e34e9d1fb..302f738bf 100644 --- a/packages/docusaurus-plugin-typedoc/package.json +++ b/packages/docusaurus-plugin-typedoc/package.json @@ -23,7 +23,8 @@ "prepublishOnly": "npm run lint && npm run build", "build": "rm -rf ./dist && tsc", "build-and-test": "npm run build && npm run test", - "test": "jest" + "test": "jest", + "test:update": "npm test -- -u" }, "author": "Thomas Grey", "license": "MIT", diff --git a/packages/docusaurus-plugin-typedoc/src/options.ts b/packages/docusaurus-plugin-typedoc/src/options.ts index d977e582f..51e1fcdb6 100644 --- a/packages/docusaurus-plugin-typedoc/src/options.ts +++ b/packages/docusaurus-plugin-typedoc/src/options.ts @@ -7,7 +7,6 @@ const DEFAULT_PLUGIN_OPTIONS = { hidePageHeader: true, entryFileName: 'index.md', theme: 'docusaurus', - githubPages: false, sidebar: { autoConfiguration: true, pretty: false, diff --git a/packages/docusaurus-plugin-typedoc/src/plugin.ts b/packages/docusaurus-plugin-typedoc/src/plugin.ts index 2ec806438..413490c4a 100644 --- a/packages/docusaurus-plugin-typedoc/src/plugin.ts +++ b/packages/docusaurus-plugin-typedoc/src/plugin.ts @@ -56,10 +56,6 @@ async function generateTypedoc(context: any, opts: Partial) { const outputDir = app.options.getValue('out'); - if (options.cleanOutputDir) { - removeDir(outputDir); - } - if (context.siteConfig?.markdown?.format !== 'mdx') { app.renderer.on(PageEvent.END, (event: PageEvent) => { event.contents = event.contents?.replace(/\\ 0) { - files.forEach(function (filename) { - if (fs.statSync(path + '/' + filename).isDirectory()) { - removeDir(path + '/' + filename); - } else { - fs.unlinkSync(path + '/' + filename); - } - }); - fs.rmdirSync(path); - } else { - fs.rmdirSync(path); - } - } -} diff --git a/packages/docusaurus-plugin-typedoc/test/specs/__snapshots__/docusaurus.spec.ts.snap b/packages/docusaurus-plugin-typedoc/test/specs/__snapshots__/docusaurus.spec.ts.snap index da9470ae8..8a448abc5 100644 --- a/packages/docusaurus-plugin-typedoc/test/specs/__snapshots__/docusaurus.spec.ts.snap +++ b/packages/docusaurus-plugin-typedoc/test/specs/__snapshots__/docusaurus.spec.ts.snap @@ -14,10 +14,6 @@ exports[`Docusaurus: Defaults should render 1`] = ` - [module-1](module-1/index.md) - [module-2](module-2/index.md) - -*** - -Generated using [TypeDoc](https://typedoc.org) and [typedoc-plugin-markdown](https://typedoc-plugin-markdown.org). " `; @@ -35,9 +31,5 @@ exports[`Docusaurus: Global members should render 2 1`] = ` - [module-1](module-1/index.md) - [module-2](module-2/index.md) - -*** - -Generated using [TypeDoc](https://typedoc.org) and [typedoc-plugin-markdown](https://typedoc-plugin-markdown.org). " `; diff --git a/packages/docusaurus-plugin-typedoc/test/specs/docusaurus.spec.ts b/packages/docusaurus-plugin-typedoc/test/specs/docusaurus.spec.ts index 7745761df..304d6e1e1 100644 --- a/packages/docusaurus-plugin-typedoc/test/specs/docusaurus.spec.ts +++ b/packages/docusaurus-plugin-typedoc/test/specs/docusaurus.spec.ts @@ -13,6 +13,7 @@ async function bootstrap( entryPoints, tsconfig: ['./test/stubs/tsconfig.json'], readme: 'none', + hideGenerator: true, }; const plugin = typedocPlugin( diff --git a/packages/typedoc-github-wiki-theme/src/options/presets.ts b/packages/typedoc-github-wiki-theme/src/options/presets.ts index 657439691..60d4db98e 100644 --- a/packages/typedoc-github-wiki-theme/src/options/presets.ts +++ b/packages/typedoc-github-wiki-theme/src/options/presets.ts @@ -1,5 +1,4 @@ export default { entryFileName: 'Home.md', hidePageHeader: true, - githubPages: false, }; diff --git a/packages/typedoc-github-wiki-theme/src/theme.ts b/packages/typedoc-github-wiki-theme/src/theme.ts index 9f0b8c633..827c1688e 100644 --- a/packages/typedoc-github-wiki-theme/src/theme.ts +++ b/packages/typedoc-github-wiki-theme/src/theme.ts @@ -9,21 +9,20 @@ import { MarkdownTheme, MarkdownThemeRenderContext, } from 'typedoc-plugin-markdown'; -import { UrlMapping } from 'typedoc-plugin-markdown/dist/plugin/url-mapping'; export class GithubWikiTheme extends MarkdownTheme { override getRenderContext(pageEvent: MarkdownPageEvent) { return new ThemeRenderContext(this, pageEvent, this.application.options); } - getUrls(project: ProjectReflection): UrlMapping[] { + getUrls(project: ProjectReflection) { return super.getUrls(project).map((urlMapping) => { if (urlMapping.model.kindOf(ReflectionKind.Project)) { return urlMapping; } return { ...urlMapping, - url: this.getUrl(urlMapping.model), + url: this.getUrl(urlMapping.model as DeclarationReflection), }; }); } diff --git a/packages/typedoc-gitlab-wiki-theme/src/options/presets.ts b/packages/typedoc-gitlab-wiki-theme/src/options/presets.ts index e64ab50b3..c692f3ee4 100644 --- a/packages/typedoc-gitlab-wiki-theme/src/options/presets.ts +++ b/packages/typedoc-gitlab-wiki-theme/src/options/presets.ts @@ -1,5 +1,4 @@ export default { entryFileName: 'home.md', hidePageHeader: true, - githubPages: false, }; diff --git a/packages/typedoc-plugin-frontmatter/test/typedoc-base.json b/packages/typedoc-plugin-frontmatter/test/typedoc-base.json index 8925016a4..51f389705 100644 --- a/packages/typedoc-plugin-frontmatter/test/typedoc-base.json +++ b/packages/typedoc-plugin-frontmatter/test/typedoc-base.json @@ -6,6 +6,5 @@ "plugin": ["typedoc-plugin-markdown", "typedoc-plugin-frontmatter"], "hidePageHeader": true, "hideBreadcrumbs": true, - "hideGenerator": true, - "githubPages": false + "hideGenerator": true } diff --git a/packages/typedoc-plugin-markdown/CONTRIBUTING.md b/packages/typedoc-plugin-markdown/CONTRIBUTING.md index 6de8ea00c..1aab95200 100644 --- a/packages/typedoc-plugin-markdown/CONTRIBUTING.md +++ b/packages/typedoc-plugin-markdown/CONTRIBUTING.md @@ -1,3 +1,82 @@ # Contributing guidelines -Contributions and suggestions are welcome. Contributing guidelines coming soon. +> Please note this guide is work in progress! + +Thank you showing interest in contributing to this plugin - contributions and suggestions are very welcome. + +## Overview + +This is a simple monorepo managed by [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces) with `typedoc-plugin-markdown` the "core" package. This guide is for developing inside the `typedoc-plugin-markdown` core package. + +This guide does not cover TypeDoc specifically so it might be useful to read the [TypeDoc development guide](https://typedoc.org/guides/development/) for an overview on TypeDoc's architecture. + +## Getting started + +1. Clone the repository:
`git clone git@github.com:tgreyuk/typedoc-plugin-markdown.git` + +2. Checkout the `next` branch:
`git checkout next` + +3. Install dependecnices from the repository root.
`npm install` + +4. cd into the package:
`cd packages/typedoc-plugin-markdown` + +5. Build and run tests:
`npm run build-and-test` + +If the project builds and the tests run successfully you are ready to get going. + +## High-level architecture + +At a high level the codebase consists of an exposed TypeDoc plugin along side an assoicated theme. + +At a highlevel: + +- `index.ts` contains the public api and exports the required `load` funtion. +- The `plugin` folder contains functionality to setup the plugin and configure the renderer. +- The `support` folder contains agnostic support helpers used in the plugin. +- The `theme` folder contains the logic that sets up the custom Markdown theme. + +## Testing + +Test use the [Jest](https://jestjs.io/) with [ts-jest](https://kulshekhar.github.io/ts-jest/) as the test framework. To run all tests use the following command: + +```shell +npm run test +``` + +Jest snapshots are used quite heavily to compare the output generated from templates. To update the snapshots run with the `update` flag. + +## Linting + +To run linting on the project use: + +```shell +npm run lint +``` + +Both the code and some example generated markdown are linted using [eslint](https://eslint.org/) and [markdowmlint](https://github.com/DavidAnson/markdownlint) respectively. + +## Submitting a PR + +Please create a PR with a clear description of what the change is. + +If the PR requires a new package release please also create a [changeset](https://github.com/changesets/changesets) which is the tool used for versioning and releasing packages. + +Run the changeset command in the project root: + +```shell +npx changeset +``` + +A one line changeset description will be enough but please ensure the correct semantic version is selected `patch` for a bug fix or `minor` for a new feature. + +The resulting changeset file will something like this: + +```markdown +--- +'typedoc-plugin-markdown': patch +--- + +- A simple description for a patch fix. This message will also appear in the changelog. +``` + +Please also submit this file with the PR. diff --git a/packages/typedoc-plugin-markdown/package.json b/packages/typedoc-plugin-markdown/package.json index 79808bfaa..ef244e7a9 100644 --- a/packages/typedoc-plugin-markdown/package.json +++ b/packages/typedoc-plugin-markdown/package.json @@ -11,13 +11,14 @@ "prepublishOnly": "npm run lint && npm run build", "prebuild": "rm -rf dist && prebuild-options && ts-node scripts/prebuild-resources", "build": "tsc", - "pretest": "ts-node ./test/__scripts__/prepare.ts", - "test": "npm-run-all test:*", - "test:lint-md": "node test/__scripts__/lint.md.mjs", - "test:lint-mdx": "node test/__scripts__/lint.mdx.mjs", - "test:jest": "jest", + "pretest": "ts-node ./test/__scripts__/prepare", + "test-md": "node test/__scripts__/lint.md.mjs", + "test-mdx": "node test/__scripts__/lint.mdx.mjs", + "test": "npm run test-md && npm run test-mdx && jest", + "test:update": "npm run build && npm test -- -u", "build-and-run": "npm run build && npm run pretest", - "build-and-test": "npm run build && npm run test" + "build-and-test": "npm run build && npm run test", + "docs": "npm run build && typedoc --out ./docs/api" }, "author": "Thomas Grey", "license": "MIT", diff --git a/packages/typedoc-plugin-markdown/scripts/prebuild-resources.ts b/packages/typedoc-plugin-markdown/scripts/prebuild-resources.ts index 56eb16d73..59f58cd57 100644 --- a/packages/typedoc-plugin-markdown/scripts/prebuild-resources.ts +++ b/packages/typedoc-plugin-markdown/scripts/prebuild-resources.ts @@ -57,6 +57,8 @@ async function main() { : `${capitalize(kind.key)}s`; kindsString.push(` + // THIS FILE IS AUTOGENERATED - DO NOT EDIT DIRECTLY + export const KIND_DEFAULTS: Record = { ${kinds .map((kind) => { @@ -117,7 +119,7 @@ async function main() { '..', 'src', 'theme', - 'render-context.ts', + 'markdown-theme-render-context.ts', ); const importsOut: string[] = []; diff --git a/packages/typedoc-plugin-markdown/src/index.ts b/packages/typedoc-plugin-markdown/src/index.ts index 16ef31610..04e6cead7 100644 --- a/packages/typedoc-plugin-markdown/src/index.ts +++ b/packages/typedoc-plugin-markdown/src/index.ts @@ -1,9 +1,12 @@ /** * Exposes the public API of the plugin */ -export * from './options/maps'; -export { PluginOptions } from './options/models'; -export { load } from './plugin/bootstrap'; -export { MarkdownPageEvent, MarkdownRendererEvent } from './plugin/events'; -export { MarkdownTheme, MarkdownThemeRenderContext } from './theme'; -export { NavigationItem } from './theme/models'; +export * from './plugin'; +export * from './plugin/options/maps'; +export { PluginOptions } from './plugin/options/models'; +export * from './plugin/utils'; +export { + MarkdownTheme, + MarkdownThemeRenderContext, + NavigationItem, +} from './theme'; diff --git a/packages/typedoc-plugin-markdown/src/plugin/bootstrap.ts b/packages/typedoc-plugin-markdown/src/plugin/bootstrap.ts deleted file mode 100644 index 92ac29ca0..000000000 --- a/packages/typedoc-plugin-markdown/src/plugin/bootstrap.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Exposes the {@link load} function that is called by TypeDoc to bootstrap the plugin. - * @module - */ - -import { - Application, - Context, - Converter, - DeclarationOption, - Renderer, - Theme, -} from 'typedoc'; -import * as options from '../options/declarations'; -import { MarkdownTheme } from '../theme'; -import { generateMarkdown, renderMarkdown } from './renderer'; - -/** - * - * The main plugin entrypoint containing all bootstrapping logic. - */ - -export function load(app: Application) { - /** - * add options - */ - Object.entries(options).forEach(([name, option]) => { - app.options.addDeclaration({ - name, - ...option, - } as DeclarationOption); - }); - - /** - * Apply custom renderer methods (there should probably be a better solution to this) - * See {@link plugin/renderer}. - */ - Object.defineProperty(app, 'generateDocs', { value: generateMarkdown }); - - Object.defineProperty(app.renderer, 'render', { - value: renderMarkdown, - }); - - Object.defineProperty(app.renderer, 'themes', { - value: new Map Theme>([ - ['default', MarkdownTheme], - ]), - }); - - app.converter.on(Converter.EVENT_RESOLVE_END, (context: Context) => { - if (app.options.packageDir) { - (app.renderer as any).packages = { - ...((app.renderer as any).packages || {}), - [context.project.name]: { dir: app.options.packageDir }, - }; - } - }); -} diff --git a/packages/typedoc-plugin-markdown/src/plugin/events.ts b/packages/typedoc-plugin-markdown/src/plugin/events.ts index 7a4589518..6f0792f6a 100644 --- a/packages/typedoc-plugin-markdown/src/plugin/events.ts +++ b/packages/typedoc-plugin-markdown/src/plugin/events.ts @@ -1,7 +1,6 @@ import * as path from 'path'; import { Event, ProjectReflection, Reflection } from 'typedoc'; -import { NavigationItem } from '../theme/models'; -import { RenderTemplate, UrlMapping } from './url-mapping'; +import { NavigationItem, RenderTemplate, UrlMapping } from '../theme/models'; /** * Extends the RendererEvent from TypeDoc to expose navigation property. diff --git a/packages/typedoc-plugin-markdown/src/plugin/index.ts b/packages/typedoc-plugin-markdown/src/plugin/index.ts new file mode 100644 index 000000000..80ac61543 --- /dev/null +++ b/packages/typedoc-plugin-markdown/src/plugin/index.ts @@ -0,0 +1,92 @@ +import { + Application, + Context, + Converter, + DeclarationOption, + Renderer, + Theme, +} from 'typedoc'; +import { MarkdownTheme } from '../theme'; +import * as declarations from './options/declarations'; +import { generateDocs, render } from './renderer'; + +export * from './events'; + +/** + * + * The function that is called by TypeDoc to bootstrap the plugin. {@see https://typedoc.org/guides/development/#plugins} + * + * Here we expose additional TypeDoc options and make some adjustments. + * + */ +export function load(app: Application) { + /** + * ==================== + * 1. Bootstrap Options + * ==================== + */ + + /** + * Interate over declaration definitions and to the container. + */ + Object.entries(declarations).forEach(([name, declaration]) => { + app.options.addDeclaration({ + name, + ...declaration, + } as DeclarationOption); + }); + + /** + * ================================================= + * 2. Intercept and modify some TypeDoc core methods + * ================================================= + * + * Currently the TypeDoc {@link Renderer} class is quite coupled to the HTML theme so we override a couple of core methods. + * + * @todo Ideally there would be proper decoupling in the TypeDoc core between the {@link Application} and {@link Renderer} which requires further investigation. + * + */ + + /** + * Replace the default HTML theme the with the {@link MarkdownTheme} + */ + Object.defineProperty(app.renderer, 'themes', { + value: new Map Theme>([ + ['default', MarkdownTheme], + ]), + }); + + /** + * Replace TypeDoc's {@link app.generateDocs} method with our own {@link generateDocs} method. + */ + Object.defineProperty(app, 'generateDocs', { value: generateDocs }); + + /** + * Replace TypeDoc's {@link app.renderer.render} method with our own {@link render} method. + */ + Object.defineProperty(app.renderer, 'render', { + value: render, + }); + + /** + * =============================================================== + * 3. Apply any other behaviour + * ================================================================ + */ + + /** + * Currently options set for packages are only stored on the converter and are destroyed before being passed to the {@link Renderer}. + * + * By intercepting the package options set in the converter and storing them on the renderer we can use them later in the theme. + * + * @todo Ideally this functionality would be available in TypeDoc core - to investigate. + */ + app.converter.on(Converter.EVENT_RESOLVE_END, (context: Context) => { + if (app.options.packageDir) { + (app.renderer as any).packageOptions = { + ...((app.renderer as any).packageOptions || {}), + [context.project.name]: app.options, + }; + } + }); +} diff --git a/packages/typedoc-plugin-markdown/src/options/declarations.ts b/packages/typedoc-plugin-markdown/src/plugin/options/declarations.ts similarity index 100% rename from packages/typedoc-plugin-markdown/src/options/declarations.ts rename to packages/typedoc-plugin-markdown/src/plugin/options/declarations.ts diff --git a/packages/typedoc-plugin-markdown/src/options/maps.ts b/packages/typedoc-plugin-markdown/src/plugin/options/maps.ts similarity index 96% rename from packages/typedoc-plugin-markdown/src/options/maps.ts rename to packages/typedoc-plugin-markdown/src/plugin/options/maps.ts index 2d19a73c9..96f1ad7c8 100644 --- a/packages/typedoc-plugin-markdown/src/options/maps.ts +++ b/packages/typedoc-plugin-markdown/src/plugin/options/maps.ts @@ -1,4 +1,4 @@ -import { KIND_DEFAULTS } from '../theme/constants/kinds'; +import { KIND_DEFAULTS } from '../../theme/constants/kinds'; /** * Defines outputFileStrategy model for the `outputFileStrategy` option. diff --git a/packages/typedoc-plugin-markdown/src/options/models.ts b/packages/typedoc-plugin-markdown/src/plugin/options/models.ts similarity index 100% rename from packages/typedoc-plugin-markdown/src/options/models.ts rename to packages/typedoc-plugin-markdown/src/plugin/options/models.ts diff --git a/packages/typedoc-plugin-markdown/src/plugin/renderer.ts b/packages/typedoc-plugin-markdown/src/plugin/renderer.ts index ca65c603d..d6bc3b5cf 100644 --- a/packages/typedoc-plugin-markdown/src/plugin/renderer.ts +++ b/packages/typedoc-plugin-markdown/src/plugin/renderer.ts @@ -1,51 +1,52 @@ -/** - * Contains functionality to decouple HTML logic from the TypeDoc [Renderer](https://typedoc.org/api/classes/Renderer.html). - * @module - */ - import * as fs from 'fs'; -import * as path from 'path'; import { + Application, DeclarationReflection, - Options, ProjectReflection, Reflection, + Renderer, } from 'typedoc'; -import { formatContents } from '../support/utils'; import { MarkdownPageEvent, MarkdownRendererEvent } from './events'; +import { nicePath, writeFileSync } from './utils'; /** - * Replacement of TypeDoc's [Application.generateDocs](https://typedoc.org/api/classes/Application.html#generateDocs) to decouple HTML logic. + * Contains functionality to decouple HTML logic from the TypeDoc's {@link Renderer}. + * + * @todo Investigate ways to properly decouple HTML logic from the TypeDoc renderer. * + * @module */ -export async function generateMarkdown( - project: ProjectReflection, - out: string, -) { - const start = Date.now(); +/** + * Replacement of TypeDoc's {@link Application.generateDocs} method to decouple HTML logic. + * + */ +export async function generateDocs(project: ProjectReflection, out: string) { + const start = Date.now(); await this.renderer.render(project, out); - if (this.logger.hasErrors()) { this.logger.error( 'Documentation could not be generated due to the errors above.', ); } else { this.logger.info(`Documentation generated at ${nicePath(out)}`); - this.logger.verbose(`Markdown rendering took ${Date.now() - start}ms`); } } /** - * Replacement of TypeDoc's [Renderer.render](https://typedoc.org/api/classes/Renderer.html#render) to decouple HTML logic. - * - Removes uneccessary async calls to load highlighters - * - Removes hooks logic + * Replacement of TypeDoc's {@link Renderer.render} method to decouple HTML logic. + * + * This is essentially a copy of the base method with a few tweaks. + * + * - Removes uneccessary async calls to load highlighters only required for html theme. + * - Removes hooks logic that are jsx specific. + * - Adds any logic specific to markdown rendering. */ -export async function renderMarkdown( +export async function render( project: ProjectReflection, outputDirectory: string, -): Promise { +) { this.renderStartTime = Date.now(); if (this.cleanOutputDir) { @@ -66,20 +67,6 @@ export async function renderMarkdown( return; } - if (this.githubPages) { - try { - const text = - 'TypeDoc added this file to prevent GitHub Pages from ' + - 'using Jekyll. You can turn off this behavior by setting ' + - 'the `githubPages` option to false.'; - - fs.writeFileSync(path.join(outputDirectory, '.nojekyll'), text); - } catch (error) { - this.application.warn('Could not create .nojekyll file.'); - return; - } - } - this.prepareTheme(); const output = new MarkdownRendererEvent( @@ -88,30 +75,11 @@ export async function renderMarkdown( project, ); - if (this.packages) { - const getOptionsForPackage = new Promise((resolve, reject) => { - const packages = {}; - Object.entries(this.packages).forEach(async ([k, v]) => { - packages[k] = {}; - const origOptions = this.application.options; - const packageOptions: Options = origOptions.copyForPackage( - (v as any).dir, - ); - await packageOptions.read(this.application.logger, (v as any).dir); - const isSet = packageOptions.isSet('outputFileStrategy'); - packages[k].outputFileStrategy = isSet - ? packageOptions.getValue('outputFileStrategy') - : null; - resolve(packages); - }); - }); - - this.packages = await getOptionsForPackage; - } - output.urls = this.theme!.getUrls(project); output.navigation = this.theme!.getNavigation(project); + this.trigger(output); + await Promise.all(this.preRenderAsyncJobs.map((job) => job(output))); this.preRenderAsyncJobs = []; @@ -147,7 +115,7 @@ export async function renderMarkdown( } try { - writeFileSync(page.filename, formatContents(page.contents as string)); + writeFileSync(page.filename, page.contents as string); } catch (error) { this.application.logger.error(`Could not write ${page.filename}`); } @@ -161,22 +129,3 @@ export async function renderMarkdown( this.theme = void 0; } - -function writeFileSync(fileName: string, data: string) { - fs.mkdirSync(path.dirname(normalizePath(fileName)), { recursive: true }); - fs.writeFileSync(normalizePath(fileName), data); -} - -function normalizePath(path: string) { - return path.replace(/\\/g, '/'); -} - -function nicePath(absPath: string) { - if (!path.isAbsolute(absPath)) return absPath; - - const relativePath = path.relative(process.cwd(), absPath); - if (relativePath.startsWith('..')) { - return normalizePath(absPath); - } - return `./${normalizePath(relativePath)}`; -} diff --git a/packages/typedoc-plugin-markdown/src/plugin/url-mapping.ts b/packages/typedoc-plugin-markdown/src/plugin/url-mapping.ts deleted file mode 100644 index deae9d388..000000000 --- a/packages/typedoc-plugin-markdown/src/plugin/url-mapping.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { MarkdownPageEvent } from './events'; - -export class UrlMapping { - url: string; - - model: Model; - - template: RenderTemplate>; - - constructor( - url: string, - model: Model, - template: RenderTemplate>, - ) { - this.url = url; - this.model = model; - this.template = template; - } -} - -export type RenderTemplate = (data: T) => string; diff --git a/packages/typedoc-plugin-markdown/src/plugin/utils.ts b/packages/typedoc-plugin-markdown/src/plugin/utils.ts new file mode 100644 index 000000000..411fd7fbc --- /dev/null +++ b/packages/typedoc-plugin-markdown/src/plugin/utils.ts @@ -0,0 +1,38 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Some useful utility functions - essentially cherry picked from: + * + * - https://github.com/TypeStrong/typedoc/blob/master/src/lib/utils/fs.ts + * - https://github.com/TypeStrong/typedoc/blob/master/src/lib/utils/path.ts + * + * @module + */ + +/** + * Writes a file to disc. + */ +export function writeFileSync(fileName: string, data: string) { + fs.mkdirSync(path.dirname(normalizePath(fileName)), { recursive: true }); + fs.writeFileSync(normalizePath(fileName), data); +} + +/** + * Returns a readable path from an absolute path. + */ +export function nicePath(absPath: string) { + if (!path.isAbsolute(absPath)) return absPath; + const relativePath = path.relative(process.cwd(), absPath); + if (relativePath.startsWith('..')) { + return normalizePath(absPath); + } + return `./${normalizePath(relativePath)}`; +} + +/** + * Normalizes directory seperators from a given path. + */ +export function normalizePath(path: string) { + return path.replace(/\\/g, '/'); +} diff --git a/packages/typedoc-plugin-markdown/src/support/elements.ts b/packages/typedoc-plugin-markdown/src/support/elements.ts deleted file mode 100644 index d360f65d0..000000000 --- a/packages/typedoc-plugin-markdown/src/support/elements.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * A set of pure functions that returns markdown elements as strings. - * - * @module - * - */ - -import { formatContents, trimLastLine, unEscapeChars } from './utils'; - -export const heading = (level: number, text: string) => { - level = level > 6 ? 6 : level; - return `${[...Array(level)].map(() => '#').join('')} ${text}`; -}; - -export const link = (label: string, url: string | null) => - url ? `[${label}](${url})` : ''; - -export const bold = (text: string) => `**${text}**`; - -export const italic = (text: string) => `*${text}*`; - -export const backTicks = (text: string) => - /(\`)/g.test(text) ? text.replace(/`/g, '\\`') : `\`${text}\``; - -export const unorderedList = (items: T[]) => - items.map((item) => `- ${item}`).join('\n'); - -export const horizontalRule = () => '\n\n***\n\n'; - -export const codeBlock = (content: string) => { - const trimmedContent = - content.endsWith('}') || - content.endsWith('};') || - content.endsWith('>') || - content.endsWith('>;') - ? trimLastLine(content) - : content; - return '```ts\n' + unEscapeChars(trimmedContent) + '\n```'; -}; - -export const strikeThrough = (content: string) => `~~${content}~~`; - -export const table = (headers: string[], rows: string[][]) => - `\n| ${headers.join(' | ')} |\n| ${headers - .map(() => ':------') - .join(' | ')} |\n${rows.map((row) => `| ${row.join(' | ')} |\n`).join('')}`; - -export const blockQuoteBlock = (content: string) => { - const lines = formatContents(content).split('\n'); - return lines - .map((line) => (line.length ? `> ${line.trim()}` : '>')) - .join('\n'); -}; - -export const indentBlock = (content: string) => { - const lines = content.split('\n'); - return lines - .filter((line) => Boolean(line.length)) - .map((line) => ` ${line}`) - .join('\n'); -}; diff --git a/packages/typedoc-plugin-markdown/src/support/utils.ts b/packages/typedoc-plugin-markdown/src/support/utils.ts deleted file mode 100644 index 52dd197ca..000000000 --- a/packages/typedoc-plugin-markdown/src/support/utils.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * A set of pure utils to be consumed accross the plugin. - * - * @module - */ - -export function escapeChars(str: string) { - return str - .replace(/>/g, '\\>') - .replace(//gi; - return str.replace(re, (tags) => { - const htmlRe = - /<(?!\/?(div|span|p|a|br|img|ul|li|strike|em|strong|b)(>|\s))[^<]+?>/g; - const shouldEscape = tags.match(htmlRe); - return shouldEscape ? tags.replace(/>/g, '>` ').replace(/ (index === lines.length - 1 ? line.trim() : line)) - .join('\n'); -} - -export function unEscapeChars(str: string) { - return str - .replace(/\\/g, '>') - .replace(/\\_/g, '_') - .replace(/\\{/g, '{') - .replace(/`/g, '') - .replace(/\*\*/g, '') - .replace(/\\\|/g, '|') - .replace(/\\\]/g, ']') - .replace(/\\\[/g, '[') - .replace(/\[([^\[\]]*)\]\((.*?)\)/gm, '$1'); -} - -export function stripComments(str: string) { - return str - .replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:^\s*\/\/(?:.*)$)/g, ' ') - .replace(/\n/g, '') - .replace(/^\s+|\s+$|(\s)+/g, '$1'); -} - -export function formatTableDescriptionCol(str: string) { - return str.replace(/\|/g, '\\|'); -} - -export function formatTableNameCol(str: string) { - return str.includes('|') ? str.replace(/\|/g, '\\|') : `\`${str}\``; -} - -export function stripLineBreaks(str: string, includeHTML = true) { - return str - .replace(/\n(?=(?:[^`]*`[^`]*`)*[^`]*$)/gi, includeHTML ? '
' : ' ') - .replace(/\`\`\`ts/g, '`') - .replace(/\`\`\`/g, '`') - .replace(/\n/g, ' '); -} - -export function camelToTitleCase(text: string) { - return ( - text.substring(0, 1).toUpperCase() + - text.substring(1).replace(/[a-z][A-Z]/g, (x) => `${x[0]} ${x[1]}`) - ); -} - -export function slugify(str: string) { - return str - .trim() - .replace(/[^\w\s-]/g, '') - .replace(/[\s_-]+/g, '-') - .replace(/^-+|-+$/g, ''); -} - -export function formatContents(contents: string) { - return ( - contents.replace(/[\r\n]{3,}/g, '\n\n').replace(/^\s+|\s+$/g, '') + '\n' - ); -} diff --git a/packages/typedoc-plugin-markdown/src/theme/constants/kinds.ts b/packages/typedoc-plugin-markdown/src/theme/constants/kinds.ts index f92075a30..c57c62bcb 100644 --- a/packages/typedoc-plugin-markdown/src/theme/constants/kinds.ts +++ b/packages/typedoc-plugin-markdown/src/theme/constants/kinds.ts @@ -1,3 +1,5 @@ +// THIS FILE IS AUTOGENERATED - DO NOT EDIT DIRECTLY + export const KIND_DEFAULTS: Record = { 'kind.class.singular': 'Class', 'kind.class.plural': 'Classes', diff --git a/packages/typedoc-plugin-markdown/src/theme/helpers.ts b/packages/typedoc-plugin-markdown/src/theme/helpers.ts index 6cc1d43e5..ee2a81a8f 100644 --- a/packages/typedoc-plugin-markdown/src/theme/helpers.ts +++ b/packages/typedoc-plugin-markdown/src/theme/helpers.ts @@ -7,43 +7,10 @@ import { DeclarationReflection, - ParameterReflection, ProjectReflection, ReflectionKind, SignatureReflection, } from 'typedoc'; -import { backTicks, strikeThrough } from '../support/elements'; -import { escapeChars } from '../support/utils'; - -export function getDeclarationType(declaration: DeclarationReflection) { - if (declaration.signatures) { - return declaration.signatures[0].type; - } - if (declaration.getSignature) { - return declaration.getSignature.type; - } - if (declaration.setSignature) { - return declaration.setSignature.type; - } - return declaration.type; -} - -export function getProjectDisplayName( - project: ProjectReflection, - includeVersion: boolean, -): string { - const version = - includeVersion && project.packageVersion - ? ` v${project.packageVersion}` - : ''; - return `${project.name}${version ? version : ''}`; -} - -export function hasIndex( - reflection: DeclarationReflection | ProjectReflection, -) { - return reflection.groups?.some((group) => group.allChildrenHaveOwnDocument()); -} export function isAbsoluteIndex( reflection: DeclarationReflection | ProjectReflection, @@ -103,50 +70,6 @@ export const KEYWORD_MAP = { [ReflectionKind.Function]: 'function', }; -export function getMemberTitle(reflection: DeclarationReflection) { - const md: string[] = []; - - const name: string[] = []; - - if ( - reflection?.kind === ReflectionKind.Class && - reflection?.flags?.includes('Abstract') - ) { - name.push(backTicks('abstract') + ' '); - } - - name.push( - `${ - reflection.name.startsWith('[') && reflection.signatures?.length - ? backTicks(reflection.name) - : escapeChars(reflection.name) - }`, - ); - - if (reflection.signatures?.length) { - name.push('()'); - } - - if (reflection.typeParameters) { - const typeParameters = reflection.typeParameters - .map((typeParameter) => typeParameter.name) - .join(', '); - name.push(`${`\\<${typeParameters}\\>`}`); - } - - if (reflection.flags.isOptional) { - name.push('?'); - } - - if (reflection.isDeprecated && reflection.isDeprecated()) { - md.push(strikeThrough(name.join(''))); - } else { - md.push(name.join('')); - } - - return md.join(': '); -} - export function flattenDeclarations( props: DeclarationReflection[], includeSignatures = false, @@ -218,37 +141,6 @@ export function flattenDeclarations( ); } -export function getSignatureParameters( - parameters: ParameterReflection[], - format = false, -) { - const firstOptionalParamIndex = parameters.findIndex( - (parameter) => parameter.flags.isOptional, - ); - return ( - '(' + - parameters - .map((param, i) => { - const paramsmd: string[] = []; - if (param.flags.isRest) { - paramsmd.push('...'); - } - const paramItem = `${backTicks(param.name)}${ - param.flags.isOptional || - (firstOptionalParamIndex !== -1 && i > firstOptionalParamIndex) - ? '?' - : '' - }`; - paramsmd.push( - `${format && parameters.length > 2 ? `\n ` : ''}${paramItem}`, - ); - return paramsmd.join(''); - }) - .join(`, `) + - ')' - ); -} - export function getIndexFileName( reflection: ProjectReflection | DeclarationReflection, isPackages = false, diff --git a/packages/typedoc-plugin-markdown/src/theme/index.ts b/packages/typedoc-plugin-markdown/src/theme/index.ts index f0cc7eb36..f764c6a2d 100644 --- a/packages/typedoc-plugin-markdown/src/theme/index.ts +++ b/packages/typedoc-plugin-markdown/src/theme/index.ts @@ -5,5 +5,6 @@ * @module */ -export * from './render-context'; -export * from './theme'; +export * from './markdown-theme'; +export * from './markdown-theme-render-context'; +export * from './models'; diff --git a/packages/typedoc-plugin-markdown/src/theme/render-context.ts b/packages/typedoc-plugin-markdown/src/theme/markdown-theme-render-context.ts similarity index 56% rename from packages/typedoc-plugin-markdown/src/theme/render-context.ts rename to packages/typedoc-plugin-markdown/src/theme/markdown-theme-render-context.ts index 8bfdbd93d..9e8c6df96 100644 --- a/packages/typedoc-plugin-markdown/src/theme/render-context.ts +++ b/packages/typedoc-plugin-markdown/src/theme/markdown-theme-render-context.ts @@ -1,9 +1,14 @@ import * as path from 'path'; -import { Options, Reflection, ReflectionKind } from 'typedoc'; -import { TextContentMappings } from '../options/models'; -import { MarkdownPageEvent } from '../plugin/events'; +import { + DeclarationReflection, + Options, + ParameterReflection, + Reflection, + ReflectionKind, +} from 'typedoc'; +import { MarkdownPageEvent, MarkdownTheme } from '..'; +import { TextContentMappings } from '../plugin/options/models'; import { PLURAL_KIND_KEY_MAP, SINGULAR_KIND_KEY_MAP } from './constants/kinds'; -import { MarkdownTheme } from './theme'; /* start_imports */ import { breadcrumbs } from './resources/partials/breadcrumbs'; @@ -70,13 +75,105 @@ function bind(fn: (f: F, ...a: L) => R, first: F) { * The render context of the {@link MarkdownTheme}. * This follows the implementation of TypeDocs [DefaultThemeRenderContext](https://typedoc.org/api/classes/DefaultThemeRenderContext.html) */ + export class MarkdownThemeRenderContext { constructor( private theme: MarkdownTheme, - public page: MarkdownPageEvent | null, + private page: MarkdownPageEvent | null, public options: Options, ) {} + /** + * Object containing functions to render various types of markdown. + */ + markdown = { + /** + * Returns a heading in markdown format + * @param level The level of the heading + * @param text The text of the heading + */ + heading: (level: number, text: string) => { + level = level > 6 ? 6 : level; + return `${[...Array(level)].map(() => '#').join('')} ${text}`; + }, + /** + * The link element + * @param label The text to display for the link + * @param url The url to link to + */ + link: (label: string, url: string | null) => + url ? `[${label}](${url})` : '', + bold: (text: string) => `**${text}**`, + + italic: (text: string) => `*${text}*`, + + backTicks: (text: string) => + /(\`)/g.test(text) ? text.replace(/`/g, '\\`') : `\`${text}\``, + + unorderedList: (items: T[]) => + items.map((item) => `- ${item}`).join('\n'), + + horizontalRule: () => '\n\n***\n\n', + + codeBlock: (content: string) => { + const unEscapeChars = (str: string) => { + return str + .replace(/\\/g, '>') + .replace(/\\_/g, '_') + .replace(/\\{/g, '{') + .replace(/`/g, '') + .replace(/\*\*/g, '') + .replace(/\\\|/g, '|') + .replace(/\\\]/g, ']') + .replace(/\\\[/g, '[') + .replace(/\[([^\[\]]*)\]\((.*?)\)/gm, '$1'); + }; + const trimLastLine = (content: string) => { + const lines = content.split('\n'); + return lines + .map((line, index) => + index === lines.length - 1 ? line.trim() : line, + ) + .join('\n'); + }; + const trimmedContent = + content.endsWith('}') || + content.endsWith('};') || + content.endsWith('>') || + content.endsWith('>;') + ? trimLastLine(content) + : content; + return '```ts\n' + unEscapeChars(trimmedContent) + '\n```'; + }, + + strikeThrough: (content: string) => `~~${content}~~`, + + table: (headers: string[], rows: string[][]) => + `\n| ${headers.join(' | ')} |\n| ${headers + .map(() => ':------') + .join(' | ')} |\n${rows + .map((row) => `| ${row.join(' | ')} |\n`) + .join('')}`, + + blockQuoteBlock: (content: string) => { + const lines = ( + content.replace(/[\r\n]{3,}/g, '\n\n').replace(/^\s+|\s+$/g, '') + '\n' + ).split('\n'); + return lines + .map((line) => (line.length ? `> ${line.trim()}` : '>')) + .join('\n'); + }, + + indentBlock: (content: string) => { + const lines = content.split('\n'); + return lines + .filter((line) => Boolean(line.length)) + .map((line) => ` ${line}`) + .join('\n'); + }, + }; + urlTo = (reflection: Reflection) => { return this.relativeURL(reflection.url); }; @@ -105,6 +202,66 @@ export class MarkdownThemeRenderContext { } }; + /** + * A set of pure utils to be consumed accross the plugin. + * + */ + + utils = { + escapeChars: (str: string) => { + return str + .replace(/>/g, '\\>') + .replace(/ { + const re = /<(?=(?:[^`]*`[^`]*`)*[^`]*$)[^<]+?>/gi; + return str.replace(re, (tags) => { + const htmlRe = + /<(?!\/?(div|span|p|a|br|img|ul|li|strike|em|strong|b)(>|\s))[^<]+?>/g; + const shouldEscape = tags.match(htmlRe); + return shouldEscape + ? tags.replace(/>/g, '>` ').replace(/ { + return str + .replace(/(?:\/\*(?:[\s\S]*?)\*\/)|(?:^\s*\/\/(?:.*)$)/g, ' ') + .replace(/\n/g, '') + .replace(/^\s+|\s+$|(\s)+/g, '$1'); + }, + + formatTableDescriptionCol: (str: string) => { + return str.replace(/\|/g, '\\|'); + }, + + formatTableNameCol: (str: string) => { + return str.includes('|') ? str.replace(/\|/g, '\\|') : `\`${str}\``; + }, + + stripLineBreaks: (str: string, includeHTML = true) => { + return str + .replace( + /\n(?=(?:[^`]*`[^`]*`)*[^`]*$)/gi, + includeHTML ? '
' : ' ', + ) + .replace(/\`\`\`ts/g, '`') + .replace(/\`\`\`/g, '`') + .replace(/\n/g, ' '); + }, + }; + getTextContent(key: keyof TextContentMappings) { return this.theme.textMappings[key]; } @@ -122,6 +279,10 @@ export class MarkdownThemeRenderContext { return this.getTextContent(key) || singularString; } + parseUrl(url: string) { + return encodeURI(url); + } + indexTitle(textContent: string, name: string, version?: string) { return textContent .replace('{projectName}', name) @@ -130,8 +291,95 @@ export class MarkdownThemeRenderContext { .trim(); } - parseUrl(url: string) { - return encodeURI(url); + getParameterDefaultValue(parameter: ParameterReflection) { + return parameter.defaultValue && parameter.defaultValue !== '...' + ? parameter.defaultValue + : 'undefined'; + } + + getDeclarationType(declaration: DeclarationReflection) { + if (declaration.signatures) { + return declaration.signatures[0].type; + } + if (declaration.getSignature) { + return declaration.getSignature.type; + } + if (declaration.setSignature) { + return declaration.setSignature.type; + } + return declaration.type; + } + + getMemberTitle(reflection: DeclarationReflection) { + const md: string[] = []; + + const name: string[] = []; + + if ( + reflection?.kind === ReflectionKind.Class && + reflection.flags?.includes('Abstract') + ) { + name.push(this.markdown.backTicks('abstract') + ' '); + } + + name.push( + `${ + reflection.name.startsWith('[') && reflection.signatures?.length + ? this.markdown.backTicks(reflection.name) + : this.utils.escapeChars(reflection.name) + }`, + ); + + if (reflection.signatures?.length) { + name.push('()'); + } + + if (reflection.typeParameters) { + const typeParameters = reflection.typeParameters + .map((typeParameter) => typeParameter.name) + .join(', '); + name.push(`${`\\<${typeParameters}\\>`}`); + } + + if (reflection.flags.isOptional) { + name.push('?'); + } + + if (reflection.isDeprecated && reflection.isDeprecated()) { + md.push(this.markdown.strikeThrough(name.join(''))); + } else { + md.push(name.join('')); + } + + return md.join(': '); + } + + getSignatureParameters(parameters: ParameterReflection[], format = false) { + const firstOptionalParamIndex = parameters.findIndex( + (parameter) => parameter.flags.isOptional, + ); + return ( + '(' + + parameters + .map((param, i) => { + const paramsmd: string[] = []; + if (param.flags.isRest) { + paramsmd.push('...'); + } + const paramItem = `${this.markdown.backTicks(param.name)}${ + param.flags.isOptional || + (firstOptionalParamIndex !== -1 && i > firstOptionalParamIndex) + ? '?' + : '' + }`; + paramsmd.push( + `${format && parameters.length > 2 ? `\n ` : ''}${paramItem}`, + ); + return paramsmd.join(''); + }) + .join(`, `) + + ')' + ); } /* start_resources */ diff --git a/packages/typedoc-plugin-markdown/src/theme/theme.ts b/packages/typedoc-plugin-markdown/src/theme/markdown-theme.ts similarity index 81% rename from packages/typedoc-plugin-markdown/src/theme/theme.ts rename to packages/typedoc-plugin-markdown/src/theme/markdown-theme.ts index bf0dffe1a..ab7f21b97 100644 --- a/packages/typedoc-plugin-markdown/src/theme/theme.ts +++ b/packages/typedoc-plugin-markdown/src/theme/markdown-theme.ts @@ -1,27 +1,24 @@ import { DeclarationReflection, + DefaultTheme, ProjectReflection, Reflection, ReflectionKind, - RenderTemplate, Renderer, Theme, } from 'typedoc'; -import { OutputFileStrategy, StaticText } from '../options/maps'; -import { TextContentMappings } from '../options/models'; -import { MarkdownPageEvent } from '../plugin/events'; -import { UrlMapping } from '../plugin/url-mapping'; -import { slugify } from '../support/utils'; -import { NavigationItem, TemplateMapping } from './models'; -import { NavigationContext } from './navigation-context'; -import { MarkdownThemeRenderContext } from './render-context'; -import { UrlsContext } from './urls-context'; + +import { MarkdownPageEvent } from '../plugin'; +import { OutputFileStrategy, StaticText } from '../plugin/options/maps'; +import { TextContentMappings } from '../plugin/options/models'; +import { MarkdownThemeRenderContext } from './markdown-theme-render-context'; +import { NavigationItem, RenderTemplate, TemplateMapping } from './models'; +import navigation from './navigation'; +import urls from './urls'; /** * This is in-built MarkdownTheme which extends TypeDocs Theme class. - * This follows the implementation of TypeDoc's [DefaultTheme](https://typedoc.org/api/classes/DefaultThemeRender.html). - * - * The {@link render } and {@link getUrls} methods is where the work happens. + * This follows the implementation of TypeDoc's {@link DefaultTheme}. */ export class MarkdownTheme extends Theme { textMappings: TextContentMappings; @@ -32,25 +29,23 @@ export class MarkdownTheme extends Theme { */ constructor(renderer: Renderer) { super(renderer); - - // DEPRECATED PROPS WARNING (to delete) - const deprecatedOptions = [ - 'indexPageTitle', - 'memberPageTitle', - 'hideInPageTOC', - ]; - - deprecatedOptions.forEach((option) => { - if (this.application.options.isSet(option)) { - this.application.logger.warn( - `[typedoc-plugin-markdown] "${option}" is deprecated and will be removed in v4 release. Please see https://www.typedoc-plugin-markdown.org/options#${option.toLowerCase()}`, - ); - } - }); - this.textMappings = this.getTextContentMappings(); + this.runDeprecatedWanings(); + } + + /** + * Renders a template and page model to a string. + */ + render( + page: MarkdownPageEvent, + template: RenderTemplate>, + ) { + return this.formatContents(template(page)); } + /** + * Creates a instance of render context for each page template. + */ getRenderContext(pageEvent: MarkdownPageEvent | null) { return new MarkdownThemeRenderContext( this, @@ -59,44 +54,6 @@ export class MarkdownTheme extends Theme { ); } - getTextContent(key: keyof TextContentMappings) { - return this.textMappings[key]; - } - - private getTextContentMappings() { - const textFromOptions = this.application.options.getValue( - 'textContentMappings', - ); - const knownKeys = Object.keys(StaticText); - for (const key of Object.keys(textFromOptions)) { - if (!knownKeys.includes(key)) { - this.application.logger.warn( - `[typedoc-plugin-markdown] "${key}" is not a valid "textContentMappings" key. Valid keys are ${knownKeys - .map((key) => `"${key}"`) - .join(', ')}.`, - ); - } - } - return { ...StaticText, ...(textFromOptions || {}) }; - } - - private getNavigationContext() { - return new NavigationContext( - this, - this.application.options.getRawValues(), - this.application.renderer, - ); - } - - private getUrlsContext(project: ProjectReflection) { - return new UrlsContext( - this, - project, - this.application.renderer, - this.application.options, - ); - } - readmeTemplate = (pageEvent: MarkdownPageEvent) => { return this.getRenderContext(pageEvent).readmeTemplate(pageEvent); }; @@ -115,22 +72,34 @@ export class MarkdownTheme extends Theme { return this.getRenderContext(pageEvent).memberTemplate(pageEvent); }; - /** - * Renders a template and page model to a string. - */ - render( - page: MarkdownPageEvent, - template: RenderTemplate>, - ) { - return template(page) as string; + getUrls(project: ProjectReflection) { + return urls(this, project).getUrls(); } - getUrls(project: ProjectReflection): UrlMapping[] { - return this.getUrlsContext(project).getUrls(); + getNavigation(project: ProjectReflection): NavigationItem[] { + return navigation(this, project).getNavigation(); } - getNavigation(project: ProjectReflection): NavigationItem[] { - return this.getNavigationContext().getNavigation(project); + slugify(str: string) { + return str + .toLowerCase() + .trim() + .replace(/[^\w\s-]/g, '') + .replace(/[\s_-]+/g, '-') + .replace(/^-+|-+$/g, ''); + } + + formatContents(contents: string) { + return ( + contents.replace(/[\r\n]{3,}/g, '\n\n').replace(/^\s+|\s+$/g, '') + '\n' + ); + } + + /** + * Returns the text content from a given key. + */ + getTextContent(key: keyof TextContentMappings) { + return this.textMappings[key]; } /** @@ -147,7 +116,7 @@ export class MarkdownTheme extends Theme { const getDirectoryName = (reflectionKind: ReflectionKind) => { const pluralString = ReflectionKind.pluralString(reflectionKind); - return slugify(pluralString).toLowerCase(); + return this.slugify(pluralString).toLowerCase(); }; // const membersWithOwnFile = @@ -229,4 +198,38 @@ export class MarkdownTheme extends Theme { } return mappings[kind]; } + + private getTextContentMappings() { + const textFromOptions = this.application.options.getValue( + 'textContentMappings', + ); + const knownKeys = Object.keys(StaticText); + for (const key of Object.keys(textFromOptions)) { + if (!knownKeys.includes(key)) { + this.application.logger.warn( + `[typedoc-plugin-markdown] "${key}" is not a valid "textContentMappings" key. Valid keys are ${knownKeys + .map((key) => `"${key}"`) + .join(', ')}.`, + ); + } + } + return { ...StaticText, ...(textFromOptions || {}) }; + } + + private runDeprecatedWanings() { + // DEPRECATED PROPS WARNING (to delete) + const deprecatedOptions = [ + 'indexPageTitle', + 'memberPageTitle', + 'hideInPageTOC', + ]; + + deprecatedOptions.forEach((option) => { + if (this.application.options.isSet(option)) { + this.application.logger.warn( + `[typedoc-plugin-markdown] "${option}" is deprecated and will be removed in v4 release. Please see https://www.typedoc-plugin-markdown.org/options#${option.toLowerCase()}`, + ); + } + }); + } } diff --git a/packages/typedoc-plugin-markdown/src/theme/models.ts b/packages/typedoc-plugin-markdown/src/theme/models.ts index 93d71cce3..49eca2600 100644 --- a/packages/typedoc-plugin-markdown/src/theme/models.ts +++ b/packages/typedoc-plugin-markdown/src/theme/models.ts @@ -5,7 +5,8 @@ */ import { ReflectionKind } from 'typedoc'; -import { OutputFileStrategy } from '../options/maps'; +import { MarkdownPageEvent } from '../plugin/events'; +import { OutputFileStrategy } from '../plugin/options/maps'; export interface UrlOption { parentUrl?: string; @@ -27,3 +28,11 @@ export interface NavigationItem { isReadme?: boolean; isGroup?: boolean; } + +export interface UrlMapping { + url: string; + model: Model; + template: RenderTemplate>; +} + +export type RenderTemplate = (data: T) => string; diff --git a/packages/typedoc-plugin-markdown/src/theme/navigation-context.ts b/packages/typedoc-plugin-markdown/src/theme/navigation.ts similarity index 57% rename from packages/typedoc-plugin-markdown/src/theme/navigation-context.ts rename to packages/typedoc-plugin-markdown/src/theme/navigation.ts index 2f3f52163..18be7b391 100644 --- a/packages/typedoc-plugin-markdown/src/theme/navigation-context.ts +++ b/packages/typedoc-plugin-markdown/src/theme/navigation.ts @@ -6,50 +6,47 @@ import { ReflectionCategory, ReflectionGroup, ReflectionKind, - Renderer, - TypeDocOptions, } from 'typedoc'; -import { OutputFileStrategy } from '../options/maps'; -import { NavigationItem } from '../theme/models'; -import { MarkdownTheme } from './theme'; +import { OutputFileStrategy } from '../plugin/options/maps'; +import { MarkdownTheme } from './markdown-theme'; +import { NavigationItem } from './models'; -export class NavigationContext { - navigation: NavigationItem[] = []; +export default (theme: MarkdownTheme, project: ProjectReflection) => { + const options = theme.application.options; + const navigation: NavigationItem[] = []; + const optionsForPackages = (theme.application.renderer as any).packageOptions; - constructor( - public theme: MarkdownTheme, - public options: Partial, - public renderer: Renderer, - ) {} - - getNavigation(project: ProjectReflection): NavigationItem[] { + function getNavigation() { const isPackages = - this.options.entryPointStrategy === EntryPointStrategy.Packages; + options.getValue('entryPointStrategy') === EntryPointStrategy.Packages; if (isPackages) { - if (Object.keys((this.renderer as any).packages)?.length === 1) { - this.buildNavigationFromProject(project); + if (Object.keys(optionsForPackages)?.length === 1) { + buildNavigationFromProject(project); } else { project.children?.forEach((projectChild) => { - this.buildNavigationFromPackage(projectChild); + buildNavigationFromPackage(projectChild); }); } } else { - this.buildNavigationFromProject(project); + buildNavigationFromProject(project); } - return this.navigation; + return navigation; } - buildNavigationFromPackage(projectChild: DeclarationReflection) { - const entryFileName = this.options.entryFileName as string; + function buildNavigationFromPackage(projectChild: DeclarationReflection) { + const entryFileName = options.getValue('entryFileName'); const preservePackageReadme = - Boolean(projectChild.readme) && !this.options.mergeReadme; + Boolean(projectChild.readme) && !options.getValue('mergeReadme'); + + const packageOptions = optionsForPackages[projectChild.name]; - const packageMeta = (this.renderer as any).packages[projectChild.name]; + const isSet = packageOptions.isSet('outputFileStrategy'); - const outputFileStrategy = - packageMeta?.outputFileStrategy || this.options.outputFileStrategy; + const outputFileStrategy = isSet + ? packageOptions.getValue('outputFileStrategy') + : options.getValue('outputFileStrategy'); const projectChildUrl = preservePackageReadme ? `${path.dirname(projectChild.url as string)}/${entryFileName}` @@ -69,68 +66,62 @@ export class NavigationContext { outputFileStrategy === OutputFileStrategy.Modules ) { children.push({ - title: this.theme.getTextContent('label.globals'), + title: theme.getTextContent('label.globals'), url: projectChild.url, }); } - const childGroups = this.getChildrenOrGroups( - projectChild, - outputFileStrategy, - ); + const childGroups = getChildrenOrGroups(projectChild, outputFileStrategy); if (childGroups) { children.push(...childGroups); } - this.navigation.push({ + navigation.push({ title: projectChild.name, children, ...(projectChildUrl && { url: projectChildUrl }), }); } - buildNavigationFromProject( + function buildNavigationFromProject( project: ProjectReflection | DeclarationReflection, ) { + const entryModule = options.getValue('entryModule'); if (project.groups?.length) { - const entryModule = Boolean( + const isEntryModule = Boolean( project?.groups[0]?.children.find( - (child) => child.name === this.options.entryModule, + (child) => child.name === entryModule, ), ); const isOnlyModules = project.children?.every((child) => child.kindOf(ReflectionKind.Module), ); if ( - (project.groups.length === 1 && !Boolean(entryModule)) || + (project.groups.length === 1 && !Boolean(isEntryModule)) || isOnlyModules ) { - const children = this.getGroupChildren(project.groups[0]); + const children = getGroupChildren(project.groups[0]); if (children) { - this.navigation.push( - ...children.filter( - (child) => child.title !== this.options.entryModule, - ), + navigation.push( + ...children.filter((child) => child.title !== entryModule), ); } } else { project.groups?.forEach((projectGroup) => { - const children = this.getGroupChildren(projectGroup); + const children = getGroupChildren(projectGroup); const indexModule = projectGroup.children.find( - (child) => child.name === this.options.entryModule, + (child) => child.name === entryModule, ); if (children.length) { - this.navigation.push({ + navigation.push({ title: projectGroup.title, - children: children.filter( - (child) => child.title !== this.options.entryModule, - ), + children: children.filter((child) => child.title !== entryModule), }); } if (indexModule) { - const children = this.getChildrenOrGroups(indexModule); + const children = getChildrenOrGroups(indexModule); if (children) { - this.navigation.push(...children); + navigation.push(...children); } } }); @@ -138,11 +129,11 @@ export class NavigationContext { } } - getCategoryGroupChildren(group: ReflectionCategory) { + function getCategoryGroupChildren(group: ReflectionCategory) { return group.children ?.filter((child) => child.hasOwnDocument) .map((child) => { - const children = this.getChildrenOrGroups(child); + const children = getChildrenOrGroups(child); return { title: child.name, url: child.url, @@ -151,7 +142,7 @@ export class NavigationContext { }); } - getGroupChildren( + function getGroupChildren( group: ReflectionGroup, outputFileStrategy?: OutputFileStrategy, ) { @@ -159,7 +150,7 @@ export class NavigationContext { return group.categories?.map((category) => { return { title: category.title, - children: this.getCategoryGroupChildren(category), + children: getCategoryGroupChildren(category), }; }); } @@ -167,7 +158,7 @@ export class NavigationContext { return group.children ?.filter((child) => child.hasOwnDocument) .map((child) => { - const mapping = this.theme.getTemplateMapping( + const mapping = theme.getTemplateMapping( child.kind, outputFileStrategy, ); @@ -176,7 +167,7 @@ export class NavigationContext { const children = child.categories?.length ? child.categories ?.map((category) => { - const catChildren = this.getCategoryGroupChildren(category); + const catChildren = getCategoryGroupChildren(category); return catChildren.length ? { title: category.title, @@ -186,7 +177,7 @@ export class NavigationContext { }) .filter((cat) => Boolean(cat)) - : this.getChildrenOrGroups(child, outputFileStrategy); + : getChildrenOrGroups(child, outputFileStrategy); return { title: child.name, url: child.url, @@ -196,21 +187,18 @@ export class NavigationContext { }); } - getChildrenOrGroups( + function getChildrenOrGroups( reflection: DeclarationReflection, outputFileStrategy?: OutputFileStrategy, ) { if ( reflection.groups?.some((group) => group.allChildrenHaveOwnDocument()) ) { - if (this.options.excludeGroups) { + if (options.getValue('excludeGroups')) { return reflection.children ?.filter((child) => child.hasOwnDocument) .map((child) => { - const children = this.getChildrenOrGroups( - child, - outputFileStrategy, - ); + const children = getChildrenOrGroups(child, outputFileStrategy); return { title: child.name, url: child.url, @@ -225,17 +213,13 @@ export class NavigationContext { if (isModulesGroup) { return ( - this.getGroupChildren(reflection.groups[0], outputFileStrategy) || - null + getGroupChildren(reflection.groups[0], outputFileStrategy) || null ); } return reflection.groups ?.map((group) => { - const groupChildren = this.getGroupChildren( - group, - outputFileStrategy, - ); + const groupChildren = getGroupChildren(group, outputFileStrategy); return groupChildren.length ? { title: group.title, @@ -247,4 +231,7 @@ export class NavigationContext { } return null; } -} + return { + getNavigation, + }; +}; diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/breadcrumbs.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/breadcrumbs.ts index 9f36e79a3..ed5dde771 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/breadcrumbs.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/breadcrumbs.ts @@ -1,20 +1,19 @@ import * as path from 'path'; import { DeclarationReflection, ProjectReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { MarkdownPageEvent } from '../../../plugin/events'; -import { link } from '../../../support/elements'; -import { escapeChars } from '../../../support/utils'; -import { getProjectDisplayName } from '../../helpers'; +import { MarkdownPageEvent } from '../../..'; /** * Renders the breadcrumbs - * @mergeTarget */ export function breadcrumbs( context: MarkdownThemeRenderContext, page: MarkdownPageEvent, -): string { +) { const md: string[] = []; + const { escapeChars } = context.utils; + + const { link } = context.markdown; const entryFileName = context.options.getValue('entryFileName'); @@ -27,7 +26,7 @@ export function breadcrumbs( const homeLabel = context .getTextContent('breadcrumbs.home') - .replace('{projectName}', getProjectDisplayName(page.project, false)); + .replace('{projectName}', page.project.name); md.push(link(homeLabel, context.relativeURL(entryFileName))); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/comment.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/comment.ts index 668423527..84692b117 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/comment.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/comment.ts @@ -1,7 +1,5 @@ import { Comment } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { bold, heading } from '../../../support/elements'; -import { camelToTitleCase, escapeAngleBrackets } from '../../../support/utils'; /** * @category Partials @@ -15,6 +13,9 @@ export function comment( ): string { const md: string[] = []; + const { heading, bold } = context.markdown; + const { escapeAngleBrackets } = context.utils; + if (showSummary && comment.summary?.length > 0) { md.push(context.commentParts(comment.summary)); } @@ -36,3 +37,10 @@ export function comment( return escapeAngleBrackets(md.join('\n\n')); } + +function camelToTitleCase(text: string) { + return ( + text.substring(0, 1).toUpperCase() + + text.substring(1).replace(/[a-z][A-Z]/g, (x) => `${x[0]} ${x[1]}`) + ); +} diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/footer.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/footer.ts index 7d6d5b6b8..70fc7d33e 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/footer.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/footer.ts @@ -1,11 +1,11 @@ import { MarkdownThemeRenderContext } from '../..'; -import { horizontalRule } from '../../../support/elements'; /** * @category Partials */ export function footer(context: MarkdownThemeRenderContext): string { if (!context.options.getValue('hideGenerator')) { + const { horizontalRule } = context.markdown; const generatorText = context .getTextContent('footer.generator') ?.replace(/TypeDoc/g, '[TypeDoc](https://typedoc.org)') diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/header.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/header.ts index c00abefef..a6b64a4c7 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/header.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/header.ts @@ -5,8 +5,7 @@ import { ReflectionKind, } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { MarkdownPageEvent } from '../../../plugin/events'; -import { bold, link } from '../../../support/elements'; +import { MarkdownPageEvent } from '../../..'; /** * @category Partials @@ -36,6 +35,8 @@ function projectHeader( const md: string[] = []; + const { bold, link } = context.markdown; + const title = context.indexTitle( context.getTextContent('header.title'), page.project.name, @@ -104,6 +105,7 @@ function packageHeader( } const md: string[] = []; + const { bold, link } = context.markdown; const readmeLabel = context.getTextContent('header.readme'); const indexLabel = context.getTextContent('header.docs'); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/index.page.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/index.page.ts index d86f0bfba..a4145ae03 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/index.page.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/index.page.ts @@ -5,10 +5,7 @@ import { ProjectReflection, } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { MarkdownPageEvent } from '../../../plugin/events'; -import { heading } from '../../../support/elements'; -import { escapeChars } from '../../../support/utils'; -import { hasIndex } from '../../helpers'; +import { MarkdownPageEvent } from '../../..'; /** * @category Partials @@ -19,9 +16,11 @@ export function pageIndex( headingLevel: number, ): string { const md: string[] = []; + const { heading } = context.markdown; + const { escapeChars } = context.utils; - if (hasIndex(page.model)) { - md.push(context.reflectionIndex(page.model, false, headingLevel)); + if (page.model?.groups?.some((group) => group.allChildrenHaveOwnDocument())) { + md.push(context.reflectionIndex(page.model, headingLevel)); return md.join('\n\n'); } diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/index.reflection.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/index.reflection.ts index df6959033..616289da5 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/index.reflection.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/index.reflection.ts @@ -6,67 +6,40 @@ import { ReflectionKind, } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { heading, indentBlock, link, table } from '../../../support/elements'; -import { - escapeChars, - formatTableDescriptionCol, - slugify, -} from '../../../support/utils'; export function reflectionIndex( context: MarkdownThemeRenderContext, reflection: ProjectReflection | DeclarationReflection, - inline = false, headingLevel: number, ): string { const md: string[] = []; + const { heading } = context.markdown; + const subHeadingLevel = headingLevel; if (reflection.categories) { reflection.categories.forEach((categoryGroup) => { md.push(heading(subHeadingLevel, categoryGroup.title) + '\n'); - md.push(getGroup(context, categoryGroup, inline) + '\n'); + md.push(getGroup(context, categoryGroup) + '\n'); }); } else { const groups = reflection.groups?.filter((group) => - inline - ? !group.allChildrenHaveOwnDocument() - : group.allChildrenHaveOwnDocument(), + group.allChildrenHaveOwnDocument(), ); groups?.forEach((reflectionGroup) => { if (reflectionGroup.categories) { md.push(heading(subHeadingLevel, reflectionGroup.title) + '\n'); reflectionGroup.categories.forEach((categoryGroup) => { md.push(heading(subHeadingLevel + 1, categoryGroup.title) + '\n'); - md.push(getGroup(context, categoryGroup, inline) + '\n'); + md.push(getGroup(context, categoryGroup) + '\n'); }); } else { - const hasChildren = reflectionGroup.children.some((child) => - Boolean(child.url), + md.push( + heading(subHeadingLevel, context.groupTitle(reflectionGroup.title)) + + '\n', ); - if (inline) { - md.push( - `- [${context.groupTitle( - reflectionGroup.title, - )}](${context.relativeURL(context.page?.url)}#${slugify( - context.groupTitle(reflectionGroup.title), - ).toLowerCase()})`, - ); - if (hasChildren) { - md.push( - indentBlock(getGroup(context, reflectionGroup, inline) + '\n'), - ); - } - } else { - md.push( - heading( - subHeadingLevel, - context.groupTitle(reflectionGroup.title), - ) + '\n', - ); - md.push(getGroup(context, reflectionGroup, inline) + '\n'); - } + md.push(getGroup(context, reflectionGroup) + '\n'); } }); } @@ -76,9 +49,8 @@ export function reflectionIndex( function getGroup( context: MarkdownThemeRenderContext, group: ReflectionGroup | ReflectionCategory, - inline: boolean, ) { - if (!inline && context.options.getValue('indexFormat') === 'table') { + if (context.options.getValue('indexFormat') === 'table') { return getTable(context, group); } return getList(context, group); @@ -88,12 +60,15 @@ function getTable( context: MarkdownThemeRenderContext, group: ReflectionGroup | ReflectionCategory, ) { + const { escapeChars } = context.utils; const reflectionKind = group.children[0].kind; + const { table, link } = context.markdown; const headers = [ ReflectionKind.singularString(reflectionKind), context.getTextContent('label.description'), ]; const rows: string[][] = []; + const { formatTableDescriptionCol } = context.utils; group.children.forEach((child) => { const row: string[] = []; @@ -120,6 +95,7 @@ function getList( context: MarkdownThemeRenderContext, group: ReflectionGroup | ReflectionCategory, ) { + const { escapeChars } = context.utils; const children = group.children .filter((child) => Boolean(child.url)) .map((child) => { diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/list.parameters.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/list.parameters.ts index cad229a63..761576c4a 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/list.parameters.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/list.parameters.ts @@ -1,8 +1,6 @@ import { ParameterReflection, ReflectionKind } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks, bold } from '../../../support/elements'; -import { escapeChars } from '../../../support/utils'; /** * @category Partials @@ -11,6 +9,9 @@ export function parametersList( context: MarkdownThemeRenderContext, parameters: ParameterReflection[], ): string { + const { bold, backTicks } = context.markdown; + const { escapeChars } = context.utils; + const parseParams = (current: any, acc: any) => { const shouldFlatten = current.type?.declaration?.kind === ReflectionKind.TypeLiteral && @@ -64,7 +65,9 @@ export function parametersList( } if (parameter.defaultValue) { - identifier.push('= ' + getDefaultValue(parameter)); + identifier.push( + '= ' + backTicks(context.getParameterDefaultValue(parameter)), + ); } row.push(`• ${rest}${identifier.join('')}`); @@ -78,9 +81,3 @@ export function parametersList( return rows.join('\n\n'); } - -function getDefaultValue(parameter: ParameterReflection) { - return parameter.defaultValue && parameter.defaultValue !== '...' - ? backTicks(parameter.defaultValue) - : backTicks('undefined'); -} diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/list.typeparameters.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/list.typeparameters.ts index 203673081..dbd2034fe 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/list.typeparameters.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/list.typeparameters.ts @@ -1,6 +1,5 @@ import { TypeParameterReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { bold } from '../../../support/elements'; /** * @category Partials @@ -8,12 +7,13 @@ import { bold } from '../../../support/elements'; export function typeParametersList( context: MarkdownThemeRenderContext, typeParameters: TypeParameterReflection[], - headingLevel: number, ): string { const rows: string[] = []; typeParameters?.forEach((typeParameter) => { const row: string[] = []; + const { bold } = context.markdown; + const nameCol: string[] = [bold(typeParameter.name)]; if (typeParameter.type) { diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.accessor.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.accessor.ts index a7068bfd1..a1bf88bf5 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.accessor.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.accessor.ts @@ -1,6 +1,5 @@ import { DeclarationReflection, ReflectionKind } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { heading } from '../../../support/elements'; /** * @category Partials @@ -12,6 +11,8 @@ export function accessorMember( ): string { const md: string[] = []; + const { heading } = context.markdown; + if (declaration.getSignature) { md.push( context.signatureMemberIdentifier(declaration.getSignature, { diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.constructors.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.constructors.ts index f9e163235..4016d319f 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.constructors.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.constructors.ts @@ -1,7 +1,5 @@ import { DeclarationReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { heading } from '../../../support/elements'; -import { escapeChars } from '../../../support/utils'; /** * @category Partials @@ -13,6 +11,9 @@ export function constructorMember( ): string { const md: string[] = []; + const { heading } = context.markdown; + const { escapeChars } = context.utils; + reflection.signatures?.forEach((signature) => { const params = signature.parameters?.map((param) => param.name).join(', '); md.push(heading(headingLevel, `${escapeChars(signature.name)}(${params})`)); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.declaration.identifier.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.declaration.identifier.ts index 4ac572245..d20b6440a 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.declaration.identifier.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.declaration.identifier.ts @@ -1,12 +1,6 @@ import { DeclarationReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks, bold, codeBlock } from '../../../support/elements'; -import { - escapeChars, - stripComments, - stripLineBreaks, -} from '../../../support/utils'; -import { KEYWORD_MAP, getDeclarationType, isGroupKind } from '../../helpers'; +import { KEYWORD_MAP, isGroupKind } from '../../helpers'; /** * @category Partials @@ -16,10 +10,12 @@ export function declarationMemberIdentifier( reflection: DeclarationReflection, ): string { const md: string[] = []; + const { backTicks, bold, codeBlock } = context.markdown; + const { escapeChars, stripComments, stripLineBreaks } = context.utils; const useCodeBlocks = context.options.getValue('useCodeBlocks'); - const declarationType = getDeclarationType(reflection); + const declarationType = context.getDeclarationType(reflection); const prefix: string[] = []; diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.declaration.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.declaration.ts index 5067cba5a..03593d2fb 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.declaration.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.declaration.ts @@ -5,7 +5,6 @@ import { ReflectionType, } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { heading } from '../../../support/elements'; /** * @category Partials @@ -17,6 +16,7 @@ export function declarationMember( nested = false, ) { const md: string[] = []; + const { heading } = context.markdown; md.push(context.declarationMemberIdentifier(declaration)); @@ -74,12 +74,7 @@ export function declarationMember( if (context.options.getValue('parametersFormat') === 'table') { md.push(context.typeParametersTable(declaration.typeParameters)); } else { - md.push( - context.typeParametersList( - declaration.typeParameters, - headingLevel + 1, - ), - ); + md.push(context.typeParametersList(declaration.typeParameters)); } } diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.hierarchy.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.hierarchy.ts index 328b76601..ff6f5d69e 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.hierarchy.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.hierarchy.ts @@ -1,11 +1,5 @@ import { DeclarationHierarchy, SomeType, Type } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { - backTicks, - bold, - heading, - unorderedList, -} from '../../../support/elements'; export function memberHierarchy( context: MarkdownThemeRenderContext, @@ -13,6 +7,7 @@ export function memberHierarchy( headingLevel: number, ): string { const md: string[] = []; + const { heading, unorderedList } = context.markdown; const parent = !declarationHierarchy.isTarget ? declarationHierarchy.types .map((hierarchyType) => { @@ -53,6 +48,7 @@ function getHierarchyType( isTarget: boolean, context: MarkdownThemeRenderContext, ) { + const { bold, backTicks } = context.markdown; return isTarget ? bold(backTicks(hierarchyType.toString())) : context.someType(hierarchyType as SomeType); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.indexsignature.title.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.indexsignature.title.ts index b40d7621d..09e3c4b40 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.indexsignature.title.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.indexsignature.title.ts @@ -1,6 +1,5 @@ import { SignatureReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks } from '../../../support/elements'; /** * @category Partials @@ -10,6 +9,7 @@ export function indexSignatureTitle( signature: SignatureReflection, ): string { const md = ['']; + const { backTicks } = context.markdown; const params = signature.parameters ? signature.parameters.map((parameter) => { return parameter.type diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.inheritance.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.inheritance.ts index cf25070cf..089bdd756 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.inheritance.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.inheritance.ts @@ -5,7 +5,6 @@ import { SignatureReflection, } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks, heading, link } from '../../../support/elements'; export function inheritance( context: MarkdownThemeRenderContext, @@ -13,6 +12,7 @@ export function inheritance( headingLevel: number, ): string { const md: string[] = []; + const { heading } = context.markdown; if (reflection.implementationOf) { if (headingLevel !== -1) { @@ -47,6 +47,7 @@ const typeAndParent = ( context: MarkdownThemeRenderContext, props: ArrayType | ReferenceType, ) => { + const { link, backTicks } = context.markdown; if (props) { if ('elementType' in props) { return typeAndParent(context, props.elementType as any) + '[]'; diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.reflection.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.reflection.ts index f194ccd23..6ee2c4c8a 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.reflection.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.reflection.ts @@ -1,7 +1,6 @@ import { DeclarationReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { heading, unorderedList } from '../../../support/elements'; -import { hasIndex, isAbsoluteIndex } from '../../helpers'; +import { isAbsoluteIndex } from '../../helpers'; /** * @category Partials @@ -12,6 +11,7 @@ export function reflectionMember( headingLevel: number, ): string { const md: string[] = []; + const { heading, unorderedList } = context.markdown; if (reflection.comment) { md.push(context.comment(reflection.comment, headingLevel)); @@ -31,9 +31,7 @@ export function reflectionMember( if (context.options.getValue('parametersFormat') === 'table') { md.push(context.typeParametersTable(reflection.typeParameters)); } else { - md.push( - context.typeParametersList(reflection.typeParameters, headingLevel + 1), - ); + md.push(context.typeParametersList(reflection.typeParameters)); } } @@ -59,16 +57,14 @@ export function reflectionMember( md.push(context.indexSignatureTitle(reflection.indexSignature)); } - if (hasIndex(reflection)) { + if (reflection?.groups?.some((group) => group.allChildrenHaveOwnDocument())) { const isAbsolute = isAbsoluteIndex(reflection); - if (isAbsolute) { md.push(heading(headingLevel, context.getTextContent('label.index'))); } md.push( context.reflectionIndex( reflection, - false, isAbsolute ? headingLevel + 1 : headingLevel, ), ); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.identifier.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.identifier.ts index a9a52dfe3..3e40cab2d 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.identifier.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.identifier.ts @@ -1,12 +1,6 @@ import { SignatureReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks, bold, codeBlock } from '../../../support/elements'; -import { escapeChars } from '../../../support/utils'; -import { - KEYWORD_MAP, - getSignatureParameters, - isGroupKind, -} from '../../helpers'; +import { KEYWORD_MAP, isGroupKind } from '../../helpers'; /** * @category Partials @@ -20,6 +14,8 @@ export function signatureMemberIdentifier( }, ): string { const md: string[] = []; + const { backTicks, bold, codeBlock } = context.markdown; + const { escapeChars } = context.utils; const DEFAULT_OPTIONS = { accessor: null, @@ -62,7 +58,9 @@ export function signatureMemberIdentifier( ); } - md.push(getSignatureParameters(signature.parameters || [], useCodeBlocks)); + md.push( + context.getSignatureParameters(signature.parameters || [], useCodeBlocks), + ); if (signature.type) { md.push(`: ${context.someType(signature.type)}`); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.returns.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.returns.ts index 3ab20960a..af4f28d6a 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.returns.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.returns.ts @@ -6,7 +6,6 @@ import { SomeType, } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks, blockQuoteBlock, heading } from '../../../support/elements'; /** * @category Partials @@ -17,6 +16,7 @@ export function signatureMemberReturns( headingLevel: number, ): string { const md: string[] = []; + const { heading, blockQuoteBlock } = context.markdown; const typeDeclaration = (signature.type as any) ?.declaration as DeclarationReflection; @@ -74,6 +74,7 @@ function getReturnType( typeDeclaration?: DeclarationReflection, type?: SomeType, ) { + const { backTicks } = context.markdown; if (typeDeclaration?.children) { return backTicks('Object'); } diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.ts index 629232dab..50a28931b 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.signature.ts @@ -1,6 +1,5 @@ import { ReflectionKind, SignatureReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { heading } from '../../../support/elements'; /** * @category Partials @@ -13,6 +12,7 @@ export function signatureMember( accessor?: string, ): string { const md: string[] = []; + const { heading } = context.markdown; if (!nested) { md.push( @@ -39,9 +39,7 @@ export function signatureMember( if (context.options.getValue('parametersFormat') === 'table') { md.push(context.typeParametersTable(signature.typeParameters)); } else { - md.push( - context.typeParametersList(signature.typeParameters, headingLevel + 1), - ); + md.push(context.typeParametersList(signature.typeParameters)); } } diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.sources.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.sources.ts index 10e6f2aa0..a7d5c40b0 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.sources.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.sources.ts @@ -1,7 +1,5 @@ import { DeclarationReflection, SignatureReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { heading, link } from '../../../support/elements'; -import { escapeChars } from '../../../support/utils'; /** * @category Partials @@ -12,6 +10,8 @@ export function sources( headingLevel: number, ): string { const md: string[] = []; + const { heading, link } = context.markdown; + const { escapeChars } = context.utils; if (headingLevel !== -1) { md.push(heading(headingLevel, context.getTextContent('label.source'))); } diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.ts index 1448441ea..7782954db 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/member.ts @@ -4,9 +4,6 @@ import { ReflectionKind, } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { heading } from '../../../support/elements'; -import { escapeChars } from '../../../support/utils'; -import { getMemberTitle } from '../../helpers'; /** * @category Partials @@ -18,6 +15,8 @@ export function member( nested = false, ): string { const md: string[] = []; + const { heading } = context.markdown; + const { escapeChars } = context.utils; if (context.options.getValue('namedAnchors')) { md.push(``); @@ -27,7 +26,7 @@ export function member( !reflection.hasOwnDocument && !(reflection.kind === ReflectionKind.Constructor) ) { - const memberName = getMemberTitle(reflection); + const memberName = context.getMemberTitle(reflection); const memberHeading = context .getTextContent('title.member') .replace('{name}', memberName) diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/members.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/members.ts index 2ec5a07d8..70c34305d 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/members.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/members.ts @@ -6,7 +6,6 @@ import { } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { heading, horizontalRule } from '../../../support/elements'; import { isGroupKind } from '../../helpers'; export function members( @@ -15,6 +14,7 @@ export function members( headingLevel: number, ): string { const md: string[] = []; + const { heading, horizontalRule } = context.markdown; const displayHr = (reflection: DeclarationReflection) => { if (context.options.getValue('outputFileStrategy') === 'modules') { diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/page.title.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/page.title.ts index 88361e813..c2719ecea 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/page.title.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/page.title.ts @@ -3,10 +3,7 @@ import { ProjectReflection, ReflectionKind, } from 'typedoc'; -import { MarkdownThemeRenderContext } from '../..'; -import { MarkdownPageEvent } from '../../../plugin/events'; - -import { getMemberTitle } from '../../helpers'; +import { MarkdownPageEvent, MarkdownThemeRenderContext } from '../../..'; /** * @category Partials @@ -26,7 +23,7 @@ export function pageTitle( ); } - const name = getMemberTitle(page.model as DeclarationReflection); + const name = context.getMemberTitle(page.model as DeclarationReflection); const textContent = page.model.kindOf(ReflectionKind.Module) ? context.getTextContent('title.modulePage') diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.enum-members.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.enum-members.ts index 529702b8d..5b9b2eb38 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.enum-members.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.enum-members.ts @@ -1,8 +1,5 @@ import { DeclarationReflection, ReflectionType } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks } from '../../../support/elements'; -import { stripLineBreaks } from '../../../support/utils'; -import { getDeclarationType } from '../../helpers'; /** * @category Partials @@ -11,6 +8,8 @@ export function enumMembersTable( context: MarkdownThemeRenderContext, props: DeclarationReflection[], ): string { + const { backTicks } = context.markdown; + const { stripLineBreaks } = context.utils; const comments = props.map((param) => !!param.comment?.hasVisibleComponent()); const hasComments = comments.some((value) => Boolean(value)); @@ -24,7 +23,7 @@ export function enumMembersTable( } const rows = props.map((property: DeclarationReflection) => { - const propertyType = getDeclarationType(property); + const propertyType = context.getDeclarationType(property); const row: string[] = []; const nameColumn: string[] = []; diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.parameters.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.parameters.ts index 61d84f592..819f52e91 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.parameters.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.parameters.ts @@ -1,11 +1,5 @@ import { ParameterReflection, ReflectionKind } from 'typedoc'; - import { MarkdownThemeRenderContext } from '../..'; -import { backTicks, table } from '../../../support/elements'; -import { - formatTableDescriptionCol, - stripLineBreaks, -} from '../../../support/utils'; /** * @category Partials @@ -14,6 +8,9 @@ export function parametersTable( context: MarkdownThemeRenderContext, parameters: ParameterReflection[], ): string { + const { table, backTicks } = context.markdown; + const { stripLineBreaks, formatTableDescriptionCol } = context.utils; + const parseParams = (current: any, acc: any) => { const shouldFlatten = current.type?.declaration?.kind === ReflectionKind.TypeLiteral && @@ -82,7 +79,7 @@ export function parametersTable( } if (showDefaults) { - row.push(getDefaultValue(parameter)); + row.push(backTicks(context.getParameterDefaultValue(parameter))); } if (hasComments) { @@ -102,12 +99,6 @@ export function parametersTable( return table(headers, rows); } -function getDefaultValue(parameter: ParameterReflection) { - return parameter.defaultValue && parameter.defaultValue !== '...' - ? backTicks(parameter.defaultValue) - : backTicks('undefined'); -} - function hasDefaultValues(parameters: ParameterReflection[]) { const defaultValues = (parameters as ParameterReflection[]).map( (param) => diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.properties.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.properties.ts index 3f8517fb7..a0f252a56 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.properties.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.properties.ts @@ -1,15 +1,6 @@ import { DeclarationReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks, strikeThrough, table } from '../../../support/elements'; -import { - formatTableDescriptionCol, - stripLineBreaks, -} from '../../../support/utils'; -import { - flattenDeclarations, - getDeclarationType, - getModifier, -} from '../../helpers'; +import { flattenDeclarations, getModifier } from '../../helpers'; /** * @category Partials @@ -19,6 +10,9 @@ export function propertiesTable( props: DeclarationReflection[], isEventProps = false, ): string { + const { backTicks, table, strikeThrough } = context.markdown; + const { formatTableDescriptionCol, stripLineBreaks } = context.utils; + const modifiers = props.map((param) => getModifier(param)); const hasModifiers = modifiers.some((value) => Boolean(value)); const hasOverrides = props.some((prop) => Boolean(prop.overwrites)); @@ -58,7 +52,7 @@ export function propertiesTable( const declarations = flattenDeclarations(props); declarations.forEach((property: DeclarationReflection, index: number) => { - const propertyType = getDeclarationType(property); + const propertyType = context.getDeclarationType(property); const row: string[] = []; if (hasModifiers) { diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.type-declaration.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.type-declaration.ts index 19a6edb61..5a7416032 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.type-declaration.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.type-declaration.ts @@ -1,11 +1,5 @@ import { DeclarationReflection, SomeType } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { table } from '../../../support/elements'; -import { - formatTableDescriptionCol, - formatTableNameCol, - stripLineBreaks, -} from '../../../support/utils'; import { flattenDeclarations } from '../../helpers'; /** @@ -15,6 +9,10 @@ export function typeDeclarationTable( context: MarkdownThemeRenderContext, props: DeclarationReflection[], ): string { + const { table } = context.markdown; + const { formatTableDescriptionCol, stripLineBreaks, formatTableNameCol } = + context.utils; + const headers: string[] = []; headers.push(context.getTextContent('label.member')); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.typeparameters.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.typeparameters.ts index 4bad09ba8..47a14dbc4 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.typeparameters.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/table.typeparameters.ts @@ -1,10 +1,5 @@ import { TypeParameterReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks, table } from '../../../support/elements'; -import { - formatTableDescriptionCol, - stripLineBreaks, -} from '../../../support/utils'; /** * @category Partials @@ -13,6 +8,9 @@ export function typeParametersTable( context: MarkdownThemeRenderContext, typeParameters: TypeParameterReflection[], ): string { + const { backTicks, table } = context.markdown; + const { formatTableDescriptionCol, stripLineBreaks } = context.utils; + const hasDefault = typeParameters.some((typeParameter) => Boolean(typeParameter.default), ); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.declaration.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.declaration.ts index eaec4464d..e20dc5b1e 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.declaration.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.declaration.ts @@ -1,7 +1,5 @@ import { DeclarationReflection, SomeType } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks } from '../../../support/elements'; -import { getDeclarationType } from '../../helpers'; /** * @category Partials @@ -10,6 +8,7 @@ export function declarationType( context: MarkdownThemeRenderContext, declarationReflection: DeclarationReflection, ): string { + const { backTicks } = context.markdown; if (declarationReflection.indexSignature || declarationReflection.children) { let indexSignature = ''; const declarationIndexSignature = declarationReflection.indexSignature; @@ -41,7 +40,7 @@ export function declarationType( name.push(backTicks(obj.name)); - const theType = getDeclarationType(obj) as SomeType; + const theType = context.getDeclarationType(obj) as SomeType; const typeString = context.someType(theType); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.function.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.function.ts index 657fce2a0..ff02842e1 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.function.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.function.ts @@ -1,6 +1,5 @@ import { SignatureReflection, SomeType } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks } from '../../../support/elements'; /** * @category Partials @@ -9,6 +8,7 @@ export function functionType( context: MarkdownThemeRenderContext, modelSignatures: SignatureReflection[], ): string { + const { backTicks } = context.markdown; const functions = modelSignatures.map((fn) => { const typeParams = fn.typeParameters ? `\\<${fn.typeParameters diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.inferred.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.inferred.ts index 0079c0860..d607cec63 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.inferred.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.inferred.ts @@ -1,6 +1,5 @@ import { InferredType } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { escapeChars } from '../../../support/utils'; /** * @category Partials @@ -9,5 +8,6 @@ export function inferredType( context: MarkdownThemeRenderContext, model: InferredType, ): string { + const { escapeChars } = context.utils; return `infer ${escapeChars(model.name)}`; } diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.intrinsic.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.intrinsic.ts index 7dcd9589f..a62b73aad 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.intrinsic.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.intrinsic.ts @@ -1,6 +1,5 @@ import { IntrinsicType } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks } from '../../../support/elements'; /** * @category Partials @@ -9,5 +8,6 @@ export function intrinsicType( context: MarkdownThemeRenderContext, model: IntrinsicType, ): string { + const { backTicks } = context.markdown; return backTicks(model.name); } diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.query.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.query.ts index 002f649ae..a4878e6df 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.query.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.query.ts @@ -1,6 +1,5 @@ import { QueryType } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { italic } from '../../../support/elements'; /** * @category Partials @@ -9,5 +8,6 @@ export function queryType( context: MarkdownThemeRenderContext, queryType: QueryType, ): string { + const { italic } = context.markdown; return `${italic('typeof')} ${context.someType(queryType.queryType)}`; } diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.reference.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.reference.ts index 6996466cb..7a189e29b 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.reference.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.reference.ts @@ -1,6 +1,5 @@ import { ReferenceType } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks } from '../../../support/elements'; /** * @category Partials @@ -10,6 +9,7 @@ export function referenceType( referenceType: ReferenceType, foreCollpase = false, ): string { + const { backTicks } = context.markdown; if ( referenceType.reflection || (referenceType.name && referenceType.typeArguments) diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.reflection.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.reflection.ts index 489d535ea..612cd2d84 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.reflection.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.reflection.ts @@ -1,6 +1,5 @@ import { ReflectionType } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks } from '../../../support/elements'; /** * @category Partials @@ -10,6 +9,7 @@ export function reflectionType( reflectionType: ReflectionType, foreCollpase = false, ): string { + const { backTicks } = context.markdown; const root = reflectionType instanceof ReflectionType ? reflectionType.declaration diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.some.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.some.ts index 8d3fb7910..22ff0a7fb 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.some.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.some.ts @@ -16,7 +16,6 @@ import { UnknownType, } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { backTicks } from '../../../support/elements'; /** * @category Partials @@ -26,6 +25,7 @@ export function someType( someType: SomeType, foreCollpase = false, ): string { + const { backTicks } = context.markdown; if (!someType) { return ''; } diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.unknown.ts b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.unknown.ts index 476ed4c10..5a752e15e 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.unknown.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/partials/type.unknown.ts @@ -1,6 +1,5 @@ import { UnknownType } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { escapeChars } from '../../../support/utils'; /** * @category Partials @@ -9,5 +8,5 @@ export function unknownType( context: MarkdownThemeRenderContext, model: UnknownType, ): string { - return escapeChars(model.name); + return context.utils.escapeChars(model.name); } diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/templates/index.ts b/packages/typedoc-plugin-markdown/src/theme/resources/templates/index.ts deleted file mode 100644 index 5eabc7ff2..000000000 --- a/packages/typedoc-plugin-markdown/src/theme/resources/templates/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './member'; -export * from './project'; -export * from './read-me'; -export * from './reflection'; diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/templates/member.ts b/packages/typedoc-plugin-markdown/src/theme/resources/templates/member.ts index 1df3a32a6..5dc4ce2da 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/templates/member.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/templates/member.ts @@ -1,7 +1,6 @@ import { DeclarationReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { MarkdownPageEvent } from '../../../plugin/events'; -import { heading } from '../../../support/elements'; +import { MarkdownPageEvent } from '../../..'; /** * @category Templates @@ -11,6 +10,7 @@ export function memberTemplate( page: MarkdownPageEvent, ) { const md: string[] = []; + const { heading } = context.markdown; if (!context.options.getValue('hidePageHeader')) { md.push(context.header(page)); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/templates/project.ts b/packages/typedoc-plugin-markdown/src/theme/resources/templates/project.ts index d45567765..55ba5cc9d 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/templates/project.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/templates/project.ts @@ -1,7 +1,6 @@ import { ProjectReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { MarkdownPageEvent } from '../../../plugin/events'; -import { heading } from '../../../support/elements'; +import { MarkdownPageEvent } from '../../..'; /** * @category Templates @@ -11,6 +10,7 @@ export function projectTemplate( page: MarkdownPageEvent, ) { const md: string[] = []; + const { heading } = context.markdown; if (!context.options.getValue('hidePageHeader')) { md.push(context.header(page)); diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/templates/read-me.ts b/packages/typedoc-plugin-markdown/src/theme/resources/templates/read-me.ts index c2038f070..50dc0e6ed 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/templates/read-me.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/templates/read-me.ts @@ -1,6 +1,6 @@ import { ProjectReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { MarkdownPageEvent } from '../../../plugin/events'; +import { MarkdownPageEvent } from '../../../'; /** * @category Templates diff --git a/packages/typedoc-plugin-markdown/src/theme/resources/templates/reflection.ts b/packages/typedoc-plugin-markdown/src/theme/resources/templates/reflection.ts index b79fcc1a2..38ea58fd9 100644 --- a/packages/typedoc-plugin-markdown/src/theme/resources/templates/reflection.ts +++ b/packages/typedoc-plugin-markdown/src/theme/resources/templates/reflection.ts @@ -1,7 +1,6 @@ import { DeclarationReflection } from 'typedoc'; import { MarkdownThemeRenderContext } from '../..'; -import { MarkdownPageEvent } from '../../../plugin/events'; -import { heading } from '../../../support/elements'; +import { MarkdownPageEvent } from '../../..'; /** * @category Templates @@ -11,6 +10,7 @@ export function reflectionTemplate( page: MarkdownPageEvent, ) { const md: string[] = []; + const { heading } = context.markdown; if (!context.options.getValue('hidePageHeader')) { md.push(context.header(page)); diff --git a/packages/typedoc-plugin-markdown/src/theme/urls-context.ts b/packages/typedoc-plugin-markdown/src/theme/urls-context.ts deleted file mode 100644 index e7d3fc0eb..000000000 --- a/packages/typedoc-plugin-markdown/src/theme/urls-context.ts +++ /dev/null @@ -1,339 +0,0 @@ -import * as path from 'path'; -import { - DeclarationReflection, - EntryPointStrategy, - Options, - ProjectReflection, - Reflection, - ReflectionKind, - Renderer, -} from 'typedoc'; -import { OutputFileStrategy } from '../options/maps'; -import { UrlMapping } from '../plugin/url-mapping'; -import { slugify } from '../support/utils'; -import { getIndexFileName } from './helpers'; -import { UrlOption } from './models'; -import { MarkdownTheme } from './theme'; - -export class UrlsContext { - urls: UrlMapping[] = []; - anchors: Record = {}; - - constructor( - public theme: MarkdownTheme, - public project: ProjectReflection, - public renderer: Renderer, - public options: Options, - ) {} - - /** - * Map the models of the given project to the desired output files. - * Based on TypeDoc DefaultTheme.getUrls() - * - * @param project The project whose urls should be generated. - */ - getUrls(): UrlMapping[] { - const preserveReadme = - Boolean(this.project.readme) && !this.options.getValue('mergeReadme'); - - const preserveModulesPage = - (this.project?.groups && - Boolean( - this.project?.groups[0]?.children.find( - (child) => child.name === this.options.getValue('entryModule'), - ), - )) || - false; - - const isPackages = - this.options.getValue('entryPointStrategy') === - EntryPointStrategy.Packages; - - const entryFileName = this.options.getValue('entryFileName'); - - const indexFilename = getIndexFileName(this.project, isPackages); - - this.project.url = preserveReadme - ? indexFilename - : preserveModulesPage - ? indexFilename - : this.options.getValue('entryFileName'); - - if (preserveReadme) { - this.urls.push( - new UrlMapping( - preserveModulesPage ? 'readme_.md' : entryFileName, - this.project, - this.theme.readmeTemplate, - ), - ); - - this.urls.push( - new UrlMapping(indexFilename, this.project, this.theme.projectTemplate), - ); - } else { - this.urls.push( - new UrlMapping( - preserveModulesPage ? indexFilename : entryFileName, - this.project, - this.theme.projectTemplate, - ), - ); - } - - if (isPackages) { - if (Object.keys((this.renderer as any).packages)?.length === 1) { - this.buildUrlsFromProject(this.project); - } else { - this.project.children?.forEach((projectChild) => { - this.buildUrlsFromPackage(projectChild); - }); - } - } else { - this.buildUrlsFromProject(this.project); - } - return this.urls; - } - - private buildUrlsFromPackage(projectChild: DeclarationReflection) { - const entryFileName = this.options.getValue('entryFileName'); - const preservePackageReadme = - Boolean(projectChild.readme) && !this.options.getValue('mergeReadme'); - - const packagesIndex = getIndexFileName(projectChild); - const packageMeta = (this.renderer as any).packages[projectChild.name]; - - const outputFileStrategy = - packageMeta?.outputFileStrategy || - this.options.getValue('outputFileStrategy'); - - const url = `${projectChild.name}/${ - preservePackageReadme ? packagesIndex : entryFileName - }`; - - if (preservePackageReadme) { - this.urls.push( - new UrlMapping( - `${path.dirname(url)}/${entryFileName}`, - projectChild as any, - this.theme.readmeTemplate, - ), - ); - } - - this.urls.push( - new UrlMapping(url, projectChild as any, this.theme.projectTemplate), - ); - - projectChild.url = url; - - this.buildUrlsFromProject(projectChild, url, outputFileStrategy); - } - - /** - * - * @param project - * @param isPackage - */ - private buildUrlsFromProject( - project: ProjectReflection | DeclarationReflection, - parentUrl?: string, - outputFileStrategy?: OutputFileStrategy, - ) { - project.groups?.forEach((projectGroup) => { - projectGroup.children?.forEach((projectGroupChild) => { - this.buildUrlsFromGroup(projectGroupChild, { - ...(parentUrl && { parentUrl }), - ...(outputFileStrategy && { outputFileStrategy }), - }); - }); - }); - } - - private buildUrlsFromGroup( - reflection: DeclarationReflection, - options: UrlOption, - ) { - const mapping = this.theme.getTemplateMapping( - reflection.kind, - options.outputFileStrategy, - ); - - if (mapping) { - const directory = options.directory || mapping.directory; - const urlPath = this.getUrlPath(reflection, { - ...options, - directory, - }); - - const url = this.getUrl(reflection, urlPath); - - this.urls.push(new UrlMapping(url, reflection, mapping.template)); - reflection.url = url; - reflection.hasOwnDocument = true; - - reflection.groups?.forEach((group) => { - group.children.forEach((groupChild) => { - const mapping = this.theme.getTemplateMapping( - groupChild.kind, - options.outputFileStrategy, - ); - this.buildUrlsFromGroup(groupChild, { - parentUrl: urlPath, - directory: mapping?.directory || null, - outputFileStrategy: options.outputFileStrategy, - }); - }); - }); - } else if (reflection.parent) { - this.applyAnchorUrl(reflection, reflection.parent); - } - } - - getUrl(reflection: DeclarationReflection, urlPath: string) { - if (reflection.name === this.options.getValue('entryModule')) { - return this.options.getValue('entryFileName'); - } - if ( - this.options.getValue('outputFileStrategy') === - OutputFileStrategy.Modules && - reflection.name === 'index' - ) { - return urlPath.replace('index.md', 'module.index.md'); - } - return urlPath; - } - - getUrlPath(reflection: DeclarationReflection, options: UrlOption) { - const alias = reflection.name - .replace(/^_+/, '') - .replace(//, '-'); - - const parentDir = options.parentUrl - ? path.dirname(options.parentUrl) - : null; - - const dir = () => { - if (reflection.kind === ReflectionKind.Namespace) { - return `${options.directory}/${alias}`; - } - - if (reflection.kind === ReflectionKind.Module) { - return alias; - } - - return options.directory - ? options.directory - : `${slugify(ReflectionKind.singularString(reflection.kind))}.${alias}`; - }; - - const filename = () => { - if ( - [ReflectionKind.Module, ReflectionKind.Namespace].includes( - reflection.kind, - ) && - this.options.getValue('outputFileStrategy') === - OutputFileStrategy.Modules && - !this.childrenIncludeNamespaces(reflection) - ) { - return null; - } - if ( - [ReflectionKind.Module, ReflectionKind.Namespace].includes( - reflection.kind, - ) - ) { - return path.parse(this.options.getValue('entryFileName')).name; - } - return alias; - }; - - return ( - [parentDir, dir(), filename()].filter((part) => Boolean(part)).join('/') + - '.md' - ); - } - - private applyAnchorUrl( - reflection: DeclarationReflection, - container: Reflection, - ) { - if (container.url) { - if (!reflection.kindOf(ReflectionKind.TypeLiteral)) { - const anchorPrefix = this.options.getValue('anchorPrefix'); - const anchorId = this.getAnchorId(reflection); - - if (anchorId) { - if (!this.anchors[container.url]) { - this.anchors[container.url] = []; - } - - this.anchors[container.url].push(anchorId); - - const count = this.anchors[container.url]?.filter( - (id) => id === anchorId, - )?.length; - - const anchorParts = [anchorId]; - - if (count > 1) { - anchorParts.push(`-${count - 1}`); - } - - if (anchorPrefix) { - anchorParts.unshift(`${anchorPrefix}`); - } - - reflection.url = container.url + '#' + anchorParts.join(''); - reflection.anchor = anchorParts.join(''); - } - } - reflection.hasOwnDocument = false; - } - if (reflection.parent) { - reflection.traverse((child) => { - if (child instanceof DeclarationReflection) { - this.applyAnchorUrl(child, container); - } - }); - } - } - - private getAnchorId(reflection: DeclarationReflection) { - const preserveAnchorCasing = this.options.getValue('preserveAnchorCasing'); - - const anchorName = this.getAnchorName(reflection); - - if (anchorName) { - return preserveAnchorCasing ? anchorName : anchorName.toLowerCase(); - } - - return null; - } - - private getAnchorName(reflection: DeclarationReflection) { - const htmlTableAnchors = this.options.getValue('namedAnchors'); - - if (!htmlTableAnchors) { - if ( - (reflection.kindOf(ReflectionKind.Property) && - this.options.getValue('propertiesFormat') === 'table') || - (reflection.kindOf(ReflectionKind.EnumMember) && - this.options.getValue('enumMembersFormat') === 'table') - ) { - return null; - } - } - if (reflection.kindOf(ReflectionKind.Constructor)) { - return 'Constructors'; - } - return reflection.name; - } - - private childrenIncludeNamespaces(reflection: DeclarationReflection) { - return reflection.children?.some( - (child) => child.kind === ReflectionKind.Namespace, - ); - } -} diff --git a/packages/typedoc-plugin-markdown/src/theme/urls.ts b/packages/typedoc-plugin-markdown/src/theme/urls.ts new file mode 100644 index 000000000..49b044f83 --- /dev/null +++ b/packages/typedoc-plugin-markdown/src/theme/urls.ts @@ -0,0 +1,331 @@ +import * as path from 'path'; +import { + DeclarationReflection, + EntryPointStrategy, + ProjectReflection, + Reflection, + ReflectionKind, +} from 'typedoc'; +import { OutputFileStrategy } from '../plugin/options/maps'; +import { getIndexFileName } from './helpers'; +import { MarkdownTheme } from './markdown-theme'; +import { UrlMapping, UrlOption } from './models'; + +/** + * Map the models of the given project to the desired output files. + * Based on TypeDoc DefaultTheme.getUrls() + * + * @param project The project whose urls should be generated. + */ +export default (theme: MarkdownTheme, project: ProjectReflection) => { + const options = theme.application.options; + const optionsForPackages = (theme.application.renderer as any).packageOptions; + const urls: UrlMapping[] = []; + const anchors: Record = {}; + + function getUrls() { + const preserveReadme = + Boolean(project.readme) && !options.getValue('mergeReadme'); + + const preserveModulesPage = + (project?.groups && + Boolean( + project?.groups[0]?.children.find( + (child) => child.name === options.getValue('entryModule'), + ), + )) || + false; + + const isPackages = + options.getValue('entryPointStrategy') === EntryPointStrategy.Packages; + + const entryFileName = options.getValue('entryFileName'); + + const indexFilename = getIndexFileName(project, isPackages); + + project.url = preserveReadme + ? indexFilename + : preserveModulesPage + ? indexFilename + : options.getValue('entryFileName'); + + if (preserveReadme) { + urls.push({ + url: preserveModulesPage ? 'readme_.md' : entryFileName, + model: project, + template: theme.readmeTemplate, + }); + urls.push({ + url: indexFilename, + model: project, + template: theme.projectTemplate, + }); + } else { + urls.push({ + url: preserveModulesPage ? indexFilename : entryFileName, + model: project, + template: theme.projectTemplate, + }); + } + + if (isPackages) { + if (Object.keys(optionsForPackages)?.length === 1) { + buildUrlsFromProject(project); + } else { + project.children?.forEach((projectChild) => { + buildUrlsFromPackage(projectChild); + }); + } + } else { + buildUrlsFromProject(project); + } + + return urls; + } + + function buildUrlsFromProject( + project: ProjectReflection | DeclarationReflection, + parentUrl?: string, + outputFileStrategy?: OutputFileStrategy, + ) { + project.groups?.forEach((projectGroup) => { + projectGroup.children?.forEach((projectGroupChild) => { + buildUrlsFromGroup(projectGroupChild, { + ...(parentUrl && { parentUrl }), + ...(outputFileStrategy && { outputFileStrategy }), + }); + }); + }); + } + + function buildUrlsFromPackage(projectChild: DeclarationReflection) { + const entryFileName = options.getValue('entryFileName'); + const preservePackageReadme = + Boolean(projectChild.readme) && !options.getValue('mergeReadme'); + + const packagesIndex = getIndexFileName(projectChild); + + const packageOptions = optionsForPackages[projectChild.name]; + + const isSet = packageOptions.isSet('outputFileStrategy'); + + const outputFileStrategy = isSet + ? packageOptions.getValue('outputFileStrategy') + : options.getValue('outputFileStrategy'); + + const url = `${projectChild.name}/${ + preservePackageReadme ? packagesIndex : entryFileName + }`; + + if (preservePackageReadme) { + urls.push({ + url: `${path.dirname(url)}/${entryFileName}`, + model: projectChild as any, + template: theme.readmeTemplate, + }); + } + urls.push({ + url: url, + model: projectChild as any, + template: theme.projectTemplate, + }); + + projectChild.url = url; + + buildUrlsFromProject(projectChild, url, outputFileStrategy); + } + + function buildUrlsFromGroup( + reflection: DeclarationReflection, + options: UrlOption, + ) { + const mapping = theme.getTemplateMapping( + reflection.kind, + options.outputFileStrategy, + ); + + if (mapping) { + const directory = options.directory || mapping.directory; + const urlPath = getUrlPath(reflection, { + ...options, + directory, + }); + + const url = getUrl(reflection, urlPath); + urls.push({ + url: url, + model: reflection, + template: mapping.template, + }); + + reflection.url = url; + reflection.hasOwnDocument = true; + + reflection.groups?.forEach((group) => { + group.children.forEach((groupChild) => { + const mapping = theme.getTemplateMapping( + groupChild.kind, + options.outputFileStrategy, + ); + buildUrlsFromGroup(groupChild, { + parentUrl: urlPath, + directory: mapping?.directory || null, + outputFileStrategy: options.outputFileStrategy, + }); + }); + }); + } else if (reflection.parent) { + applyAnchorUrl(reflection, reflection.parent); + } + } + + function getUrl(reflection: DeclarationReflection, urlPath: string) { + if (reflection.name === options.getValue('entryModule')) { + return options.getValue('entryFileName'); + } + if ( + options.getValue('outputFileStrategy') === OutputFileStrategy.Modules && + reflection.name === 'index' + ) { + return urlPath.replace('index.md', 'module.index.md'); + } + return urlPath; + } + + function getUrlPath(reflection: DeclarationReflection, urlOption: UrlOption) { + const alias = reflection.name + .replace(/^_+/, '') + .replace(//, '-'); + + const parentDir = urlOption.parentUrl + ? path.dirname(urlOption.parentUrl) + : null; + + const dir = () => { + if (reflection.kind === ReflectionKind.Namespace) { + return `${urlOption.directory}/${alias}`; + } + + if (reflection.kind === ReflectionKind.Module) { + return alias; + } + + return urlOption.directory + ? urlOption.directory + : `${theme.slugify( + ReflectionKind.singularString(reflection.kind), + )}.${alias}`; + }; + + const filename = () => { + if ( + [ReflectionKind.Module, ReflectionKind.Namespace].includes( + reflection.kind, + ) && + options.getValue('outputFileStrategy') === OutputFileStrategy.Modules && + !childrenIncludeNamespaces(reflection) + ) { + return null; + } + if ( + [ReflectionKind.Module, ReflectionKind.Namespace].includes( + reflection.kind, + ) + ) { + return path.parse(options.getValue('entryFileName')).name; + } + return alias; + }; + + return ( + [parentDir, dir(), filename()].filter((part) => Boolean(part)).join('/') + + '.md' + ); + } + + function applyAnchorUrl( + reflection: DeclarationReflection, + container: Reflection, + ) { + if (container.url) { + if (!reflection.kindOf(ReflectionKind.TypeLiteral)) { + const anchorPrefix = options.getValue('anchorPrefix'); + const anchorId = getAnchorId(reflection); + + if (anchorId) { + if (!anchors[container.url]) { + anchors[container.url] = []; + } + + anchors[container.url].push(anchorId); + + const count = anchors[container.url]?.filter((id) => id === anchorId) + ?.length; + + const anchorParts = [anchorId]; + + if (count > 1) { + anchorParts.push(`-${count - 1}`); + } + + if (anchorPrefix) { + anchorParts.unshift(`${anchorPrefix}`); + } + + reflection.url = container.url + '#' + anchorParts.join(''); + reflection.anchor = anchorParts.join(''); + } + } + reflection.hasOwnDocument = false; + } + if (reflection.parent) { + reflection.traverse((child) => { + if (child instanceof DeclarationReflection) { + applyAnchorUrl(child, container); + } + }); + } + } + + function getAnchorId(reflection: DeclarationReflection) { + const preserveAnchorCasing = options.getValue('preserveAnchorCasing'); + + const anchorName = getAnchorName(reflection); + + if (anchorName) { + return preserveAnchorCasing ? anchorName : anchorName.toLowerCase(); + } + + return null; + } + + function getAnchorName(reflection: DeclarationReflection) { + const htmlTableAnchors = options.getValue('namedAnchors'); + + if (!htmlTableAnchors) { + if ( + (reflection.kindOf(ReflectionKind.Property) && + options.getValue('propertiesFormat') === 'table') || + (reflection.kindOf(ReflectionKind.EnumMember) && + options.getValue('enumMembersFormat') === 'table') + ) { + return null; + } + } + if (reflection.kindOf(ReflectionKind.Constructor)) { + return 'Constructors'; + } + return reflection.name; + } + + function childrenIncludeNamespaces(reflection: DeclarationReflection) { + return reflection.children?.some( + (child) => child.kind === ReflectionKind.Namespace, + ); + } + + return { + getUrls, + }; +}; diff --git a/packages/typedoc-plugin-markdown/typedoc.json b/packages/typedoc-plugin-markdown/typedoc.json new file mode 100644 index 000000000..037bf7aeb --- /dev/null +++ b/packages/typedoc-plugin-markdown/typedoc.json @@ -0,0 +1,30 @@ +{ + "plugin": ["typedoc-plugin-markdown"], + "entryPoints": ["./src/theme/markdown-theme-render-context.ts"], + "readme": "none", + "hidePageHeader": true, + "expandObjects": false, + "useCodeBlocks": true, + "disableSources": true, + "excludePrivate": true, + "sort": ["instance-first", "required-first"], + "externalSymbolLinkMappings": { + "typedoc": { + "Application": "https://typedoc.org/api/classes/Application.html", + "CommentDisplayPart": "https://typedoc.org/api/types/Models.CommentDisplayPart.html", + "DeclarationReflection": "https://typedoc.org/api/classes/Models.DeclarationReflection.html", + "DefaultThemeRenderContext": "https://typedoc.org/api/classes/DefaultThemeRenderContext.html", + "Options": "https://typedoc.org/api/classes/Options.html", + "PageEvent": "https://typedoc.org/api/classes/PageEvent.html", + "ProjectReflection": "https://typedoc.org/api/classes/Models.ProjectReflection.html", + "Renderer": "https://typedoc.org/api/classes/Renderer.html", + "RendererEvent": "https://typedoc.org/api/classes/RendererEvent.html", + "RenderTemplate": "https://typedoc.org/api/types/RenderTemplate.html", + "Reflection": "https://typedoc.org/api/classes/Models.Reflection.html", + "ReflectionGroup": "https://typedoc.org/api/classes/Models.ReflectionGroup.html", + "Theme": "https://typedoc.org/api/classes/Theme.html", + "TypeDocOptions": "https://typedoc.org/api/interfaces/TypeDocOptions.html", + "UrlMapping": "https://typedoc.org/api/classes/UrlMapping.html" + } + } +} diff --git a/packages/typedoc-plugin-remark/test/typedoc.base.json b/packages/typedoc-plugin-remark/test/typedoc.base.json index 6ca02d0f7..c05ec3c1c 100644 --- a/packages/typedoc-plugin-remark/test/typedoc.base.json +++ b/packages/typedoc-plugin-remark/test/typedoc.base.json @@ -5,7 +5,6 @@ "plugin": ["typedoc-plugin-markdown", "typedoc-plugin-remark"], "readme": "none", "parametersFormat": "table", - "githubPages": false, "disableSources": true, "cleanOutputDir": true, "hideGenerator": true, diff --git a/packages/typedoc-vitepress-theme/test/typedoc.base.json b/packages/typedoc-vitepress-theme/test/typedoc.base.json index 5a9935b63..9eaf4cc90 100644 --- a/packages/typedoc-vitepress-theme/test/typedoc.base.json +++ b/packages/typedoc-vitepress-theme/test/typedoc.base.json @@ -4,7 +4,6 @@ "out": "./out", "plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"], "readme": "none", - "githubPages": false, "disableSources": true, "cleanOutputDir": true, "hideGenerator": true, diff --git a/stubs/typedoc.cjs b/stubs/typedoc.cjs index d1ad532fc..92c36d8ab 100644 --- a/stubs/typedoc.cjs +++ b/stubs/typedoc.cjs @@ -1,7 +1,6 @@ const path = require('path'); module.exports = { cleanOutputDir: true, - githubPages: false, sourceLinkTemplate: 'http://source-url', tsconfig: path.join(__dirname, './tsconfig.json'), externalSymbolLinkMappings: {