mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-19 21:13:30 +01:00
feat(website): parse tsdoc comments (#8386)
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
import type { ApiEnum, ApiModel } from '@microsoft/api-extractor-model';
|
import type { ApiEnum, ApiModel } from '@microsoft/api-extractor-model';
|
||||||
import { DocItem } from './DocItem';
|
import { DocItem } from './DocItem';
|
||||||
import { genToken, resolveDocComment, TokenDocumentation } from '~/util/parse.server';
|
import { CommentNodeContainer } from './comment/CommentNodeContainer';
|
||||||
|
import { genToken, TokenDocumentation } from '~/util/parse.server';
|
||||||
|
|
||||||
export interface EnumMemberData {
|
export interface EnumMemberData {
|
||||||
name: string;
|
name: string;
|
||||||
initializerTokens: TokenDocumentation[];
|
initializerTokens: TokenDocumentation[];
|
||||||
summary: string | null;
|
summary: ReturnType<DocItem['toJSON']>['summary'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DocEnum extends DocItem<ApiEnum> {
|
export class DocEnum extends DocItem<ApiEnum> {
|
||||||
@@ -17,7 +18,9 @@ export class DocEnum extends DocItem<ApiEnum> {
|
|||||||
this.members = item.members.map((member) => ({
|
this.members = item.members.map((member) => ({
|
||||||
name: member.name,
|
name: member.name,
|
||||||
initializerTokens: member.initializerExcerpt?.spannedTokens.map((token) => genToken(this.model, token)) ?? [],
|
initializerTokens: member.initializerExcerpt?.spannedTokens.map((token) => genToken(this.model, token)) ?? [],
|
||||||
summary: resolveDocComment(member),
|
summary: member.tsdocComment
|
||||||
|
? new CommentNodeContainer(member.tsdocComment.summarySection, model, member).toJSON()
|
||||||
|
: null,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ApiModel, ApiDeclaredItem } from '@microsoft/api-extractor-model';
|
import type { ApiModel, ApiDeclaredItem } from '@microsoft/api-extractor-model';
|
||||||
|
import { CommentNodeContainer } from './comment/CommentNodeContainer';
|
||||||
import type { ReferenceData } from '~/util/model.server';
|
import type { ReferenceData } from '~/util/model.server';
|
||||||
import { resolveName, genReference, resolveDocComment, TokenDocumentation, genToken } from '~/util/parse.server';
|
import { resolveName, genReference, TokenDocumentation, genToken } from '~/util/parse.server';
|
||||||
|
|
||||||
export type DocItemConstructor<T = DocItem> = new (...args: any[]) => T;
|
export type DocItemConstructor<T = DocItem> = new (...args: any[]) => T;
|
||||||
|
|
||||||
@@ -8,11 +9,12 @@ export class DocItem<T extends ApiDeclaredItem = ApiDeclaredItem> {
|
|||||||
public readonly item: T;
|
public readonly item: T;
|
||||||
public readonly name: string;
|
public readonly name: string;
|
||||||
public readonly referenceData: ReferenceData;
|
public readonly referenceData: ReferenceData;
|
||||||
public readonly summary: string | null;
|
|
||||||
public readonly model: ApiModel;
|
public readonly model: ApiModel;
|
||||||
public readonly excerpt: string;
|
public readonly excerpt: string;
|
||||||
public readonly excerptTokens: TokenDocumentation[] = [];
|
public readonly excerptTokens: TokenDocumentation[] = [];
|
||||||
public readonly kind: string;
|
public readonly kind: string;
|
||||||
|
public readonly remarks: CommentNodeContainer | null;
|
||||||
|
public readonly summary: CommentNodeContainer | null;
|
||||||
|
|
||||||
public constructor(model: ApiModel, item: T) {
|
public constructor(model: ApiModel, item: T) {
|
||||||
this.item = item;
|
this.item = item;
|
||||||
@@ -20,19 +22,25 @@ export class DocItem<T extends ApiDeclaredItem = ApiDeclaredItem> {
|
|||||||
this.model = model;
|
this.model = model;
|
||||||
this.name = resolveName(item);
|
this.name = resolveName(item);
|
||||||
this.referenceData = genReference(item);
|
this.referenceData = genReference(item);
|
||||||
this.summary = resolveDocComment(item);
|
|
||||||
this.excerpt = item.excerpt.text;
|
this.excerpt = item.excerpt.text;
|
||||||
this.excerptTokens = item.excerpt.spannedTokens.map((token) => genToken(model, token));
|
this.excerptTokens = item.excerpt.spannedTokens.map((token) => genToken(model, token));
|
||||||
|
this.remarks = item.tsdocComment?.remarksBlock
|
||||||
|
? new CommentNodeContainer(item.tsdocComment.remarksBlock.content, model, item.parent)
|
||||||
|
: null;
|
||||||
|
this.summary = item.tsdocComment?.summarySection
|
||||||
|
? new CommentNodeContainer(item.tsdocComment.summarySection, model, item.parent)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public toJSON() {
|
public toJSON() {
|
||||||
return {
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
referenceData: this.referenceData,
|
referenceData: this.referenceData,
|
||||||
summary: this.summary,
|
summary: this.summary?.toJSON() ?? null,
|
||||||
excerpt: this.excerpt,
|
excerpt: this.excerpt,
|
||||||
excerptTokens: this.excerptTokens,
|
excerptTokens: this.excerptTokens,
|
||||||
kind: this.kind,
|
kind: this.kind,
|
||||||
|
remarks: this.remarks?.toJSON() ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
packages/website/src/DocModel/comment/CommentNode.ts
Normal file
22
packages/website/src/DocModel/comment/CommentNode.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type { ApiItem, ApiModel } from '@microsoft/api-extractor-model';
|
||||||
|
import type { DocNode } from '@microsoft/tsdoc';
|
||||||
|
|
||||||
|
export class CommentNode<T extends DocNode = DocNode> {
|
||||||
|
public readonly node: T;
|
||||||
|
public readonly kind: string;
|
||||||
|
public readonly model: ApiModel;
|
||||||
|
public readonly parentItem: ApiItem | null;
|
||||||
|
|
||||||
|
public constructor(node: T, model: ApiModel, parentItem?: ApiItem) {
|
||||||
|
this.node = node;
|
||||||
|
this.kind = node.kind;
|
||||||
|
this.model = model;
|
||||||
|
this.parentItem = parentItem ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toJSON() {
|
||||||
|
return {
|
||||||
|
kind: this.kind,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { ApiItem, ApiModel } from '@microsoft/api-extractor-model';
|
||||||
|
import type { DocNodeContainer } from '@microsoft/tsdoc';
|
||||||
|
import { createCommentNode } from '.';
|
||||||
|
import { CommentNode } from './CommentNode';
|
||||||
|
|
||||||
|
export class CommentNodeContainer<T extends DocNodeContainer = DocNodeContainer> extends CommentNode<DocNodeContainer> {
|
||||||
|
public readonly nodes: CommentNode[];
|
||||||
|
|
||||||
|
public constructor(container: T, model: ApiModel, parentItem?: ApiItem) {
|
||||||
|
super(container, model, parentItem);
|
||||||
|
this.nodes = container.nodes.map((node) => createCommentNode(node, model, parentItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override toJSON() {
|
||||||
|
return {
|
||||||
|
...super.toJSON(),
|
||||||
|
nodes: this.nodes.map((node) => node.toJSON()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import type { ApiModel, ApiItem } from '@microsoft/api-extractor-model';
|
||||||
|
import type { DocFencedCode } from '@microsoft/tsdoc';
|
||||||
|
import { CommentNode } from './CommentNode';
|
||||||
|
|
||||||
|
export class FencedCodeCommentNode extends CommentNode<DocFencedCode> {
|
||||||
|
public readonly code: string;
|
||||||
|
public readonly language: string;
|
||||||
|
|
||||||
|
public constructor(node: DocFencedCode, model: ApiModel, parentItem?: ApiItem) {
|
||||||
|
super(node, model, parentItem);
|
||||||
|
this.code = node.code;
|
||||||
|
this.language = node.language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override toJSON() {
|
||||||
|
return {
|
||||||
|
...super.toJSON(),
|
||||||
|
code: this.code,
|
||||||
|
language: this.language,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
54
packages/website/src/DocModel/comment/LinkTagCommentNode.ts
Normal file
54
packages/website/src/DocModel/comment/LinkTagCommentNode.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import type { ApiItem, ApiModel } from '@microsoft/api-extractor-model';
|
||||||
|
import type { DocDeclarationReference, DocLinkTag } from '@microsoft/tsdoc';
|
||||||
|
import { CommentNode } from './CommentNode';
|
||||||
|
import { generatePath, resolveName } from '~/util/parse.server';
|
||||||
|
|
||||||
|
export function genToken(
|
||||||
|
model: ApiModel,
|
||||||
|
ref: DocDeclarationReference,
|
||||||
|
context: ApiItem | null,
|
||||||
|
): LinkTagCodeLink | null {
|
||||||
|
if (!context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = model.resolveDeclarationReference(ref, context).resolvedApiItem ?? null;
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: resolveName(item),
|
||||||
|
kind: item.kind,
|
||||||
|
path: generatePath(item.getHierarchy()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LinkTagCodeLink {
|
||||||
|
name: string;
|
||||||
|
kind: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LinkTagCommentNode extends CommentNode<DocLinkTag> {
|
||||||
|
public readonly codeDestination: LinkTagCodeLink | null;
|
||||||
|
public readonly text: string | null;
|
||||||
|
public readonly urlDestination: string | null;
|
||||||
|
|
||||||
|
public constructor(node: DocLinkTag, model: ApiModel, parentItem?: ApiItem) {
|
||||||
|
super(node, model, parentItem);
|
||||||
|
this.codeDestination = node.codeDestination ? genToken(model, node.codeDestination, this.parentItem) : null;
|
||||||
|
this.text = node.linkText ?? null;
|
||||||
|
this.urlDestination = node.urlDestination ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override toJSON() {
|
||||||
|
return {
|
||||||
|
...super.toJSON(),
|
||||||
|
text: this.text,
|
||||||
|
codeDestination: this.codeDestination,
|
||||||
|
urlDestination: this.urlDestination,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import type { ApiItem, ApiModel } from '@microsoft/api-extractor-model';
|
||||||
|
import type { DocPlainText } from '@microsoft/tsdoc';
|
||||||
|
import { CommentNode } from './CommentNode';
|
||||||
|
|
||||||
|
export class PlainTextCommentNode extends CommentNode<DocPlainText> {
|
||||||
|
public readonly text: string;
|
||||||
|
|
||||||
|
public constructor(node: DocPlainText, model: ApiModel, parentItem?: ApiItem) {
|
||||||
|
super(node, model, parentItem);
|
||||||
|
this.text = node.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override toJSON() {
|
||||||
|
return {
|
||||||
|
...super.toJSON(),
|
||||||
|
text: this.text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/website/src/DocModel/comment/index.ts
Normal file
22
packages/website/src/DocModel/comment/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type { ApiModel, ApiItem } from '@microsoft/api-extractor-model';
|
||||||
|
import type { DocNode, DocPlainText, DocLinkTag, DocParagraph, DocFencedCode } from '@microsoft/tsdoc';
|
||||||
|
import { CommentNode } from './CommentNode';
|
||||||
|
import { CommentNodeContainer } from './CommentNodeContainer';
|
||||||
|
import { FencedCodeCommentNode } from './FencedCodeCommentNode';
|
||||||
|
import { LinkTagCommentNode } from './LinkTagCommentNode';
|
||||||
|
import { PlainTextCommentNode } from './PlainTextCommentNode';
|
||||||
|
|
||||||
|
export function createCommentNode(node: DocNode, model: ApiModel, parentItem?: ApiItem): CommentNode {
|
||||||
|
switch (node.kind) {
|
||||||
|
case 'PlainText':
|
||||||
|
return new PlainTextCommentNode(node as DocPlainText, model, parentItem);
|
||||||
|
case 'LinkTag':
|
||||||
|
return new LinkTagCommentNode(node as DocLinkTag, model, parentItem);
|
||||||
|
case 'Paragraph':
|
||||||
|
return new CommentNodeContainer(node as DocParagraph, model, parentItem);
|
||||||
|
case 'FencedCode':
|
||||||
|
return new FencedCodeCommentNode(node as DocFencedCode, model, parentItem);
|
||||||
|
default:
|
||||||
|
return new CommentNode(node, model, parentItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
import { CommentSection } from './Comment';
|
||||||
import { HyperlinkedText } from './HyperlinkedText';
|
import { HyperlinkedText } from './HyperlinkedText';
|
||||||
|
import type { DocItem } from '~/DocModel/DocItem';
|
||||||
import type { TokenDocumentation } from '~/util/parse.server';
|
import type { TokenDocumentation } from '~/util/parse.server';
|
||||||
|
|
||||||
export enum CodeListingSeparatorType {
|
export enum CodeListingSeparatorType {
|
||||||
@@ -9,7 +11,7 @@ export enum CodeListingSeparatorType {
|
|||||||
|
|
||||||
export interface CodeListingProps {
|
export interface CodeListingProps {
|
||||||
name: string;
|
name: string;
|
||||||
summary?: string | null;
|
summary?: ReturnType<DocItem['toJSON']>['summary'];
|
||||||
typeTokens: TokenDocumentation[];
|
typeTokens: TokenDocumentation[];
|
||||||
separator?: CodeListingSeparatorType;
|
separator?: CodeListingSeparatorType;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
@@ -34,7 +36,7 @@ export function CodeListing({
|
|||||||
<HyperlinkedText tokens={typeTokens} />
|
<HyperlinkedText tokens={typeTokens} />
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
{summary && <p className="text-dark-100 dark:text-gray-300">{summary}</p>}
|
{summary && <CommentSection textClassName="text-dark-100 dark:text-gray-300" node={summary} />}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
61
packages/website/src/components/Comment.tsx
Normal file
61
packages/website/src/components/Comment.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
|
import type { CommentNode } from '~/DocModel/comment/CommentNode';
|
||||||
|
import type { CommentNodeContainer } from '~/DocModel/comment/CommentNodeContainer';
|
||||||
|
import type { FencedCodeCommentNode } from '~/DocModel/comment/FencedCodeCommentNode';
|
||||||
|
import type { LinkTagCommentNode } from '~/DocModel/comment/LinkTagCommentNode';
|
||||||
|
import type { PlainTextCommentNode } from '~/DocModel/comment/PlainTextCommentNode';
|
||||||
|
|
||||||
|
export interface RemarksBlockProps {
|
||||||
|
node: ReturnType<CommentNode['toJSON']>;
|
||||||
|
textClassName?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CommentSection({ node, textClassName }: RemarksBlockProps): JSX.Element {
|
||||||
|
const createNode = (node: ReturnType<CommentNode['toJSON']>): ReactNode => {
|
||||||
|
switch (node.kind) {
|
||||||
|
case 'PlainText':
|
||||||
|
return <span>{(node as ReturnType<PlainTextCommentNode['toJSON']>).text}</span>;
|
||||||
|
case 'Paragraph':
|
||||||
|
return (
|
||||||
|
<p className={textClassName}>
|
||||||
|
{(node as ReturnType<CommentNodeContainer['toJSON']>).nodes.map((node) => createNode(node))}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
case 'SoftBreak':
|
||||||
|
return <br />;
|
||||||
|
case 'LinkTag': {
|
||||||
|
const { codeDestination, urlDestination, text } = node as ReturnType<LinkTagCommentNode['toJSON']>;
|
||||||
|
|
||||||
|
if (codeDestination) {
|
||||||
|
return <Link href={codeDestination.path}>{text ?? codeDestination.name}</Link>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urlDestination) {
|
||||||
|
return <Link href={urlDestination}>{text ?? urlDestination}</Link>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case 'FencedCodeBlock': {
|
||||||
|
const { language, code } = node as ReturnType<FencedCodeCommentNode['toJSON']>;
|
||||||
|
return <SyntaxHighlighter language={language}>{code}</SyntaxHighlighter>;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{node.kind === 'Paragraph' || node.kind === 'Section' ? (
|
||||||
|
<>{(node as CommentNodeContainer).nodes.map(createNode)}</>
|
||||||
|
) : (
|
||||||
|
<>{createNode(node)}</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,9 +3,11 @@ import { VscListSelection, VscSymbolParameter } from 'react-icons/vsc';
|
|||||||
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
||||||
import { CodeListingSeparatorType } from './CodeListing';
|
import { CodeListingSeparatorType } from './CodeListing';
|
||||||
|
import { CommentSection } from './Comment';
|
||||||
import { HyperlinkedText } from './HyperlinkedText';
|
import { HyperlinkedText } from './HyperlinkedText';
|
||||||
import { Section } from './Section';
|
import { Section } from './Section';
|
||||||
import { TypeParamTable } from './TypeParamTable';
|
import { TypeParamTable } from './TypeParamTable';
|
||||||
|
import type { DocItem } from '~/DocModel/DocItem';
|
||||||
import { generateIcon } from '~/util/icon';
|
import { generateIcon } from '~/util/icon';
|
||||||
import type { TokenDocumentation, TypeParameterData } from '~/util/parse.server';
|
import type { TokenDocumentation, TypeParameterData } from '~/util/parse.server';
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@ export interface DocContainerProps {
|
|||||||
name: string;
|
name: string;
|
||||||
kind: string;
|
kind: string;
|
||||||
excerpt: string;
|
excerpt: string;
|
||||||
summary?: string | null;
|
summary?: ReturnType<DocItem['toJSON']>['summary'];
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
extendsTokens?: TokenDocumentation[] | null;
|
extendsTokens?: TokenDocumentation[] | null;
|
||||||
typeParams?: TypeParameterData[];
|
typeParams?: TypeParameterData[];
|
||||||
@@ -50,7 +52,11 @@ export function DocContainer({ name, kind, excerpt, summary, typeParams, childre
|
|||||||
) : null}
|
) : null}
|
||||||
<div className="space-y-10">
|
<div className="space-y-10">
|
||||||
<Section iconElement={<VscListSelection />} title="Summary" className="dark:text-white">
|
<Section iconElement={<VscListSelection />} title="Summary" className="dark:text-white">
|
||||||
<p className="text-dark-100 dark:text-gray-300">{summary ?? 'No summary provided.'}</p>
|
{summary ? (
|
||||||
|
<CommentSection textClassName="text-dark-100 dark:text-gray-300" node={summary} />
|
||||||
|
) : (
|
||||||
|
<p className="text-dark-100 dark:text-gray-300">No summary provided.</p>
|
||||||
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
{typeParams?.length ? (
|
{typeParams?.length ? (
|
||||||
<Section
|
<Section
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FiLink } from 'react-icons/fi';
|
import { FiLink } from 'react-icons/fi';
|
||||||
|
import { CommentSection } from './Comment';
|
||||||
import { HyperlinkedText } from './HyperlinkedText';
|
import { HyperlinkedText } from './HyperlinkedText';
|
||||||
import { ParameterTable } from './ParameterTable';
|
import { ParameterTable } from './ParameterTable';
|
||||||
import type { DocMethod } from '~/DocModel/DocMethod';
|
import type { DocMethod } from '~/DocModel/DocMethod';
|
||||||
@@ -43,7 +44,7 @@ export function MethodItem({ data }: MethodItemProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-7 mb-5">
|
<div className="mx-7 mb-5">
|
||||||
{data.summary && <p className="text-dark-100 dark:text-gray-300">{data.summary}</p>}
|
{data.summary && <CommentSection textClassName="text-dark-100 dark:text-gray-300" node={data.summary} />}
|
||||||
{data.parameters.length ? <ParameterTable data={data.parameters} /> : null}
|
{data.parameters.length ? <ParameterTable data={data.parameters} /> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function findPackage(model: ApiModel, name: string): ApiPackage | undefin
|
|||||||
| undefined;
|
| undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generatePath(items: readonly ApiItem[]) {
|
export function generatePath(items: readonly ApiItem[]) {
|
||||||
let path = '/docs/main/packages/';
|
let path = '/docs/main/packages/';
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
switch (item.kind) {
|
switch (item.kind) {
|
||||||
|
|||||||
Reference in New Issue
Block a user