mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-10 16:43:31 +01:00
feat(website): show package members in a sidebar (#8245)
* feat(website): show package members in a sidebar * fix: put response instead of loader * Apply suggestions from code review Co-authored-by: Noel <buechler.noel@outlook.com> * chore: make requested changes * refactor: make only package list scrollable * feat: make sidebar mobile responsive * fix: breakpoints for sidebar Co-authored-by: Noel <buechler.noel@outlook.com>
This commit is contained in:
@@ -8,14 +8,14 @@ import {
|
||||
import { DocItem } from './DocItem';
|
||||
import { DocMethod } from './DocMethod';
|
||||
import { DocProperty } from './DocProperty';
|
||||
import { type TokenDocumentation, genToken, TypeParameterData, generateTypeParamData } from '~/util/parse.server';
|
||||
import { TypeParameterMixin } from './TypeParameterMixin';
|
||||
import { type TokenDocumentation, genToken } from '~/util/parse.server';
|
||||
|
||||
export class DocClass extends DocItem<ApiClass> {
|
||||
export class DocClass extends TypeParameterMixin(DocItem<ApiClass>) {
|
||||
public readonly extendsTokens: TokenDocumentation[] | null;
|
||||
public readonly implementsTokens: TokenDocumentation[][];
|
||||
public readonly methods: DocMethod[] = [];
|
||||
public readonly properties: DocProperty[] = [];
|
||||
public readonly typeParameters: TypeParameterData[] = [];
|
||||
|
||||
public constructor(model: ApiModel, item: ApiClass) {
|
||||
super(model, item);
|
||||
@@ -29,8 +29,6 @@ export class DocClass extends DocItem<ApiClass> {
|
||||
excerpt.excerpt.spannedTokens.map((token) => genToken(this.model, token)),
|
||||
);
|
||||
|
||||
this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(model, typeParam));
|
||||
|
||||
for (const member of item.members) {
|
||||
switch (member.kind) {
|
||||
case ApiItemKind.Method:
|
||||
@@ -52,7 +50,6 @@ export class DocClass extends DocItem<ApiClass> {
|
||||
implementsTokens: this.implementsTokens,
|
||||
methods: this.methods.map((method) => method.toJSON()),
|
||||
properties: this.properties.map((prop) => prop.toJSON()),
|
||||
typeParameters: this.typeParameters,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
import type { ApiFunction, ApiModel } from '@microsoft/api-extractor-model';
|
||||
import type { ApiFunction, ApiModel, ApiParameterListMixin } from '@microsoft/api-extractor-model';
|
||||
import { DocItem } from './DocItem';
|
||||
import {
|
||||
type ParameterDocumentation,
|
||||
type TokenDocumentation,
|
||||
genParameter,
|
||||
genToken,
|
||||
type TypeParameterData,
|
||||
generateTypeParamData,
|
||||
} from '~/util/parse.server';
|
||||
import { TypeParameterMixin } from './TypeParameterMixin';
|
||||
import { type TokenDocumentation, genToken, genParameter, ParameterDocumentation } from '~/util/parse.server';
|
||||
|
||||
export class DocFunction extends DocItem<ApiFunction> {
|
||||
public readonly parameters: ParameterDocumentation[];
|
||||
export class DocFunction extends TypeParameterMixin(DocItem<ApiFunction>) {
|
||||
public readonly returnTypeTokens: TokenDocumentation[];
|
||||
public readonly overloadIndex: number;
|
||||
public readonly typeParameters: TypeParameterData[] = [];
|
||||
public readonly parameters: ParameterDocumentation[];
|
||||
|
||||
public constructor(model: ApiModel, item: ApiFunction) {
|
||||
super(model, item);
|
||||
this.parameters = item.parameters.map((param) => genParameter(this.model, param));
|
||||
this.returnTypeTokens = item.returnTypeExcerpt.spannedTokens.map((token) => genToken(this.model, token));
|
||||
this.overloadIndex = item.overloadIndex;
|
||||
this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(model, typeParam));
|
||||
this.parameters = (item as ApiParameterListMixin).parameters.map((param) => genParameter(this.model, param));
|
||||
}
|
||||
|
||||
public override toJSON() {
|
||||
@@ -29,7 +21,6 @@ export class DocFunction extends DocItem<ApiFunction> {
|
||||
parameters: this.parameters,
|
||||
returnTypeTokens: this.returnTypeTokens,
|
||||
overloadIndex: this.overloadIndex,
|
||||
typeParameters: this.typeParameters,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { DocItem } from './DocItem';
|
||||
import { DocMethodSignature } from './DocMethodSignature';
|
||||
import { DocProperty } from './DocProperty';
|
||||
import { TypeParameterMixin } from './TypeParameterMixin';
|
||||
import { ApiInterface, ApiItemKind, ApiMethodSignature, ApiModel, ApiPropertySignature } from '~/api-extractor.server';
|
||||
import { type TokenDocumentation, genToken, type TypeParameterData, generateTypeParamData } from '~/util/parse.server';
|
||||
import { type TokenDocumentation, genToken } from '~/util/parse.server';
|
||||
|
||||
export class DocInterface extends DocItem<ApiInterface> {
|
||||
export class DocInterface extends TypeParameterMixin(DocItem<ApiInterface>) {
|
||||
public readonly extendsTokens: TokenDocumentation[][] | null;
|
||||
public readonly methods: DocMethodSignature[] = [];
|
||||
public readonly properties: DocProperty[] = [];
|
||||
public readonly typeParameters: TypeParameterData[] = [];
|
||||
|
||||
public constructor(model: ApiModel, item: ApiInterface) {
|
||||
super(model, item);
|
||||
@@ -17,8 +17,6 @@ export class DocInterface extends DocItem<ApiInterface> {
|
||||
excerpt.excerpt.spannedTokens.map((token) => genToken(this.model, token)),
|
||||
);
|
||||
|
||||
this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(this.model, typeParam));
|
||||
|
||||
for (const member of item.members) {
|
||||
switch (member.kind) {
|
||||
case ApiItemKind.MethodSignature:
|
||||
@@ -39,7 +37,6 @@ export class DocInterface extends DocItem<ApiInterface> {
|
||||
extendsTokens: this.extendsTokens,
|
||||
methods: this.methods.map((method) => method.toJSON()),
|
||||
properties: this.properties.map((prop) => prop.toJSON()),
|
||||
typeParameters: this.typeParameters,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { ApiModel, ApiDeclaredItem } from '@microsoft/api-extractor-model';
|
||||
import type { ReferenceData } from '~/model.server';
|
||||
import { resolveName, genReference, resolveDocComment, TokenDocumentation, genToken } from '~/util/parse.server';
|
||||
|
||||
export type DocItemConstructor<T = DocItem> = new (...args: any[]) => T;
|
||||
|
||||
export class DocItem<T extends ApiDeclaredItem = ApiDeclaredItem> {
|
||||
public readonly item: T;
|
||||
public readonly name: string;
|
||||
|
||||
@@ -1,33 +1,17 @@
|
||||
import type { ApiMethod, ApiModel } from '@microsoft/api-extractor-model';
|
||||
import { DocItem } from './DocItem';
|
||||
import { DocFunction } from './DocFunction';
|
||||
import { Visibility } from './Visibility';
|
||||
import {
|
||||
type ParameterDocumentation,
|
||||
type TokenDocumentation,
|
||||
genParameter,
|
||||
genToken,
|
||||
generateTypeParamData,
|
||||
TypeParameterData,
|
||||
} from '~/util/parse.server';
|
||||
|
||||
export class DocMethod extends DocItem<ApiMethod> {
|
||||
public readonly parameters: ParameterDocumentation[];
|
||||
export class DocMethod extends DocFunction {
|
||||
public readonly static: boolean;
|
||||
public readonly optional: boolean;
|
||||
public readonly visibility: Visibility;
|
||||
public readonly returnTypeTokens: TokenDocumentation[];
|
||||
public readonly overloadIndex: number;
|
||||
public readonly typeParameters: TypeParameterData[] = [];
|
||||
|
||||
public constructor(model: ApiModel, item: ApiMethod) {
|
||||
super(model, item);
|
||||
this.parameters = item.parameters.map((param) => genParameter(this.model, param));
|
||||
this.static = item.isStatic;
|
||||
this.optional = item.isOptional;
|
||||
this.visibility = item.isProtected ? Visibility.Protected : Visibility.Public;
|
||||
this.returnTypeTokens = item.returnTypeExcerpt.spannedTokens.map((token) => genToken(this.model, token));
|
||||
this.overloadIndex = item.overloadIndex;
|
||||
this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(this.model, typeParam));
|
||||
}
|
||||
|
||||
public override toJSON() {
|
||||
@@ -36,9 +20,6 @@ export class DocMethod extends DocItem<ApiMethod> {
|
||||
static: this.static,
|
||||
optional: this.optional,
|
||||
visibility: this.visibility,
|
||||
parameters: this.parameters,
|
||||
returnTypeTokens: this.returnTypeTokens,
|
||||
overloadIndex: this.overloadIndex,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,18 @@
|
||||
import type { ApiMethodSignature, ApiModel } from '@microsoft/api-extractor-model';
|
||||
import { DocItem } from './DocItem';
|
||||
import {
|
||||
type ParameterDocumentation,
|
||||
type TokenDocumentation,
|
||||
genParameter,
|
||||
genToken,
|
||||
generateTypeParamData,
|
||||
type TypeParameterData,
|
||||
} from '~/util/parse.server';
|
||||
import { DocFunction } from './DocFunction';
|
||||
|
||||
export class DocMethodSignature extends DocItem<ApiMethodSignature> {
|
||||
public readonly parameters: ParameterDocumentation[];
|
||||
export class DocMethodSignature extends DocFunction {
|
||||
public readonly optional: boolean;
|
||||
public readonly returnTypeTokens: TokenDocumentation[];
|
||||
public readonly overloadIndex: number;
|
||||
public readonly typeParameters: TypeParameterData[] = [];
|
||||
|
||||
public constructor(model: ApiModel, item: ApiMethodSignature) {
|
||||
super(model, item);
|
||||
this.parameters = item.parameters.map((param) => genParameter(this.model, param));
|
||||
this.optional = item.isOptional;
|
||||
this.returnTypeTokens = item.returnTypeExcerpt.spannedTokens.map((token) => genToken(this.model, token));
|
||||
this.overloadIndex = item.overloadIndex;
|
||||
this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(this.model, typeParam));
|
||||
}
|
||||
|
||||
public override toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
optional: this.optional,
|
||||
parameters: this.parameters,
|
||||
returnTypeTokens: this.returnTypeTokens,
|
||||
overloadIndex: this.overloadIndex,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import type { ApiModel, ApiTypeAlias } from '@microsoft/api-extractor-model';
|
||||
import { DocItem } from './DocItem';
|
||||
import { type TokenDocumentation, genToken, generateTypeParamData, type TypeParameterData } from '~/util/parse.server';
|
||||
import { TypeParameterMixin } from './TypeParameterMixin';
|
||||
import { type TokenDocumentation, genToken } from '~/util/parse.server';
|
||||
|
||||
export class DocTypeAlias extends DocItem<ApiTypeAlias> {
|
||||
export class DocTypeAlias extends TypeParameterMixin(DocItem<ApiTypeAlias>) {
|
||||
public readonly typeTokens: TokenDocumentation[];
|
||||
public readonly typeParameters: TypeParameterData[] = [];
|
||||
|
||||
public constructor(model: ApiModel, item: ApiTypeAlias) {
|
||||
super(model, item);
|
||||
this.typeTokens = item.typeExcerpt.spannedTokens.map((token) => genToken(model, token));
|
||||
this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(this.model, typeParam));
|
||||
}
|
||||
|
||||
public override toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
typeTokens: this.typeTokens,
|
||||
typeParameters: this.typeParameters,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
24
packages/website/src/DocModel/TypeParameterMixin.ts
Normal file
24
packages/website/src/DocModel/TypeParameterMixin.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { ApiItem, ApiModel, ApiTypeParameterListMixin } from '@microsoft/api-extractor-model';
|
||||
import type { DocItemConstructor } from './DocItem';
|
||||
import { generateTypeParamData, TypeParameterData } from '~/util/parse.server';
|
||||
|
||||
export function TypeParameterMixin<TBase extends DocItemConstructor>(Base: TBase) {
|
||||
return class Mixed extends Base {
|
||||
public readonly typeParameters: TypeParameterData[] = [];
|
||||
|
||||
public constructor(...args: any[]);
|
||||
public constructor(model: ApiModel, item: ApiItem) {
|
||||
super(model, item);
|
||||
this.typeParameters = (item as ApiTypeParameterListMixin).typeParameters.map((typeParam) =>
|
||||
generateTypeParamData(this.model, typeParam),
|
||||
);
|
||||
}
|
||||
|
||||
public override toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
typeParameterData: this.typeParameters,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { VscSymbolClass, VscSymbolMethod, VscSymbolEnum, VscSymbolInterface, VscSymbolVariable } from 'react-icons/vsc';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vs } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
||||
import { TypeParamTable } from './TypeParamTable';
|
||||
import { generateIcon } from '~/util/icon';
|
||||
import type { TypeParameterData } from '~/util/parse.server';
|
||||
|
||||
export interface DocContainerProps {
|
||||
@@ -13,27 +13,25 @@ export interface DocContainerProps {
|
||||
typeParams?: TypeParameterData[];
|
||||
}
|
||||
|
||||
const symbolClass = 'mr-2';
|
||||
const icons = {
|
||||
Class: <VscSymbolClass color="blue" className={symbolClass} />,
|
||||
Method: <VscSymbolMethod className={symbolClass} />,
|
||||
Function: <VscSymbolMethod color="purple" className={symbolClass} />,
|
||||
Enum: <VscSymbolEnum className={symbolClass} />,
|
||||
Interface: <VscSymbolInterface color="blue" className={symbolClass} />,
|
||||
TypeAlias: <VscSymbolVariable color="blue" className={symbolClass} />,
|
||||
};
|
||||
|
||||
export function DocContainer({ name, kind, excerpt, summary, typeParams, children }: DocContainerProps) {
|
||||
return (
|
||||
<div className="px-10">
|
||||
<h1 style={{ fontFamily: 'JetBrains Mono' }} className="flex items-csenter content-center">
|
||||
{icons[kind as keyof typeof icons]}
|
||||
<h1 className="font-mono flex items-center content-center break-all">
|
||||
{generateIcon(kind, 'mr-2')}
|
||||
{name}
|
||||
</h1>
|
||||
<h3>Code declaration:</h3>
|
||||
<SyntaxHighlighter language="typescript" style={vs} codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}>
|
||||
{excerpt}
|
||||
</SyntaxHighlighter>
|
||||
<div>
|
||||
<SyntaxHighlighter
|
||||
wrapLines
|
||||
wrapLongLines
|
||||
language="typescript"
|
||||
style={vs}
|
||||
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}
|
||||
>
|
||||
{excerpt}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
{typeParams?.length ? (
|
||||
<>
|
||||
<h3>Type Parameters</h3>
|
||||
|
||||
45
packages/website/src/components/ItemSidebar.tsx
Normal file
45
packages/website/src/components/ItemSidebar.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { AiOutlineMenu } from 'react-icons/ai';
|
||||
import { VscPackage } from 'react-icons/vsc';
|
||||
import { generateIcon } from '~/util/icon';
|
||||
import type { getMembers } from '~/util/parse.server';
|
||||
|
||||
export interface ItemListProps {
|
||||
packageName: string;
|
||||
data: {
|
||||
members: ReturnType<typeof getMembers>;
|
||||
};
|
||||
}
|
||||
|
||||
function onMenuClick() {
|
||||
console.log('menu clicked');
|
||||
// Todo show/hide list
|
||||
}
|
||||
|
||||
export function ItemSidebar({ packageName, data }: ItemListProps) {
|
||||
return (
|
||||
<div className="flex flex-col max-h-full min-w-[270px] border-r-solid border-b-solid border-gray border-width-0.5">
|
||||
<div className="flex justify-between content-center items-center border-b-solid border-gray border-width-0.5">
|
||||
<h1 className="px-2 font-mono flex items-center content-center">
|
||||
<VscPackage className="px-1" />
|
||||
{`${packageName}`}
|
||||
</h1>
|
||||
<button className="lg:hidden mr-2 bg-transparent border-none" onClick={onMenuClick}>
|
||||
<AiOutlineMenu size={32} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="hidden lg:block overflow-y-scroll overflow-x-clip p-7">
|
||||
{data.members.map((member, i) => (
|
||||
<div key={i} className="mb-1">
|
||||
<a
|
||||
className="flex content-center items-center align-center font-mono no-underline break-all color-blue-500"
|
||||
href={member.path}
|
||||
>
|
||||
{generateIcon(member.kind, 'px-1')}
|
||||
{member.name}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export function Class({ data }: ClassProps) {
|
||||
kind={data.kind}
|
||||
excerpt={data.excerpt}
|
||||
summary={data.summary}
|
||||
typeParams={data.typeParameters}
|
||||
typeParams={data.typeParameterData}
|
||||
>
|
||||
<>
|
||||
{data.properties.length ? <PropertyList data={data.properties} /> : null}
|
||||
|
||||
@@ -13,7 +13,7 @@ export function Function({ data }: FunctionProps) {
|
||||
kind={data.kind}
|
||||
excerpt={data.excerpt}
|
||||
summary={data.summary}
|
||||
typeParams={data.typeParameters}
|
||||
typeParams={data.typeParameterData}
|
||||
>
|
||||
<ParameterTable data={data.parameters} />
|
||||
</DocContainer>
|
||||
|
||||
@@ -14,7 +14,7 @@ export function Interface({ data }: InterfaceProps) {
|
||||
kind={data.kind}
|
||||
excerpt={data.excerpt}
|
||||
summary={data.summary}
|
||||
typeParams={data.typeParameters}
|
||||
typeParams={data.typeParameterData}
|
||||
>
|
||||
<>
|
||||
{data.properties.length ? <PropertyList data={data.properties} /> : null}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function TypeAlias({ data }: TypeAliasProps) {
|
||||
kind={data.kind}
|
||||
excerpt={data.excerpt}
|
||||
summary={data.summary}
|
||||
typeParams={data.typeParameters}
|
||||
typeParams={data.typeParameterData}
|
||||
>
|
||||
<div>WIP</div>
|
||||
</DocContainer>
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
/* eslint-disable @typescript-eslint/no-throw-literal */
|
||||
import { json } from '@remix-run/node';
|
||||
import { Params, useLoaderData } from '@remix-run/react';
|
||||
import { Outlet, Params, useLoaderData, useParams } from '@remix-run/react';
|
||||
import { ItemSidebar } from '~/components/ItemSidebar';
|
||||
import { createApiModel } from '~/util/api-model.server';
|
||||
import { findPackage, getMembers } from '~/util/parse.server';
|
||||
|
||||
export async function loader({ params }: { params: Params }) {
|
||||
const UnknownResponse = new Response('Not Found', {
|
||||
status: 404,
|
||||
});
|
||||
|
||||
const res = await fetch(
|
||||
`https://raw.githubusercontent.com/discordjs/docs/main/${params.packageName!}/${params.branchName!}.api.json`,
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const data = await res.json();
|
||||
const model = createApiModel(data);
|
||||
const data = await res.json().catch(() => {
|
||||
throw UnknownResponse;
|
||||
});
|
||||
|
||||
const model = createApiModel(data);
|
||||
const pkg = findPackage(model, params.packageName!);
|
||||
|
||||
if (!pkg) {
|
||||
throw UnknownResponse;
|
||||
}
|
||||
|
||||
return json({
|
||||
members: getMembers(pkg!),
|
||||
members: getMembers(pkg),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,14 +36,16 @@ interface LoaderData {
|
||||
|
||||
export default function Package() {
|
||||
const data = useLoaderData<LoaderData>();
|
||||
const { packageName } = useParams();
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{data.members.map((member, i) => (
|
||||
<li key={i}>
|
||||
<a href={member.path}>{member.name}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex flex-col lg:flex-row overflow-none max-w-full h-full">
|
||||
<div className="w-full lg:min-w-1/4 lg:max-w-1/4">
|
||||
<ItemSidebar packageName={packageName!} data={data} />
|
||||
</div>
|
||||
<div>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
14
packages/website/src/util/icon.tsx
Normal file
14
packages/website/src/util/icon.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { VscSymbolClass, VscSymbolMethod, VscSymbolEnum, VscSymbolInterface, VscSymbolVariable } from 'react-icons/vsc';
|
||||
|
||||
export function generateIcon(kind: string, className?: string) {
|
||||
const icons = {
|
||||
Class: <VscSymbolClass color="blue" className={className} />,
|
||||
Method: <VscSymbolMethod className={className} />,
|
||||
Function: <VscSymbolMethod color="purple" className={className} />,
|
||||
Enum: <VscSymbolEnum className={className} />,
|
||||
Interface: <VscSymbolInterface color="blue" className={className} />,
|
||||
TypeAlias: <VscSymbolVariable color="blue" className={className} />,
|
||||
};
|
||||
|
||||
return icons[kind as keyof typeof icons];
|
||||
}
|
||||
@@ -165,6 +165,7 @@ export function genParameter(model: ApiModel, param: Parameter): ParameterDocume
|
||||
export function getMembers(pkg: ApiPackage) {
|
||||
return pkg.members[0]!.members.map((member) => ({
|
||||
name: member.displayName,
|
||||
kind: member.kind,
|
||||
path: generatePath(member.getHierarchy()),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { defineConfig } from 'unocss';
|
||||
|
||||
export default defineConfig({});
|
||||
export default defineConfig({
|
||||
theme: {
|
||||
fontFamily: {
|
||||
mono: ['JetBrains Mono'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user