diff --git a/README.md b/README.md index 25a440407ce..84565523856 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Each library has its own dev script which you can run to watch changes to that p Much of Blueprint's documentation lives inside source code as JSDoc comments in `.tsx` files and KSS markup in `.scss` files. This documentation is extracted and converted into static JSON data using [documentalist](https://github.com/palantir/documentalist/). -If you are updating documentation sources (_not_ the docs UI code which lives in `packages/docs-app` or the docs theme in `packages/docs-theme`), you'll need to run `yarn compile` from `packages/docs-data` to see changes reflected in the application. +If you are updating documentation sources (_not_ the docs UI code which lives in `packages/docs-app` or the docs theme in `packages/docs-theme`), you'll need to run `yarn compile` from `packages/docs-data` to see changes reflected in the application. For simplicity, an alias script `yarn docs-data` exists in the root to minimize directory hopping. ### Updating icons diff --git a/package.json b/package.json index 2385f91d89b..31be945cac4 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "deploy": "gh-pages -d docs -b master", "dev:all": "lerna run dev --parallel --scope '!@blueprintjs/{landing-app,table-dev-app}'", "dev:core": "lerna run dev --parallel --scope '@blueprintjs/{core,docs-app}'", - "dev:docs": "lerna run dev --parallel --scope '@blueprintjs/{docs,docs-app}'", + "dev:docs": "lerna run dev --parallel --scope '@blueprintjs/{docs-app,docs-theme}'", "dev:datetime": "lerna run dev --parallel --scope '@blueprintjs/{core,datetime,docs-app}'", "dev:labs": "lerna run dev --parallel --scope '@blueprintjs/{core,labs,select,docs-app}'", "dev:landing": "lerna run dev --parallel --scope '@blueprintjs/{core,landing-app}'", @@ -26,6 +26,7 @@ "dist:libs": "lerna run dist --parallel --scope '@blueprintjs/{core,datetime,docs-theme,icons,labs,select,table,timezone}'", "dist:apps": "lerna run dist --parallel --scope '@blueprintjs/{docs-app,landing-app,table-dev-app}'", "dist:docs": "run-s clean-docs copy-docs-app copy-landing-app", + "docs-data": "lerna run compile --scope '@blueprintjs/docs-data'", "lint": "lerna run --parallel lint", "lint-fix": "lerna run --parallel lint-fix", "test": "lerna run --parallel test", diff --git a/packages/docs-app/src/components/blueprintDocs.tsx b/packages/docs-app/src/components/blueprintDocs.tsx index c39bed43b69..068c3734ebb 100644 --- a/packages/docs-app/src/components/blueprintDocs.tsx +++ b/packages/docs-app/src/components/blueprintDocs.tsx @@ -7,6 +7,7 @@ import { Menu, MenuItem, Popover, Position, setHotkeysDialogProps } from "@blueprintjs/core"; import { IPackageInfo } from "@blueprintjs/docs-data"; import { Documentation, IDocumentationProps } from "@blueprintjs/docs-theme"; +import { ITsDocBase } from "documentalist/dist/client"; import * as React from "react"; import { NavbarActions } from "./navbarActions"; @@ -59,6 +60,7 @@ export class BlueprintDocs extends React.Component ); @@ -89,6 +91,10 @@ export class BlueprintDocs extends React.Component { diff --git a/packages/docs-app/src/index.tsx b/packages/docs-app/src/index.tsx index 70902d1921c..a201068c06c 100644 --- a/packages/docs-app/src/index.tsx +++ b/packages/docs-app/src/index.tsx @@ -29,7 +29,7 @@ const reactDocs = new ReactDocsTagRenderer(ReactDocs as any); const reactExample = new ReactExampleTagRenderer(reactExamples); const tagRenderers = { - ...createDefaultRenderers(docs), + ...createDefaultRenderers(), reactDocs: reactDocs.render, reactExample: reactExample.render, }; diff --git a/packages/docs-data/compile-docs-data b/packages/docs-data/compile-docs-data index 8f1c4aa7257..3313dc64420 100755 --- a/packages/docs-data/compile-docs-data +++ b/packages/docs-data/compile-docs-data @@ -55,10 +55,11 @@ function generateDocumentalistData() { new dm.TypescriptPlugin({ excludeNames: [/I.+State$/], excludePaths: ["node_modules/", "-app/", "test-commons/"], + tsconfigPath: path.resolve(__dirname, "../../config/tsconfig.base.json"), }), ) .use(".scss", new dm.KssPlugin()) - .documentGlobs("../*/src/**/*.{ts,tsx,scss,md}") + .documentGlobs("../*/src/**/*.{scss,md}", "../*/src/index.{ts,tsx}") .then(docs => JSON.stringify(docs, transformDocumentalistData, 2)) .then(content => fs.writeFileSync(path.join(GENERATED_SRC_DIR, DOCS_DATA_FILENAME), content)); } diff --git a/packages/docs-data/package.json b/packages/docs-data/package.json index 1d46d1be59e..ee43bbd9852 100644 --- a/packages/docs-data/package.json +++ b/packages/docs-data/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@blueprintjs/docs-theme": "^2.0.0-beta.1", - "documentalist": "^1.0.0-beta.1", + "documentalist": "^1.0.0-beta.4", "glob": "^7.1.2", "highlights": "^3.1.1", "marked": "^0.3.6", diff --git a/packages/docs-theme/package.json b/packages/docs-theme/package.json index 5549e2236bf..3cf9c2f2b91 100644 --- a/packages/docs-theme/package.json +++ b/packages/docs-theme/package.json @@ -26,7 +26,7 @@ "dependencies": { "@blueprintjs/core": "^2.0.0-beta.1", "classnames": "^2.2", - "documentalist": "^1.0.0-beta.1", + "documentalist": "^1.0.0-beta.4", "fuzzaldrin-plus": "^0.5.0", "tslib": "^1.5.0" }, diff --git a/packages/docs-theme/src/common/context.ts b/packages/docs-theme/src/common/context.ts new file mode 100644 index 00000000000..3be4922367f --- /dev/null +++ b/packages/docs-theme/src/common/context.ts @@ -0,0 +1,77 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the terms of the LICENSE file distributed with this project. + */ + +import { Utils } from "@blueprintjs/core"; +import { + IBlock, + IKssPluginData, + IMarkdownPluginData, + ITsDocBase, + ITypescriptPluginData, +} from "documentalist/dist/client"; + +/** This docs theme requires Markdown data and optionally supports Typescript and KSS data. */ +export type IDocsData = IMarkdownPluginData & (ITypescriptPluginData | {}) & (IKssPluginData | {}); + +export function hasTypescriptData(docs: IDocsData): docs is IMarkdownPluginData & ITypescriptPluginData { + return docs != null && (docs as ITypescriptPluginData).typescript != null; +} + +export function hasKssData(docs: IDocsData): docs is IMarkdownPluginData & IKssPluginData { + return docs != null && (docs as IKssPluginData).css != null; +} + +/** + * Use React context to transparently provide helpful functions to children. + * This is basically the pauper's Redux store connector: some central state from the root + * `Documentation` component is exposed to its children so those in the know can speak + * directly to their parent. + */ +export interface IDocumentationContext { + /** + * Get the Documentalist data. + * Use the `hasTypescriptData` and `hasKssData` typeguards before accessing those plugins' data. + */ + getDocsData(): IDocsData; + + /** Render a block of Documentalist documentation to a React node. */ + renderBlock(block: IBlock): React.ReactNode; + + /** Render a Documentalist Typescript type string to a React node. */ + renderType(type: string): React.ReactNode; + + /** Render the text of a "View source" link. */ + renderViewSourceLinkText(entry: ITsDocBase): React.ReactNode; +} + +/** + * To enable context access in a React component, assign `static contextTypes` and declare `context` type: + * + * ```tsx + * export class ContextComponent extends React.PureComponent { + * public static contextTypes = DocumentationContextTypes; + * public context: IDocumentationContext; + * + * public render() { + * return this.context.renderBlock(this.props.block); + * } + * } + * ``` + */ +export const DocumentationContextTypes: React.ValidationMap = { + getDocsData: assertFunctionProp, + renderBlock: assertFunctionProp, + renderType: assertFunctionProp, + renderViewSourceLinkText: assertFunctionProp, +}; + +// simple alternative to prop-types dependency +function assertFunctionProp(obj: T, key: keyof T) { + if (obj[key] != null && Utils.isFunction(obj[key])) { + return undefined; + } + return new Error(`[Blueprint] Documentation context ${key} must be function.`); +} diff --git a/packages/docs-theme/src/components/block.tsx b/packages/docs-theme/src/components/block.tsx index aee6ffb3709..767061cac2f 100644 --- a/packages/docs-theme/src/components/block.tsx +++ b/packages/docs-theme/src/components/block.tsx @@ -4,12 +4,12 @@ * Licensed under the terms of the LICENSE file distributed with this project. */ -import { IBlock, IPageData } from "documentalist/dist/client"; +import { IBlock } from "documentalist/dist/client"; import * as React from "react"; -import { ITagRendererMap, TagElement } from "../tags"; +import { ITagRendererMap } from "../tags"; -export function renderBlock(block: IBlock | undefined, tagRenderers: ITagRendererMap, page?: IPageData): TagElement[] { +export function renderBlock(block: IBlock | undefined, tagRenderers: ITagRendererMap): JSX.Element[] { if (block === undefined) { return []; } @@ -22,11 +22,11 @@ export function renderBlock(block: IBlock | undefined, tagRenderers: ITagRendere if (renderer === undefined) { throw new Error(`Unknown @tag: ${node.tag}`); } - return renderer(node, i, tagRenderers, page); + return React.createElement(renderer, { ...node, key: i }); } catch (ex) { console.error(ex.message); return ( -

+

{ex.message}

); diff --git a/packages/docs-theme/src/components/documentation.tsx b/packages/docs-theme/src/components/documentation.tsx index af440c0261b..c9c27ed139c 100644 --- a/packages/docs-theme/src/components/documentation.tsx +++ b/packages/docs-theme/src/components/documentation.tsx @@ -5,13 +5,15 @@ */ import * as classNames from "classnames"; -import { IMarkdownPluginData, isPageNode } from "documentalist/dist/client"; +import { isPageNode, ITsDocBase, linkify } from "documentalist/dist/client"; import * as React from "react"; import { FocusStyleManager, Hotkey, Hotkeys, HotkeysTarget, IProps, Utils } from "@blueprintjs/core"; +import { DocumentationContextTypes, hasTypescriptData, IDocsData, IDocumentationContext } from "../common/context"; import { eachLayoutNode } from "../common/utils"; -import { TagRenderer } from "../tags"; +import { ITagRendererMap } from "../tags"; +import { renderBlock } from "./block"; import { Navigator } from "./navigator"; import { NavMenu } from "./navMenu"; import { Page } from "./page"; @@ -24,9 +26,9 @@ export interface IDocumentationProps extends IProps { /** * All the docs data from Documentalist. - * Must include at least `{ nav, pages }` from the MarkdownPlugin. + * This theme requires the Markdown plugin, and optionally supports Typescript and KSS data. */ - docs: IMarkdownPluginData; + docs: IDocsData; /** * Callback invoked whenever the component props or state change (specifically, @@ -35,8 +37,15 @@ export interface IDocumentationProps extends IProps { */ onComponentUpdate?: (pageId: string) => void; + /** + * Callback invoked to render "View source" links in Typescript interfaces. + * The `href` of the link will be `entry.sourceUrl`. + * @default "View source" + */ + renderViewSourceLinkText?: (entry: ITsDocBase) => React.ReactNode; + /** Tag renderer functions. Unknown tags will log console errors. */ - tagRenderers: { [tag: string]: TagRenderer }; + tagRenderers: ITagRendererMap; /** * Elements to render on the left side of the navbar, typically logo and title. @@ -59,6 +68,8 @@ export interface IDocumentationState { @HotkeysTarget export class Documentation extends React.PureComponent { + public static childContextTypes = DocumentationContextTypes; + public static defaultProps = { navbarLeft: "Documentation", }; @@ -88,6 +99,20 @@ export class Documentation extends React.PureComponent docs, + renderBlock: block => renderBlock(block, this.props.tagRenderers), + renderType: hasTypescriptData(docs) + ? type => linkify(type, docs.typescript, name => {name}) + : type => type, + renderViewSourceLinkText: Utils.isFunction(renderViewSourceLinkText) + ? renderViewSourceLinkText + : () => "View source", + }; + } + public render() { const { activePageId, activeSectionId } = this.state; const { nav, pages } = this.props.docs; diff --git a/packages/docs-theme/src/components/interfaceTable.tsx b/packages/docs-theme/src/components/interfaceTable.tsx deleted file mode 100644 index c5a29decc8e..00000000000 --- a/packages/docs-theme/src/components/interfaceTable.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2017 Palantir Technologies, Inc. All rights reserved. - * - * Licensed under the terms of the LICENSE file distributed with this project. - */ - -import { Classes, Intent, Tag } from "@blueprintjs/core"; -import * as classNames from "classnames"; -import { isTsProperty, ITsClass, ITsInterface, ITsMethod, ITsProperty } from "documentalist/dist/client"; -import * as React from "react"; -import { ITagRendererMap } from "../tags"; -import { renderBlock } from "./block"; - -// HACKHACK support `code` blocks until we get real markdown parsing in ts-quick-docs -function dirtyMarkdown(text: string) { - return { - __html: text - .replace("<", "<") - .replace(/```([^`]+)```/g, (_, code) => `
${code}
`) - .replace(/`([^`]+)`/g, (_, code) => `${code}`), - }; -} - -function propTag(intent: Intent, title: string, ...children: React.ReactNode[]) { - return ( - - {title} - {children} - - ); -} - -function renderPropType(prop: ITsProperty | ITsMethod) { - if (isTsProperty(prop)) { - const formattedType = prop.type.replace(/\b(JSX\.)?Element\b/, "JSX.Element"); - return ( - - {formattedType} - {prop.defaultValue} - - ); - } else { - return ( - - {prop.signatures[0].type} - - ); - } -} - -function renderPropRow(prop: ITsProperty | ITsMethod) { - const { flags: { isDeprecated, isExternal, isOptional }, inheritedFrom, name } = prop; - const isDeprecatedBoolean = isDeprecated === true || typeof isDeprecated === "string"; - const classes = classNames("docs-prop-name", { - "docs-prop-is-deprecated": isDeprecatedBoolean, - "docs-prop-is-internal": !isExternal, - "docs-prop-is-required": !isOptional, - }); - - const tags: JSX.Element[] = []; - if (!isOptional) { - tags.push(propTag(Intent.SUCCESS, "Required")); - } - if (isDeprecatedBoolean) { - const maybeMessage = - typeof isDeprecated === "string" ? ( - - ) : ( - "" - ); - tags.push(propTag(Intent.DANGER, "Deprecated", maybeMessage)); - } - if (inheritedFrom != null) { - tags.push(propTag(Intent.NONE, "Inherited from ", {inheritedFrom})); - } - - const documentation = isTsProperty(prop) ? prop.documentation : prop.signatures[0].documentation; - // TODO: this ignores tags in prop docs, but that's kind of OK cuz they all get processed - // into prop.tags by the TS compiler. - const html = - documentation && documentation.contents.reduce((a, b) => (typeof b === "string" ? a + b : a), ""); - - return ( - - - {name} - - - {renderPropType(prop)} -
-

{tags}

- - - ); -} - -export interface IInterfaceTableProps { - iface: ITsClass | ITsInterface; - tagRenderers: ITagRendererMap; -} - -export const InterfaceTable: React.SFC = ({ iface, tagRenderers }) => { - const propRows = [...iface.properties, ...iface.methods] - .sort((a, b) => a.name.localeCompare(b.name)) - .map(renderPropRow); - return ( -
-
{iface.name}
- {renderBlock(iface.documentation, tagRenderers)} - - - - - - - - {propRows} -
PropDescription
-
- ); -}; -InterfaceTable.displayName = "Docs.InterfaceTable"; diff --git a/packages/docs-theme/src/components/page.tsx b/packages/docs-theme/src/components/page.tsx index 1ef8c9334a0..8b41574fcf1 100644 --- a/packages/docs-theme/src/components/page.tsx +++ b/packages/docs-theme/src/components/page.tsx @@ -16,7 +16,7 @@ export interface IPageProps { } export const Page: React.SFC = ({ tagRenderers, page }) => { - const pageContents = renderBlock(page, tagRenderers, page); + const pageContents = renderBlock(page, tagRenderers); return (
{pageContents} diff --git a/packages/docs-theme/src/components/typescript/apiHeader.tsx b/packages/docs-theme/src/components/typescript/apiHeader.tsx new file mode 100644 index 00000000000..7ff44034511 --- /dev/null +++ b/packages/docs-theme/src/components/typescript/apiHeader.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the terms of the LICENSE file distributed with this project. + */ + +import { isTsClass, isTsInterface, ITsDocBase } from "documentalist/dist/client"; +import * as React from "react"; +import { DocumentationContextTypes, IDocumentationContext } from "../../common/context"; + +export class ApiHeader extends React.PureComponent { + public static contextTypes = DocumentationContextTypes; + public static displayName = "Docs.ApiHeader"; + + public context: IDocumentationContext; + + public render() { + return ( +
+
+ {this.props.kind} {this.props.name} {this.renderInheritance()} +
+ + + {this.context.renderViewSourceLinkText(this.props)} + + + {this.props.children} +
+ ); + } + + private renderInheritance() { + if (isTsClass(this.props) || isTsInterface(this.props)) { + const extendsTypes = maybeJoinArray("extends", this.props.extends); + const implementsTypes = maybeJoinArray("implements", this.props.implements); + return this.context.renderType(`${extendsTypes} ${implementsTypes}`); + } + return ""; + } +} + +function maybeJoinArray(title: string, array: string[] | undefined): string { + if (array == null || array.length === 0) { + return ""; + } + return `${title} ${array.join(", ")}`; +} diff --git a/packages/docs-theme/src/components/typescript/deprecatedTag.tsx b/packages/docs-theme/src/components/typescript/deprecatedTag.tsx new file mode 100644 index 00000000000..538393ed5f8 --- /dev/null +++ b/packages/docs-theme/src/components/typescript/deprecatedTag.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the terms of the LICENSE file distributed with this project. + */ + +import { Classes, Intent, Tag } from "@blueprintjs/core"; +import * as React from "react"; + +export const DeprecatedTag: React.SFC<{ isDeprecated: boolean | string | undefined }> = ({ isDeprecated }) => { + if (isDeprecated === true || typeof isDeprecated === "string") { + return ( + + {typeof isDeprecated === "string" ? ( + + ) : ( + "Deprecated" + )} + + ); + } + return null; +}; +DeprecatedTag.displayName = "Docs.DeprecatedTag"; + +/** + * Minimal markdown renderer that supports only backtick `code` elements and triple-backtick `pre` elements. + * Does not provide any syntax highlighting. + */ +function markdownCode(text: string) { + return { + __html: text + .replace("<", "<") + .replace(/```([^`]+)```/g, (_, code) => `
${code}
`) + .replace(/`([^`]+)`/g, (_, code) => `${code}`), + }; +} diff --git a/packages/docs-theme/src/components/typescript/enumTable.tsx b/packages/docs-theme/src/components/typescript/enumTable.tsx new file mode 100644 index 00000000000..c449a9e06ba --- /dev/null +++ b/packages/docs-theme/src/components/typescript/enumTable.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the terms of the LICENSE file distributed with this project. + */ + +import * as classNames from "classnames"; +import { ITsEnum, ITsEnumMember } from "documentalist/dist/client"; +import * as React from "react"; +import { DocumentationContextTypes, IDocumentationContext } from "../../common/context"; +import { ApiHeader } from "./apiHeader"; +import { DeprecatedTag } from "./deprecatedTag"; + +export type Renderer = (props: T) => React.ReactNode; + +export interface IEnumTableProps { + data: ITsEnum; +} + +export class EnumTable extends React.PureComponent { + public static contextTypes = DocumentationContextTypes; + public static displayName = "Docs.EnumTable"; + + public context: IDocumentationContext; + + public render() { + const { data } = this.props; + const { renderBlock } = this.context; + return ( +
+ + {renderBlock(data.documentation)} + + + + + + + + {data.members.map(this.renderPropRow)} +
MembersDescription
+
+ ); + } + + private renderPropRow = (entry: ITsEnumMember) => { + const { renderBlock } = this.context; + const { flags: { isDeprecated, isExternal, isOptional }, name } = entry; + + const classes = classNames("docs-prop-name", { + "docs-prop-is-deprecated": !!isDeprecated, + "docs-prop-is-internal": !isExternal, + "docs-prop-is-required": !isOptional, + }); + + return ( + + + {name} + + + + {entry.name} + {entry.defaultValue} + +
{renderBlock(entry.documentation)}
+

{this.renderTags(entry)}

+ + + ); + }; + + private renderTags(entry: ITsEnumMember) { + const { flags: { isDeprecated } } = entry; + return ; + } +} diff --git a/packages/docs-theme/src/components/typescript/interfaceTable.tsx b/packages/docs-theme/src/components/typescript/interfaceTable.tsx new file mode 100644 index 00000000000..a70783121c7 --- /dev/null +++ b/packages/docs-theme/src/components/typescript/interfaceTable.tsx @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the terms of the LICENSE file distributed with this project. + */ + +import { Classes, Intent, Tag } from "@blueprintjs/core"; +import * as classNames from "classnames"; +import { isTsProperty, ITsClass, ITsInterface, ITsMethod, ITsProperty } from "documentalist/dist/client"; +import * as React from "react"; +import { DocumentationContextTypes, IDocumentationContext } from "../../common/context"; +import { ApiHeader } from "./apiHeader"; +import { DeprecatedTag } from "./deprecatedTag"; + +export type Renderer = (props: T) => React.ReactNode; + +export interface IInterfaceTableProps { + data: ITsClass | ITsInterface; + title: string; +} + +export class InterfaceTable extends React.PureComponent { + public static contextTypes = DocumentationContextTypes; + public static displayName = "Docs.InterfaceTable"; + + public context: IDocumentationContext; + + public render() { + const { data, title } = this.props; + const { renderBlock } = this.context; + const propRows = [...data.properties, ...data.methods] + .sort((a, b) => a.name.localeCompare(b.name)) + .map(this.renderPropRow); + return ( +
+ + {renderBlock(data.documentation)} + + + + + + + + {propRows} +
{title}Description
+
+ ); + } + + private renderPropRow = (entry: ITsProperty | ITsMethod) => { + const { renderBlock, renderType } = this.context; + const { flags: { isDeprecated, isExternal, isOptional }, name } = entry; + const { documentation } = isTsProperty(entry) ? entry : entry.signatures[0]; + + const classes = classNames("docs-prop-name", { + "docs-prop-is-deprecated": isDeprecated === true || typeof isDeprecated === "string", + "docs-prop-is-internal": !isExternal, + "docs-prop-is-required": !isOptional, + }); + + const typeInfo = isTsProperty(entry) ? ( + <> + {renderType(entry.type)} + {entry.defaultValue} + + ) : ( + <> + {renderType(entry.signatures[0].type)} + + ); + + return ( + + + {name} + + + {typeInfo} +
{renderBlock(documentation)}
+

{this.renderTags(entry)}

+ + + ); + }; + + private renderTags(entry: ITsProperty | ITsMethod) { + const { renderType } = this.context; + const { flags: { isDeprecated, isOptional }, inheritedFrom } = entry; + return ( + <> + {!isOptional && } + + {inheritedFrom && ( + + Inherited from {renderType(inheritedFrom)} + + )} + + ); + } +} diff --git a/packages/docs-theme/src/components/typescript/typeAliasTable.tsx b/packages/docs-theme/src/components/typescript/typeAliasTable.tsx new file mode 100644 index 00000000000..25f12a97554 --- /dev/null +++ b/packages/docs-theme/src/components/typescript/typeAliasTable.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the terms of the LICENSE file distributed with this project. + */ + +import { ITsTypeAlias } from "documentalist/dist/client"; +import * as React from "react"; +import { DocumentationContextTypes, IDocumentationContext } from "../../common/context"; +import { ApiHeader } from "./apiHeader"; + +export interface ITypeAliasTableProps { + data: ITsTypeAlias; +} + +export class TypeAliasTable extends React.PureComponent { + public static contextTypes = DocumentationContextTypes; + public static displayName = "Docs.TypeAliasTable"; + + public context: IDocumentationContext; + + public render() { + const { data } = this.props; + const { renderBlock, renderType } = this.context; + return ( +
+ +

= {renderType(data.type)}

+ {renderBlock(data.documentation)} +
+ ); + } +} diff --git a/packages/docs-theme/src/styles/_props.scss b/packages/docs-theme/src/styles/_props.scss index fcf4dcae4d8..e4f31a9fd10 100644 --- a/packages/docs-theme/src/styles/_props.scss +++ b/packages/docs-theme/src/styles/_props.scss @@ -70,7 +70,10 @@ } } -.docs-interface-name { +.docs-interface-header { + display: flex; + align-items: flex-end; + justify-content: space-between; margin-bottom: $pt-grid-size; border-bottom: 1px solid $pt-divider-black; padding: $pt-grid-size 0; diff --git a/packages/docs-theme/src/tags/css.tsx b/packages/docs-theme/src/tags/css.tsx index 06f6637ccae..3318b5067a5 100644 --- a/packages/docs-theme/src/tags/css.tsx +++ b/packages/docs-theme/src/tags/css.tsx @@ -4,55 +4,57 @@ * Licensed under the terms of the LICENSE file distributed with this project. */ -import { IKssExample, IKssModifier, IKssPluginData } from "documentalist/dist/client"; +import { IKssPluginData, ITag } from "documentalist/dist/client"; import * as React from "react"; +import { DocumentationContextTypes, IDocumentationContext } from "../common/context"; import { ModifierTable } from "../components/modifierTable"; -import { TagRenderer } from "./"; -const MODIFIER_PLACEHOLDER = /\{\{([\.\:]?)modifier\}\}/g; -const DEFAULT_MODIFIER: IKssModifier = { - documentation: "Default", - name: "default", -}; +export class CssExample extends React.PureComponent { + public static contextTypes = DocumentationContextTypes; + public static displayName = "Docs.CssExample"; -const CssExample: React.SFC = ({ markup, markupHtml, modifiers, reference }) => ( -
- {modifiers.length > 0 ? : undefined} -
- {renderMarkupForModifier(markup, DEFAULT_MODIFIER)} - {modifiers.map(mod => renderMarkupForModifier(markup, mod))} -
-
-
-); + public context: IDocumentationContext; -function renderMarkupForModifier(markup: string, modifier: IKssModifier) { - const { name } = modifier; - const html = markup.replace(MODIFIER_PLACEHOLDER, (_, prefix) => { - if (prefix && name.charAt(0) === prefix) { - return name.slice(1); - } else if (!prefix) { - return name; - } else { - return ""; + public render() { + const { value } = this.props; + const { css } = this.context.getDocsData() as IKssPluginData; + if (css == null || css[value] == null) { + return null; } - }); - return ( -
- {modifier.name} -
-
- ); -} + const { markup, markupHtml, modifiers, reference } = css[value]; + return ( +
+ {modifiers.length > 0 ? : undefined} +
+ {this.renderMarkupExample(markup)} + {modifiers.map(mod => this.renderMarkupExample(markup, mod.name))} +
+
+
+ ); + } -export class CssTagRenderer { - constructor(private docs: IKssPluginData) {} + private renderMarkupExample(markup: string, modifierName = "default") { + return ( +
+ {modifierName} + {this.renderMarkupForModifier(markup, modifierName)} +
+ ); + } - public render: TagRenderer = ({ value: reference }, key) => { - const example = this.docs.css[reference]; - if (example === undefined || example.reference === undefined) { - throw new Error(`Unknown @css reference: ${reference}`); - } - return ; - }; + private renderMarkupForModifier(markup: string, modifierName: string) { + const html = markup.replace(MODIFIER_PLACEHOLDER_REGEXP, (_, prefix: string) => { + if (prefix && modifierName.charAt(0) === prefix) { + return modifierName.slice(1); + } else if (!prefix) { + return modifierName; + } else { + return ""; + } + }); + return
; + } } + +const MODIFIER_PLACEHOLDER_REGEXP = /\{\{([.:]?)modifier\}\}/g; diff --git a/packages/docs-theme/src/tags/defaults.ts b/packages/docs-theme/src/tags/defaults.ts index 1fede92c5e1..f2d71ca029d 100644 --- a/packages/docs-theme/src/tags/defaults.ts +++ b/packages/docs-theme/src/tags/defaults.ts @@ -4,22 +4,19 @@ * Licensed under the terms of the LICENSE file distributed with this project. */ +import * as React from "react"; import * as tags from "./"; -import { IKssPluginData, IMarkdownPluginData, ITypescriptPluginData } from "documentalist/dist/client"; +import { IKssPluginData, IMarkdownPluginData, ITag, ITypescriptPluginData } from "documentalist/dist/client"; export interface IDocsData extends IKssPluginData, IMarkdownPluginData, ITypescriptPluginData {} -export function createDefaultRenderers(docs: IDocsData) { - const css = new tags.CssTagRenderer(docs); - const heading = new tags.HeadingTagRenderer(); - const iface = new tags.InterfaceTagRenderer(docs); - const page = new tags.PageTagRenderer(); - +export function createDefaultRenderers(): Record> { return { - css: css.render, - heading: heading.render, - interface: iface.render, - page: page.render, + css: tags.CssExample, + heading: tags.Heading, + interface: tags.TypescriptExample, + page: () => null, + // TODO: @see }; } diff --git a/packages/docs-theme/src/tags/heading.tsx b/packages/docs-theme/src/tags/heading.tsx index d26e6007a83..d3cce3b98ba 100644 --- a/packages/docs-theme/src/tags/heading.tsx +++ b/packages/docs-theme/src/tags/heading.tsx @@ -6,9 +6,8 @@ import { IHeadingTag } from "documentalist/dist/client"; import * as React from "react"; -import { TagRenderer } from "./"; -const Heading: React.SFC = ({ level, route, value }) => +export const Heading: React.SFC = ({ level, route, value }) => // use createElement so we can dynamically choose tag based on depth React.createElement( `h${level}`, @@ -20,7 +19,3 @@ const Heading: React.SFC = ({ level, route, value }) => value, ); Heading.displayName = "Docs.Heading"; - -export class HeadingTagRenderer { - public render: TagRenderer = (heading: IHeadingTag, key) => ; -} diff --git a/packages/docs-theme/src/tags/index.ts b/packages/docs-theme/src/tags/index.ts index 75a691b3f84..ca4d7f1b862 100644 --- a/packages/docs-theme/src/tags/index.ts +++ b/packages/docs-theme/src/tags/index.ts @@ -4,20 +4,15 @@ * Licensed under the terms of the LICENSE file distributed with this project. */ -import { IPageData, ITag } from "documentalist/dist/client"; +import { ITag } from "documentalist/dist/client"; export interface ITagRendererMap { - [tagName: string]: TagRenderer; + [tagName: string]: React.ComponentType | undefined; } -export type TagElement = JSX.Element | undefined; - -export type TagRenderer = (tag: ITag, key: React.Key, tagRenderers: ITagRendererMap, page?: IPageData) => TagElement; - export * from "./css"; export * from "./defaults"; export * from "./heading"; -export * from "./interface"; -export * from "./page"; export * from "./reactDocs"; export * from "./reactExample"; +export * from "./typescript"; diff --git a/packages/docs-theme/src/tags/interface.tsx b/packages/docs-theme/src/tags/interface.tsx deleted file mode 100644 index 2735d0d194a..00000000000 --- a/packages/docs-theme/src/tags/interface.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2017 Palantir Technologies, Inc. All rights reserved. - * - * Licensed under the terms of the LICENSE file distributed with this project. - */ - -import { isTsClass, isTsInterface, ITypescriptPluginData } from "documentalist/dist/client"; -import * as React from "react"; -import { InterfaceTable } from "../components/interfaceTable"; -import { TagRenderer } from "./"; - -export class InterfaceTagRenderer { - constructor(private docs: ITypescriptPluginData) {} - - public render: TagRenderer = ({ value: name }, key, tagRenderers) => { - const iface = this.docs.typescript[name]; - if (iface === undefined) { - throw new Error(`Unknown @interface ${name}`); - } - if (isTsClass(iface) || isTsInterface(iface)) { - return ; - } - throw new Error(`@interface cannot render ${iface.kind}`); - }; -} diff --git a/packages/docs-theme/src/tags/page.tsx b/packages/docs-theme/src/tags/page.tsx deleted file mode 100644 index 0e2c4d1ed67..00000000000 --- a/packages/docs-theme/src/tags/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright 2017 Palantir Technologies, Inc. All rights reserved. - * - * Licensed under the terms of the LICENSE file distributed with this project. - */ - -import { TagRenderer } from "./"; - -export class PageTagRenderer { - public render: TagRenderer = () => undefined; -} diff --git a/packages/docs-theme/src/tags/reactDocs.tsx b/packages/docs-theme/src/tags/reactDocs.tsx index 4e169d219d0..2ad603950b6 100644 --- a/packages/docs-theme/src/tags/reactDocs.tsx +++ b/packages/docs-theme/src/tags/reactDocs.tsx @@ -4,8 +4,8 @@ * Licensed under the terms of the LICENSE file distributed with this project. */ +import { ITag } from "documentalist/dist/client"; import * as React from "react"; -import { TagRenderer } from "./"; export interface IDocsMap { [name: string]: React.ComponentClass<{}>; @@ -19,15 +19,15 @@ export class ReactDocsTagRenderer { * it to an actual component class in the given map, or in the default map which contains * valid docs components from this package. Provide a custom map to inject your own components. */ - public render: TagRenderer = ({ value: componentName }, key) => { + public render: React.SFC = ({ value: componentName }) => { if (componentName == null) { - return undefined; + return null; } const docsComponent = this.docs[componentName]; if (docsComponent == null) { throw new Error(`Unknown @reactDocs component: ${componentName}`); } - return React.createElement(docsComponent, { key }); + return React.createElement(docsComponent); }; } diff --git a/packages/docs-theme/src/tags/reactExample.tsx b/packages/docs-theme/src/tags/reactExample.tsx index edbbcc7e78b..7d0628b43a7 100644 --- a/packages/docs-theme/src/tags/reactExample.tsx +++ b/packages/docs-theme/src/tags/reactExample.tsx @@ -4,8 +4,8 @@ * Licensed under the terms of the LICENSE file distributed with this project. */ +import { ITag } from "documentalist/dist/client"; import * as React from "react"; -import { TagRenderer } from "./"; export interface IExample { sourceUrl: string; @@ -41,15 +41,15 @@ export class ReactExampleTagRenderer { * it to an actual example component exported by one of the packages. Also returns * the URL of the source code on GitHub. */ - public render: TagRenderer = ({ value: exampleName }, key) => { + public render: React.SFC = ({ value: exampleName }) => { if (exampleName == null) { - return undefined; + return null; } const example = this.examples[exampleName]; if (example == null) { throw new Error(`Unknown @example component: ${exampleName}`); } - return ; + return ; }; } diff --git a/packages/docs-theme/src/tags/typescript.tsx b/packages/docs-theme/src/tags/typescript.tsx new file mode 100644 index 00000000000..995d1a951e1 --- /dev/null +++ b/packages/docs-theme/src/tags/typescript.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the terms of the LICENSE file distributed with this project. + */ + +import { + isTsClass, + isTsEnum, + isTsInterface, + isTsTypeAlias, + ITag, + ITypescriptPluginData, +} from "documentalist/dist/client"; +import * as React from "react"; +import { DocumentationContextTypes, IDocumentationContext } from "../common/context"; +import { EnumTable } from "../components/typescript/enumTable"; +import { InterfaceTable } from "../components/typescript/interfaceTable"; +import { TypeAliasTable } from "../components/typescript/typeAliasTable"; + +export const TypescriptExample: React.SFC = ({ value }, { getDocsData }: IDocumentationContext) => { + const { typescript } = getDocsData() as ITypescriptPluginData; + if (typescript == null || typescript[value] == null) { + return null; + } + const member = typescript[value]; + if (member === undefined) { + throw new Error(`Unknown @interface ${name}`); + } else if (isTsClass(member) || isTsInterface(member)) { + return ; + } else if (isTsEnum(member)) { + return ; + } else if (isTsTypeAlias(member)) { + return ; + } else { + throw new Error(`"@interface ${name}": unknown member kind "${(member as any).kind}"`); + } +}; +TypescriptExample.contextTypes = DocumentationContextTypes; +TypescriptExample.displayName = "Docs.TypescriptExample"; diff --git a/yarn.lock b/yarn.lock index 664a3fd6f5c..7b9a5100cec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2079,9 +2079,9 @@ dns-txt@^2.0.2: dependencies: buffer-indexof "^1.0.0" -documentalist@^1.0.0-beta.1: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/documentalist/-/documentalist-1.0.0-beta.2.tgz#0da69cb1e3c467146f01f02a61079140e68baf0f" +documentalist@^1.0.0-beta.4: + version "1.0.0-beta.4" + resolved "https://registry.yarnpkg.com/documentalist/-/documentalist-1.0.0-beta.4.tgz#3b2204d7fc74b38daa2664667155ab105fb8d791" dependencies: "@types/kss" "^3.0.0" glob "^7.1.1"