diff --git a/packages/g6/src/elements/edges/base-edge.ts b/packages/g6/src/elements/edges/base-edge.ts index 21699557f6..87c8943811 100644 --- a/packages/g6/src/elements/edges/base-edge.ts +++ b/packages/g6/src/elements/edges/base-edge.ts @@ -1,8 +1,8 @@ import type { BaseStyleProps, + DisplayObject, DisplayObjectConfig, Group, - ImageStyleProps, LineStyleProps, PathStyleProps, } from '@antv/g'; @@ -11,97 +11,146 @@ import type { PathArray } from '@antv/util'; import { deepMix, isEmpty, isFunction } from '@antv/util'; import type { BaseElementStyleProps, + EdgeArrowStyleProps, EdgeKey, EdgeLabelStyleProps, Keyframe, - LoopPlacement, + LoopStyleProps, Node, Point, PrefixObject, - Size, } from '../../types'; import { getBBoxHeight, getBBoxWidth, getNodeBBox } from '../../utils/bbox'; import { getCubicLoopPath, getLabelPositionStyle } from '../../utils/edge'; import { findPorts, getConnectionPoint, isSameNode } from '../../utils/element'; import { omitStyleProps, subStyleProps } from '../../utils/prefix'; import { parseSize } from '../../utils/size'; -import type { SymbolFactor } from '../../utils/symbol'; import * as Symbol from '../../utils/symbol'; import { getWordWrapWidthByEnds } from '../../utils/text'; import type { LabelStyleProps } from '../shapes'; import { Label } from '../shapes'; import { BaseShape } from '../shapes/base-shape'; -export type BaseEdgeStyleProps = BaseElementStyleProps & - Pick< - PathStyleProps, - 'isBillboard' | 'markerStart' | 'markerStartOffset' | 'markerEnd' | 'markerEndOffset' | 'markerMid' - > & - PrefixObject & - PrefixObject & - PrefixObject & - PrefixObject & - PrefixObject & { - /** - * 是否显示边的标签 - * - * Whether to display the label of the edge - */ - label?: boolean; - /** - * 是否显示边的光晕 - * - * Whether to display the halo of the edge - */ - halo?: boolean; - /** - * 是否显示边的起始箭头 - * - * Whether to display the start arrow of the edge - */ - startArrow?: boolean; - /** - * 是否显示边的结束箭头 - * - * Whether to display the end arrow of the edge - */ - endArrow?: boolean; - /** - * 起始箭头的偏移量 - * - * Offset of the start arrow - */ - startArrowOffset?: number; - /** - * 结束箭头的偏移量 - * - * Offset of the end arrow - */ - endArrowOffset?: number; - /** - * 边的起点 shape - * The source shape. Represents the start of the edge - */ - sourceNode: Node; - /** - * 边的终点 shape - * The source shape. Represents the start of the edge - */ - targetNode: Node; - /** - * 边起始连接的 port - * The Port of the source node - */ - sourcePort?: string; - /** - * 边终点连接的 port - * The Port of the target node - */ - targetPort?: string; - }; +/** + * 边的基础样式属性 + * + * Base style properties of the edge + */ +export interface BaseEdgeStyleProps + extends BaseElementStyleProps, + PrefixObject, + PrefixObject, + PrefixObject, + PrefixObject, + PrefixObject { + /** + * 是否显示边的标签 + * + * Whether to display the label of the edge + */ + label?: boolean; + /** + * 是否显示边的光晕 + * + * Whether to display the halo of the edge + */ + halo?: boolean; + /** + * 是否显示边的起始箭头 + * + * Whether to display the start arrow of the edge + */ + startArrow?: boolean; + /** + * 是否显示边的结束箭头 + * + * Whether to display the end arrow of the edge + */ + endArrow?: boolean; + /** + * 起始箭头的偏移量 + * + * Offset of the start arrow + */ + startArrowOffset?: number; + /** + * 结束箭头的偏移量 + * + * Offset of the end arrow + */ + endArrowOffset?: number; + /** + * 边的起点 shape + * + * The source shape. Represents the start of the edge + */ + sourceNode: Node; + /** + * 边的终点 shape + * + * The source shape. Represents the start of the edge + */ + targetNode: Node; + /** + * 边起始连接的 port + * + * The Port of the source node + */ + sourcePort?: string; + /** + * 边终点连接的 port + * + * The Port of the target node + */ + targetPort?: string; + /** + * 在 “起始点” 处添加一个标记图形,其中 “起始点” 为边与起始节点的交点 + * + * Add a marker graphic at the "start point", where the "start point" is the intersection of the edge and the source node + */ + markerStart?: DisplayObject | null; + /** + * 调整 “起始点” 处标记图形的位置,正偏移量向内,负偏移量向外 + * + * Adjust the position of the marker graphic at the "start point", positive offset inward, negative offset outward + * @defaultValue 0 + */ + markerStartOffset?: number; + /** + * 在 “终止点” 处添加一个标记图形,其中 “终止点” 为边与终止节点的交点 + * + * Add a marker graphic at the "end point", where the "end point" is the intersection of the edge and the target node + */ + markerEnd?: DisplayObject | null; + /** + * 调整 “终止点” 处标记图形的位置,正偏移量向内,负偏移量向外 + * + * Adjust the position of the marker graphic at the "end point", positive offset inward, negative offset outward + * @defaultValue 0 + */ + markerEndOffset?: number; + /** + * 在路径除了 “起始点” 和 “终止点” 之外的每一个顶点上放置标记图形。在内部实现中,由于我们会把路径中部分命令转换成 C 命令,因此这些顶点实际是三阶贝塞尔曲线的控制点 + * + * Place a marker graphic on each vertex of the path except for the "start point" and "end point". In the internal implementation, because we will convert some commands in the path to C commands, these vertices are actually the control points of the cubic Bezier curve + */ + markerMid?: DisplayObject | null; + /** + * 3D 场景中生效,始终朝向屏幕,因此线宽不受透视投影影像 + * + * Effective in 3D scenes, always facing the screen, so the line width is not affected by the perspective projection image + * @defaultValue false + */ + isBillboard?: boolean; +} type ParsedBaseEdgeStyleProps = Required; +/** + * 基础边元素 + * + * Base edge element + */ export abstract class BaseEdge extends BaseShape { public type = 'edge'; @@ -323,30 +372,3 @@ export abstract class BaseEdge extends BaseShape { return result; } } - -type SymbolName = 'triangle' | 'circle' | 'diamond' | 'vee' | 'rect' | 'triangleRect' | 'simple'; - -type EdgeArrowStyleProps = { - type?: SymbolName | SymbolFactor; - size?: Size; -} & PathStyleProps & - Omit & - Record; - -export type LoopStyleProps = { - /** - * 边的位置 - * The position of the edge - */ - placement?: LoopPlacement; - /** - * 指定是否顺时针绘制环 - * Specify whether to draw the loop clockwise - */ - clockwise?: boolean; - /** - * 从节点 keyShape 边缘到自环顶部的距离,用于指定自环的曲率,默认为宽度或高度的最大值 - * Determine the position from the edge of the node keyShape to the top of the self-loop, used to specify the curvature of the self-loop, the default value is the maximum of the width or height - */ - dist?: number; -}; diff --git a/packages/g6/src/elements/edges/cubic-horizontal.ts b/packages/g6/src/elements/edges/cubic-horizontal.ts index 5c68d03840..81b7e2fd8a 100644 --- a/packages/g6/src/elements/edges/cubic-horizontal.ts +++ b/packages/g6/src/elements/edges/cubic-horizontal.ts @@ -4,19 +4,33 @@ import type { Point } from '../../types'; import type { BaseEdgeStyleProps } from './base-edge'; import { Cubic } from './cubic'; +/** + * 水平方向的三次贝塞尔曲线样式配置项 + * + * Cubic Bezier curve style properties in horizontal direction + */ export interface CubicHorizontalStyleProps extends BaseEdgeStyleProps { /** * 控制点在两端点连线上的相对位置,范围为`0-1` + * * The relative position of the control point on the line, ranging from `0-1` + * @defaultValue [0.5, 0.5] */ curvePosition?: number | [number, number]; /** * 控制点距离两端点连线的距离,可理解为控制边的弯曲程度 + * * The distance of the control point from the line + * @defaultValue [0, 0] */ curveOffset?: number | [number, number]; } +/** + * 水平方向的三次贝塞尔曲线 + * + * Cubic Bezier curve in horizontal direction + */ export class CubicHorizontal extends Cubic { static defaultStyleProps: Partial = { curvePosition: [0.5, 0.5], diff --git a/packages/g6/src/elements/edges/cubic-vertical.ts b/packages/g6/src/elements/edges/cubic-vertical.ts index be1237d558..628aac6dd9 100644 --- a/packages/g6/src/elements/edges/cubic-vertical.ts +++ b/packages/g6/src/elements/edges/cubic-vertical.ts @@ -4,19 +4,33 @@ import type { Point } from '../../types'; import type { BaseEdgeStyleProps } from './base-edge'; import { Cubic } from './cubic'; +/** + * 垂直方向的三次贝塞尔曲线样式配置项 + * + * Cubic Bezier curve style properties in vertical direction + */ export interface CubicVerticalStyleProps extends BaseEdgeStyleProps { /** * 控制点在两端点连线上的相对位置,范围为`0-1` + * * The relative position of the control point on the line, ranging from `0-1` + * @defaultValue [0.5, 0.5] */ curvePosition?: number | [number, number]; /** * 控制点距离两端点连线的距离,可理解为控制边的弯曲程度 + * * The distance of the control point from the line + * @defaultValue [0, 0] */ curveOffset?: number | [number, number]; } +/** + * 垂直方向的三次贝塞尔曲线 + * + * Cubic Bezier curve in vertical direction + */ export class CubicVertical extends Cubic { static defaultStyleProps: Partial = { curvePosition: [0.5, 0.5], diff --git a/packages/g6/src/elements/edges/cubic.ts b/packages/g6/src/elements/edges/cubic.ts index e003f8d188..ec65ac1b4d 100644 --- a/packages/g6/src/elements/edges/cubic.ts +++ b/packages/g6/src/elements/edges/cubic.ts @@ -6,26 +6,41 @@ import { getCubicPath, getCurveControlPoint, parseCurveOffset, parseCurvePositio import type { BaseEdgeStyleProps } from './base-edge'; import { BaseEdge } from './base-edge'; +/** + * 三次贝塞尔曲线样式配置项 + * + * Cubic Bezier curve style properties + */ export interface CubicStyleProps extends BaseEdgeStyleProps { /** - * 控制点数组,用于定义曲线的形状。如果不指定,将会通过`curveOffset`和`curvePosition`来计算控制点 + * 控制点数组,用于定义曲线的形状。如果不指定,将会通过 `curveOffset` 和 `curvePosition` 来计算控制点 + * * Control points. Used to define the shape of the curve. If not specified, it will be calculated using `curveOffset` and `curvePosition`. */ controlPoints?: [Point, Point]; /** * 控制点在两端点连线上的相对位置,范围为`0-1` + * * The relative position of the control point on the line, ranging from `0-1` + * @defaultValue 0.5 */ curvePosition?: number | [number, number]; /** * 控制点距离两端点连线的距离,可理解为控制边的弯曲程度 + * * The distance of the control point from the line + * @defaultValue 20 */ curveOffset?: number | [number, number]; } type ParsedCubicStyleProps = Required; +/** + * 三次贝塞尔曲线 + * + * Cubic Bezier curve + */ export class Cubic extends BaseEdge { static defaultStyleProps: Partial = { curvePosition: 0.5, @@ -36,6 +51,9 @@ export class Cubic extends BaseEdge { super(deepMix({}, { style: Cubic.defaultStyleProps }, options)); } + /** + * @inheritdoc + */ protected getKeyPath(attributes: ParsedCubicStyleProps): PathArray { const [sourcePoint, targetPoint] = this.getEndpoints(attributes); const { controlPoints, curvePosition, curveOffset } = attributes; diff --git a/packages/g6/src/elements/edges/line.ts b/packages/g6/src/elements/edges/line.ts index 7c20825ad9..46d8d87f6e 100644 --- a/packages/g6/src/elements/edges/line.ts +++ b/packages/g6/src/elements/edges/line.ts @@ -4,12 +4,19 @@ import { deepMix } from '@antv/util'; import type { BaseEdgeStyleProps } from './base-edge'; import { BaseEdge } from './base-edge'; +/** + * 直线样式配置项 + * + * Line style properties + */ export type LineStyleProps = BaseEdgeStyleProps; type ParsedLineStyleProps = Required; /** - * Draw line based on BaseEdge, override drawKeyShape + * 直线 + * + * Line */ export class Line extends BaseEdge { static defaultStyleProps: Partial = {}; diff --git a/packages/g6/src/elements/edges/polyline.ts b/packages/g6/src/elements/edges/polyline.ts index 052530179f..74c7077489 100644 --- a/packages/g6/src/elements/edges/polyline.ts +++ b/packages/g6/src/elements/edges/polyline.ts @@ -1,44 +1,63 @@ import type { DisplayObjectConfig } from '@antv/g'; import type { PathArray } from '@antv/util'; import { deepMix } from '@antv/util'; -import type { Padding, Point, Port } from '../../types'; +import type { LoopStyleProps, Padding, Point, Port } from '../../types'; import { getBBoxHeight, getBBoxWidth, getNodeBBox } from '../../utils/bbox'; import { getPolylineLoopPath, getPolylinePath } from '../../utils/edge'; import { findPorts, getConnectionPoint, getPortPosition } from '../../utils/element'; import { subStyleProps } from '../../utils/prefix'; import { orth } from '../../utils/router/orth'; -import type { BaseEdgeStyleProps, LoopStyleProps } from './base-edge'; +import type { BaseEdgeStyleProps } from './base-edge'; import { BaseEdge } from './base-edge'; +/** + * 折线样式配置项 + * + * Polyline style properties + */ export interface PolylineStyleProps extends BaseEdgeStyleProps { /** * 圆角半径 + * * The radius of the rounded corner + * @defaultValue 0 */ radius?: number; /** * 控制点数组 + * * Control point array */ controlPoints?: Point[]; /** * 是否启用路由,默认开启且 controlPoints 会自动计入 + * * Whether to enable routing, it is enabled by default and controlPoints will be automatically included + * @defaultValue false */ router?: boolean; /** * 路由名称,目前支持 'orth' + * * Routing name, currently supports 'orth' + * @defaultValue 'orth' */ routerName?: 'orth'; /** * 节点边距 + * * Padding for routing calculation + * @defaultValue 10 */ routerPadding?: Padding; } type ParsedPolylineStyleProps = Required; +/** + * 折线 + * + * Polyline + */ export class Polyline extends BaseEdge { static defaultStyleProps: Partial = { radius: 0, diff --git a/packages/g6/src/elements/edges/quadratic.ts b/packages/g6/src/elements/edges/quadratic.ts index b57d8b7d1d..4a123d8782 100644 --- a/packages/g6/src/elements/edges/quadratic.ts +++ b/packages/g6/src/elements/edges/quadratic.ts @@ -6,26 +6,41 @@ import { getCurveControlPoint, getQuadraticPath } from '../../utils/edge'; import type { BaseEdgeStyleProps } from './base-edge'; import { BaseEdge } from './base-edge'; +/** + * 二次贝塞尔曲线样式配置项 + * + * Quadratic Bezier curve style properties + */ export interface QuadraticStyleProps extends BaseEdgeStyleProps { /** * 控制点,用于定义曲线的形状。如果不指定,将会通过`curveOffset`和`curvePosition`来计算控制点 + * * Control point. Used to define the shape of the curve. If not specified, it will be calculated using `curveOffset` and `curvePosition`. */ controlPoint?: Point; /** * 控制点在两端点连线上的相对位置,范围为`0-1` + * * The relative position of the control point on the line, ranging from `0-1` + * @defaultValue 0.5 */ curvePosition?: number; /** * 控制点距离两端点连线的距离,可理解为控制边的弯曲程度 + * * The distance of the control point from the line + * @defaultValue 30 */ curveOffset?: number; } type ParsedQuadraticStyleProps = Required; +/** + * 二次贝塞尔曲线 + * + * Quadratic Bezier curve + */ export class Quadratic extends BaseEdge { static defaultStyleProps: Partial = { curvePosition: 0.5, diff --git a/packages/g6/src/elements/nodes/html.ts b/packages/g6/src/elements/nodes/html.ts index a03cf19ff6..5e191f67e3 100644 --- a/packages/g6/src/elements/nodes/html.ts +++ b/packages/g6/src/elements/nodes/html.ts @@ -17,7 +17,7 @@ export type HTMLStyleProps = BaseNodeStyleProps<{ }>; /** - * @see https://github.com/antvis/G/blob/next/packages/g-lite/src/plugins/EventPlugin.ts + * @see https://github.com/antvis/G/blob/next/packages/g/src/plugins/EventPlugin.ts */ export class HTML extends BaseNode { static defaultStyleProps: Partial = { diff --git a/packages/g6/src/elements/shapes/base-shape.ts b/packages/g6/src/elements/shapes/base-shape.ts index 6358e2ea10..e5fb70a89f 100644 --- a/packages/g6/src/elements/shapes/base-shape.ts +++ b/packages/g6/src/elements/shapes/base-shape.ts @@ -7,7 +7,17 @@ import { updateStyle } from '../../utils/element'; import { setVisibility } from '../../utils/visibility'; export interface BaseShapeStyleProps extends BaseStyleProps { + /** + * x 坐标 + * + * x coordinate + */ x?: number | string; + /** + * y 坐标 + * + * y coordinate + */ y?: number | string; } diff --git a/packages/g6/src/elements/shapes/label.ts b/packages/g6/src/elements/shapes/label.ts index 6ce56beeea..406ab7fdab 100644 --- a/packages/g6/src/elements/shapes/label.ts +++ b/packages/g6/src/elements/shapes/label.ts @@ -4,15 +4,23 @@ import type { Padding } from '../../types/padding'; import type { PrefixObject } from '../../types/prefix'; import { parsePadding } from '../../utils/padding'; import { omitStyleProps, startsWith, subStyleProps } from '../../utils/prefix'; -import type { BaseShapeStyleProps } from './base-shape'; import { BaseShape } from './base-shape'; -export type LabelStyleProps = BaseShapeStyleProps & - TextStyleProps & { - background?: boolean; - } & PrefixObject & { - padding?: Padding; - }; +export interface LabelStyleProps extends TextStyleProps, PrefixObject { + /** + * 是否显示背景 + * + * Whether to show background + */ + background?: boolean; + /** + * 标签内边距 + * + * Label padding + * @defaultValue 0 + */ + padding?: Padding; +} type ParsedLabelStyleProps = Required; type LabelOptions = DisplayObjectConfig; diff --git a/packages/g6/src/exports.ts b/packages/g6/src/exports.ts index 3ba36517c8..806dc3062e 100644 --- a/packages/g6/src/exports.ts +++ b/packages/g6/src/exports.ts @@ -81,23 +81,6 @@ export { Shortcut } from './utils/shortcut'; export { parseSize } from './utils/size'; export { treeToGraphData } from './utils/tree'; -export type { BaseStyleProps } from '@antv/g'; -export type { - AntVDagreLayoutOptions, - CircularLayoutOptions, - ComboCombinedLayoutOptions, - ConcentricLayoutOptions, - D3Force3DLayoutOptions, - D3ForceLayoutOptions, - DagreLayoutOptions, - ForceAtlas2LayoutOptions, - ForceLayoutOptions, - FruchtermanLayoutOptions, - GridLayoutOptions, - MDSLayoutOptions, - RadialLayoutOptions, - RandomLayoutOptions, -} from '@antv/layout'; export type { BaseBehaviorOptions, BrushSelectOptions, @@ -134,7 +117,7 @@ export type { StarStyleProps, TriangleStyleProps, } from './elements/nodes'; -export type { BaseShapeStyleProps } from './elements/shapes'; +export type { BaseShapeStyleProps, LabelStyleProps } from './elements/shapes'; export type { BasePluginOptions, BubbleSetsOptions, @@ -174,6 +157,8 @@ export type { CornerPlacement, DirectionalPlacement, Edge, + EdgeArrowStyleProps, + EdgeLabelStyleProps, Element, ElementDatum, ElementType, @@ -188,6 +173,7 @@ export type { IPointerEvent, IViewportEvent, IWheelEvent, + LoopStyleProps, Node, Placement, Point, @@ -198,3 +184,21 @@ export type { ViewportAnimationEffectTiming, } from './types'; export type { ShortcutKey } from './utils/shortcut'; + +export type { + AntVDagreLayoutOptions, + CircularLayoutOptions, + ComboCombinedLayoutOptions, + ConcentricLayoutOptions, + D3Force3DLayoutOptions, + D3ForceLayoutOptions, + DagreLayoutOptions, + ForceAtlas2LayoutOptions, + ForceLayoutOptions, + FruchtermanLayoutOptions, + GridLayoutOptions, + MDSLayoutOptions, + RadialLayoutOptions, + RandomLayoutOptions, +} from '@antv/layout'; +export type { PathArray } from '@antv/util'; diff --git a/packages/g6/src/spec/canvas.ts b/packages/g6/src/spec/canvas.ts index 3160bec87d..acc47d86a1 100644 --- a/packages/g6/src/spec/canvas.ts +++ b/packages/g6/src/spec/canvas.ts @@ -1,4 +1,4 @@ -import type { CanvasConfig, IRenderer } from '@antv/g'; +import type { IRenderer } from '@antv/g'; import type { Canvas } from '../runtime/canvas'; /** @@ -7,7 +7,7 @@ import type { Canvas } from '../runtime/canvas'; * Canvas spec * @public */ -export interface CanvasOptions extends Pick { +export interface CanvasOptions { /** * 画布容器 * @@ -44,4 +44,10 @@ export interface CanvasOptions extends Pick { * canvas background color */ background?: string; + /** + * 设备像素比 + * + * device pixel ratio + */ + devicePixelRatio?: number; } diff --git a/packages/g6/src/types/edge.ts b/packages/g6/src/types/edge.ts index 90c8c8e530..bf55e0354b 100644 --- a/packages/g6/src/types/edge.ts +++ b/packages/g6/src/types/edge.ts @@ -1,39 +1,91 @@ -import { Line, Path, Polyline } from '@antv/g'; +import type { ImageStyleProps, Line, Path, PathStyleProps, Polyline } from '@antv/g'; +import type { PathArray } from '@antv/util'; import type { LabelStyleProps } from '../elements/shapes'; import type { CardinalPlacement, CornerPlacement } from './placement'; +import type { Size } from './size'; export type EdgeDirection = 'in' | 'out' | 'both'; export type EdgeKey = Line | Path | Polyline; -export type EdgeLabelPlacement = 'start' | 'center' | 'end' | number; - -export type EdgeLabelStyleProps = { +export interface EdgeLabelStyleProps extends LabelStyleProps { /** * 标签相对于边的位置。取值范围为 'start'、'center'、'end' 或特定比率(数字 0-1) + * * Label position relative to the edge (keyShape) that can be 'start', 'center', 'end' or a specific ratio (number 0-1) */ - placement?: EdgeLabelPlacement; + placement?: 'start' | 'center' | 'end' | number; /** * 标签平行于边的水平偏移量 + * * The horizontal offset of the label parallel to the edge */ offsetX?: number; /** * 标签垂直于边的垂直偏移量 + * * The vertical offset of the label perpendicular to the edge */ offsetY?: number; /** * 是否自动旋转以与边的方向对齐 + * * Indicates whether the label should automatically rotate to align with the edge's direction */ autoRotate?: boolean; /** * 文本的最大宽度,超出会裁减 + * * maxWidth of label text, which will be clipped if exceeded */ maxWidth?: string | number; -} & LabelStyleProps; +} + +export interface EdgeArrowStyleProps + extends PathStyleProps, + Omit, + Record { + /** + * 箭头大小 + * + * Arrow size + */ + size?: Size; + /** + * 箭头类型 + * + * Arrow type + */ + type?: + | 'triangle' + | 'circle' + | 'diamond' + | 'vee' + | 'rect' + | 'triangleRect' + | 'simple' + | ((width: number, height: number) => PathArray); +} export type LoopPlacement = CardinalPlacement | CornerPlacement; + +export interface LoopStyleProps { + /** + * 边的位置 + * + * The position of the edge + */ + placement?: LoopPlacement; + /** + * 指定是否顺时针绘制环 + * + * Specify whether to draw the loop clockwise + */ + clockwise?: boolean; + /** + * 从节点 keyShape 边缘到自环顶部的距离,用于指定自环的曲率,默认为宽度或高度的最大值 + * + * Determine the position from the edge of the node keyShape to the top of the self-loop, used to specify the curvature of the self-loop, the default value is the maximum of the width or height + */ + dist?: number; +} diff --git a/packages/g6/src/utils/edge.ts b/packages/g6/src/utils/edge.ts index 455c62be2f..6b10bc7e03 100644 --- a/packages/g6/src/utils/edge.ts +++ b/packages/g6/src/utils/edge.ts @@ -2,17 +2,7 @@ import type { AABB } from '@antv/g'; import type { PathArray } from '@antv/util'; import { isEqual, isNumber } from '@antv/util'; import type { EdgeData } from '../spec'; -import type { - EdgeKey, - EdgeLabelPlacement, - EdgeLabelStyleProps, - ID, - LoopPlacement, - Node, - Point, - Port, - Vector2, -} from '../types'; +import type { EdgeKey, EdgeLabelStyleProps, ID, LoopPlacement, Node, Point, Port, Vector2 } from '../types'; import { getBBoxHeight, getBBoxSize, getBBoxWidth, getNearestSideToPoint, getNodeBBox } from './bbox'; import { getAllPorts, getNodeConnectionPoint, getPortConnectionPoint, getPortPosition } from './element'; import { isCollinear, isHorizontal, moveTo, parsePoint } from './point'; @@ -32,7 +22,7 @@ import { add, distance, manhattanDistance, multiply, normalize, perpendicular, s */ export function getLabelPositionStyle( key: EdgeKey, - placement: EdgeLabelPlacement, + placement: EdgeLabelStyleProps['placement'], autoRotate: boolean, offsetX: number, offsetY: number, diff --git a/packages/site/.dumirc.ts b/packages/site/.dumirc.ts index c2ac709f7c..ac5ca0cb4b 100644 --- a/packages/site/.dumirc.ts +++ b/packages/site/.dumirc.ts @@ -143,28 +143,28 @@ export default defineConfig({ }, }, { - slug: 'api/element', + slug: 'apis/elements', title: { zh: 'Element - 元素', en: 'Element', }, }, { - slug: 'api/element/node', + slug: 'apis/elements/nodes', title: { zh: 'Node - 节点', en: 'Node', }, }, { - slug: 'api/element/edge', + slug: 'apis/elements/edges', title: { zh: 'Edge - 边', en: 'Edge', }, }, { - slug: 'api/element/combo', + slug: 'apis/elements/combos', title: { zh: 'Combo - 组合', en: 'Combo', diff --git a/packages/site/docs/apis/element/combo/default.en.md b/packages/site/docs/apis/element/combo/default.en.md deleted file mode 100644 index 95d8bccd51..0000000000 --- a/packages/site/docs/apis/element/combo/default.en.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: (Demo)Combo ---- \ No newline at end of file diff --git a/packages/site/docs/apis/element/combo/default.zh.md b/packages/site/docs/apis/element/combo/default.zh.md deleted file mode 100644 index f3f7994729..0000000000 --- a/packages/site/docs/apis/element/combo/default.zh.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: (示例)组合 ---- \ No newline at end of file diff --git a/packages/site/docs/apis/element/edge/default.en.md b/packages/site/docs/apis/element/edge/default.en.md deleted file mode 100644 index cc36d0c6b3..0000000000 --- a/packages/site/docs/apis/element/edge/default.en.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: (Demo)Edge ---- \ No newline at end of file diff --git a/packages/site/docs/apis/element/edge/default.zh.md b/packages/site/docs/apis/element/edge/default.zh.md deleted file mode 100644 index 4789e803b7..0000000000 --- a/packages/site/docs/apis/element/edge/default.zh.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: (示例)边 ---- \ No newline at end of file diff --git a/packages/site/docs/apis/element/node/default.en.md b/packages/site/docs/apis/element/node/default.en.md deleted file mode 100644 index 39fe98876d..0000000000 --- a/packages/site/docs/apis/element/node/default.en.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: (Demo)Node ---- \ No newline at end of file diff --git a/packages/site/docs/apis/element/node/default.zh.md b/packages/site/docs/apis/element/node/default.zh.md deleted file mode 100644 index 234adb2da3..0000000000 --- a/packages/site/docs/apis/element/node/default.zh.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: (示例)节点 ---- \ No newline at end of file diff --git a/packages/site/scripts/generate-api.ts b/packages/site/scripts/generate-api.ts index f35c789a46..8f75762fff 100644 --- a/packages/site/scripts/generate-api.ts +++ b/packages/site/scripts/generate-api.ts @@ -39,7 +39,7 @@ export function mangleScopedPackageName(packageName: string): string { const reportFolderRoot = path.resolve(path.join('support', 'api')); const reportTempFolderRoot = path.resolve(reportFolderRoot, 'temp'); -const ignorePackages = new Set(['@antv/g6-react-node', '@antv/g6-plugin-map-view', '@antv/g6-site']); +const ignorePackages = new Set(['@antv/g6-site', '@antv/g6-extension-3d']); /** * Get all typed packages. @@ -116,6 +116,7 @@ async function runApiExtractor() { // Make `export * from 'other-remirror-packages'` to work bundledPackages: [ ...Object.keys(pkg.packageJson.dependencies ?? {}), + ...Object.keys(pkg.packageJson.devDependencies ?? {}), ...Object.keys(pkg.packageJson.peerDependencies ?? {}), ].filter((name) => packageNameSet.has(name)), }; diff --git a/packages/site/src/MarkdownDocumenter.ts b/packages/site/src/MarkdownDocumenter.ts index c0c6bedb0f..368519322d 100644 --- a/packages/site/src/MarkdownDocumenter.ts +++ b/packages/site/src/MarkdownDocumenter.ts @@ -1,24 +1,19 @@ /* eslint-disable jsdoc/require-param */ -import type { - ApiEnum, - ApiItem, - ApiModel, - ApiNamespace, - ApiPackage, - Excerpt, - ExcerptToken, - IFindApiItemsResult, - IResolveDeclarationReferenceResult, -} from '@microsoft/api-extractor-model'; import { ApiAbstractMixin, ApiClass, ApiDeclaredItem, ApiDocumentedItem, + ApiEnum, ApiInitializerMixin, ApiInterface, + ApiItem, ApiItemKind, + ApiMethod, + ApiModel, + ApiNamespace, ApiOptionalMixin, + ApiPackage, ApiParameterListMixin, ApiPropertyItem, ApiProtectedMixin, @@ -27,7 +22,11 @@ import { ApiReturnTypeMixin, ApiStaticMixin, ApiTypeAlias, + Excerpt, + ExcerptToken, ExcerptTokenKind, + IFindApiItemsResult, + IResolveDeclarationReferenceResult, ReleaseTag, } from '@microsoft/api-extractor-model'; import { @@ -48,15 +47,14 @@ import { TSDocConfiguration, } from '@microsoft/tsdoc'; import { FileSystem, NewlineKind, PackageName } from '@rushstack/node-core-library'; +import { camelCase, isBoolean, upperFirst } from 'lodash'; import * as path from 'path'; - -import { camelCase, upperFirst } from 'lodash'; import prettier from 'prettier'; -import { getApiCategoryName } from './constants/api-category'; -import { Keyword, intl } from './constants/keywords'; -import { LocaleLanguage } from './constants/locale'; +import { Keyword, LocaleLanguage, getApiCategoryIntl, getHelperIntl, getKeywordIntl } from './constants'; +import { GLinks } from './constants/link'; import { CustomMarkdownEmitter } from './markdown/CustomMarkdownEmitter'; import { CustomDocNodes } from './nodes/CustomDocNodeKind'; +import { DocContainer } from './nodes/DocContainer'; import { DocDetails } from './nodes/DocDetails'; import { DocEmphasisSpan } from './nodes/DocEmphasisSpan'; import { DocHeading } from './nodes/DocHeading'; @@ -68,10 +66,18 @@ import { DocTableRow } from './nodes/DocTableRow'; import { DocUnorderedList } from './nodes/DocUnorderedList'; import { outputFolder, referenceFoldername } from './setting'; import { Utilities } from './utils/Utilities'; +import { + ICustomExcerptToken, + findAccessorExcerptTokens, + liftPrefixExcerptTokens, + parseExcerptTokens, +} from './utils/excerpt-token'; import { getBlockTagByName } from './utils/parser'; const supportedApiItems = [ApiItemKind.Interface, ApiItemKind.Enum, ApiItemKind.Class, ApiItemKind.TypeAlias]; +const hyperlinkReferences = ['BaseNodeStyleProps']; + /** * A page and its associated API items. */ @@ -82,8 +88,6 @@ export interface IPageData { } export interface ICollectedData { - // readonly pageGroups: readonly string[]; - // readonly fallbackGroup: string; readonly apiModel: ApiModel; /** * Page data keyed by page name. @@ -110,11 +114,12 @@ export class MarkdownDocumenter { private locale: LocaleLanguage = LocaleLanguage.EN; /** - * Whether the current page is a reference page. - * If true, the page will be generated in the reference folder. - * If false, the page will be generated in the root folder. + * The reference level of the current page, used to determine the heading level of the current page. + * 0: API Reference + * 1: Extension + * 2: Element Style Props */ - private isReference: boolean = true; + private referenceLevel = 0; public constructor(options: IMarkdownDocumenterOptions) { this._apiModel = options.apiModel; @@ -129,20 +134,27 @@ export class MarkdownDocumenter { const collectedData = this._initPageData(this._apiModel); // Write the API model page - this.isReference = true; + this.referenceLevel = 0; await this._generateBilingualPages(this._writeApiItemPage.bind(this), this._apiModel); // Write the API pages classified by extension for (const [_, pageData] of collectedData.pagesByName.entries()) { - // 对于交互和插件 + // 对于交互、插件、布局 if (['behaviors', 'plugins', 'layouts'].includes(pageData.group)) { - this.isReference = false; + this.referenceLevel = 1; await this._generateBilingualPages(this._writeExtensionPage.bind(this), pageData); } + // 对于元素 + if (['elements/nodes', 'elements/edges', 'elements/combos'].includes(pageData.group)) { + this.referenceLevel = 2; + await this._generateBilingualPages(this._writeElementPage.bind(this), pageData); + } + // 对于图实例,将拆分成三个页面: 配置项,实例方法,属性 // For graph instance, split into three pages: options, methods, properties if (pageData.group === 'runtime' && pageData.name === 'Graph') { + this.referenceLevel = 1; this._generateBilingualPages(this._writeGraphOptionsPage.bind(this), pageData); this._generateBilingualPages(this._writeGraphMethodsPage.bind(this), pageData); this._generateBilingualPages(this._writeGraphPropertiesPage.bind(this), pageData); @@ -173,10 +185,18 @@ export class MarkdownDocumenter { apiItem.fileUrlPath ) { const paths = apiItem.fileUrlPath?.split('/').slice(1); - let group = paths?.[0]; + // For elements, group by the first two levels of the path, such as `elements.nodes` + // For others, group by the first level of the path, such as `behaviors`, `plugins`, `layouts` + const topGroup = paths?.[0]; + let group = topGroup === 'elements' ? paths.slice(0, 2).join('/') : topGroup; const target = paths?.[paths.length - 1].replace(/\.d\.ts$/, ''); let pageName = upperFirst(camelCase(target === 'index' ? paths?.[paths.length - 2] : target)); + if (topGroup === 'elements' && paths[1] === 'combos') { + const elementType = upperFirst(paths[1].slice(0, -1)); + if (!pageName.endsWith(elementType)) pageName += elementType; + } + // Special handling for @antv/layout if (apiItem.fileUrlPath.includes('@antv/layout')) { group = 'layouts'; @@ -203,17 +223,6 @@ export class MarkdownDocumenter { } collectedData.pagesByApi.set(apiItem.displayName, pageData); pageData.apiItems.push(apiItem); - - // For interface, add the referenced interfaces to pageData.apiItems - if (apiItem instanceof ApiInterface) { - const refApiItems = apiItem.excerptTokens - .filter((token) => token.kind === ExcerptTokenKind.Reference && token.canonicalReference) - .map( - (token) => this._apiModel.resolveDeclarationReference(token.canonicalReference!, undefined).resolvedApiItem, - ) - .filter(Boolean) as ApiItem[]; - pageData.apiItems.unshift(...refApiItems); - } } for (const memberApiItem of apiItem.members) { @@ -221,6 +230,9 @@ export class MarkdownDocumenter { } } + /** + * Generate bilingual pages + */ private async _generateBilingualPages(func: (params: T) => Promise, params: T) { const languages = [LocaleLanguage.EN, LocaleLanguage.ZH]; @@ -323,7 +335,7 @@ export class MarkdownDocumenter { ]), ); } - this._appendSummarySection(output, apiItem); + this._writeSummarySection(output, apiItem); } } @@ -409,6 +421,8 @@ export class MarkdownDocumenter { const filename: string = path.join(this._outputFolder, referenceFoldername, this._getFilenameForApiItem(apiItem)); + if (filename.startsWith('index')) return; + await this._writeFile(filename, output, apiItem); } @@ -419,19 +433,20 @@ export class MarkdownDocumenter { this._appendPageTitle(output, pageData.name); const apiClass = pageData.apiItems.find((apiItem) => apiItem instanceof ApiClass) as ApiClass; + const apiInterface = pageData.apiItems.find((apiItem) => apiItem instanceof ApiInterface) as ApiInterface; if (apiClass) { - this._appendSummarySection(output, apiClass); + this._writeSummarySection(output, apiClass); + this._writeRemarksSection(output, apiClass); } - const apiInterfaces = pageData.apiItems.filter((apiItem) => apiItem instanceof ApiInterface) as ApiInterface[]; - if (apiInterfaces.length > 0) - apiInterfaces.forEach((apiInterface, i) => this._writeOptions(output, apiInterface, { showTitle: i === 0 })); + if (apiInterface) { + this._writeOptions(output, apiInterface, { includeExcerptTokens: true }); + } if (apiClass) this._writeAPIMethods(output, apiClass); - const lang = this.locale === LocaleLanguage.EN ? 'en' : 'zh'; - const filename: string = path.join(this._outputFolder, pageData.group, `${pageData.name}.${lang}.md`); + const filename: string = path.join(this._outputFolder, pageData.group, `${pageData.name}.${this._getLang()}.md`); await this._writeFile( filename, @@ -440,75 +455,327 @@ export class MarkdownDocumenter { ); } - private _writeOptions(output: DocSection, apiItem: ApiInterface | ApiClass, options?: { showTitle?: boolean }) { - const { showTitle = true } = options || {}; + private async _writeElementPage(pageData: IPageData) { const configuration: TSDocConfiguration = this._tsdocConfiguration; - showTitle && output.appendNode(new DocHeading({ configuration, title: this._intl(Keyword.OPTIONS) })); + const output: DocSection = new DocSection({ configuration }); - const apiMembers: readonly ApiItem[] = this._getMembersAndWriteIncompleteWarning(apiItem, output); + this._appendPageTitle(output, pageData.name); - for (const apiMember of apiMembers) { - const isInherited: boolean = apiMember.parent !== apiItem; - if ( - (apiMember.kind === ApiItemKind.Property || apiMember.kind === ApiItemKind.PropertySignature) && - apiMember instanceof ApiPropertyItem - ) { - // property name - output.appendNode( - new DocHeading({ - configuration, - title: Utilities.getConciseSignature(apiMember), - level: 2, - }), - ); + const apiInterface = pageData.apiItems.find((apiItem) => apiItem instanceof ApiInterface) as ApiInterface; + const apiClass = pageData.apiItems.find((apiItem) => apiItem instanceof ApiClass) as ApiClass; - // property type, optional, readonly, default value - const isOptional = ApiOptionalMixin.isBaseClassOf(apiMember) && apiMember.isOptional; + const isBase = pageData.name.startsWith('Base'); - const nodes: DocNode[] = [ - // property type - new DocEmphasisSpan({ configuration, bold: true }, [ - ...this._createParagraphForTypeExcerpt(apiMember.propertyTypeExcerpt).getChildNodes(), - new DocPlainText({ configuration, text: ' ' }), - ]), - // optional - new DocEmphasisSpan({ configuration, italic: true }, [ + if (apiClass && !isBase) { + this._writeSummarySection(output, apiClass); + this._writeRemarksSection(output, apiClass); + } + + if (apiInterface) { + this._writeElementOptions(output, apiInterface, pageData, isBase); + } + + const filename: string = path.join(this._outputFolder, pageData.group, `${pageData.name}.${this._getLang()}.md`); + + await this._writeFile(filename, output); + } + + /** + * Special for Element options, which needs to handle `Prefix` + */ + private _writeElementOptions( + output: DocSection, + apiInterface: ApiInterface, + pageData: IPageData, + includeExcerptTokens: boolean, + ) { + const configuration: TSDocConfiguration = this._tsdocConfiguration; + + if (!includeExcerptTokens) { + const elementType = pageData.group.split('/')[1].slice(0, -1); + const baseStyleFileName = upperFirst(camelCase(`base ${elementType}`)); + output.appendNode( + new DocContainer({ configuration, status: 'info', title: '说明' }, [ + new DocParagraph({ configuration }, [ new DocPlainText({ configuration, - text: isOptional ? 'Optional' : 'Required', + text: + getHelperIntl('basePropsStyleHelper', this.locale) + `(./${baseStyleFileName}.${this._getLang()}.md)`, }), - new DocPlainText({ configuration, text: ' ' }), ]), - ]; + ]), + ); + } - if (apiMember instanceof ApiDocumentedItem) { - if (apiMember.tsdocComment !== undefined) { - const defaultValueBlock: DocBlock | undefined = apiMember.tsdocComment.customBlocks?.find( - (x) => x.blockTag.tagNameWithUpperCase === StandardTags.defaultValue.tagNameWithUpperCase, + this._writeOptions(output, apiInterface, { showTitle: false, includeExcerptTokens }); + } + + private _getLinkFromExcerptToken(excerptToken: ICustomExcerptToken): string | undefined { + return this._getLinkFromGLinks(excerptToken) || this._getLinkFromCanonicalReference(excerptToken); + } + + private _getLinkFromGLinks(excerptToken: ICustomExcerptToken): string | undefined { + return GLinks[excerptToken.text]; + } + + private _getLinkFromCanonicalReference(excerptToken: ICustomExcerptToken): string | undefined { + let link: string | undefined = undefined; + + if (excerptToken.canonicalReference) { + const apiItemResult = this._apiModel.resolveDeclarationReference( + excerptToken.canonicalReference, + undefined, + ).resolvedApiItem; + + if (apiItemResult && apiItemResult instanceof ApiInterface) { + link = this._getLinkFilenameForApiItem(apiItemResult); + } + } + + return link; + } + + private _writeOptions( + output: DocSection, + apiItem: ApiInterface | ApiClass, + options?: { showTitle?: boolean; prefix?: string; includeExcerptTokens?: boolean }, + ) { + const configuration: TSDocConfiguration = this._tsdocConfiguration; + const { showTitle = true, prefix = '', includeExcerptTokens } = options || {}; + const apiMembers: readonly ApiItem[] = this._getMembersAndWriteIncompleteWarning(apiItem, output); + + if (showTitle && apiMembers.length > 0) { + output.appendNode(new DocHeading({ configuration, title: this._intl(Keyword.OPTIONS) })); + } + + if (includeExcerptTokens && this._isApiInterface(apiItem)) { + const filteredTokens = this._extractAndFilterExcerptTokens(apiItem as ApiInterface); + this._writeExcerptTokens(output, filteredTokens); + } + + apiMembers.forEach((apiMember) => { + if (this._isApiProperty(apiMember)) { + this._writePropertySections(output, apiMember, prefix, configuration); + } + }); + + if (includeExcerptTokens && apiItem instanceof ApiInterface) { + const liftedTokens = this._liftPrefixExcerptTokens(apiItem); + this._writeExcerptTokens(output, liftedTokens); + } + } + + private _extractAndFilterExcerptTokens(apiItem: ApiInterface): ICustomExcerptToken[] { + const excerptTokens = parseExcerptTokens(this._apiModel, apiItem); + return excerptTokens.filter((token) => token.type !== 'Prefix'); + } + + private _writePropertySections( + output: DocSection, + apiMember: ApiPropertyItem, + prefix: string, + configuration: TSDocConfiguration, + ): void { + const name = Utilities.getConciseSignature(apiMember); + const isRequired = + ApiOptionalMixin.isBaseClassOf(apiMember) && isBoolean(apiMember.isOptional) && !apiMember.isOptional; + const title = prefix ? camelCase(`${prefix} ${name}`) : name; + + output.appendNode( + new DocHeading({ + configuration, + title, + level: 2, + prefix: isRequired ? 'Required' : '', + }), + ); + + // write property type + const propertyContent: DocNode[] = [ + new DocEmphasisSpan({ configuration, italic: true }, [ + ...this._createParagraphForTypeExcerpt(apiMember.propertyTypeExcerpt).getChildNodes(), + new DocPlainText({ configuration, text: ' ' }), + ]), + ]; + + // write @defaultValue tag + if (apiMember instanceof ApiDocumentedItem) { + if (apiMember.tsdocComment !== undefined) { + const defaultValueBlock: DocBlock | undefined = apiMember.tsdocComment.customBlocks?.find( + (x) => x.blockTag.tagNameWithUpperCase === StandardTags.defaultValue.tagNameWithUpperCase, + ); + + if (defaultValueBlock) { + propertyContent.push( + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ + configuration, + text: 'Default: ', + }), + ]), + this._createStaticCode(defaultValueBlock.content.nodes[0] as DocSection), + ); + } + } + } + + output.appendNode(new DocNoteBox({ configuration }, [new DocParagraph({ configuration }, propertyContent)])); + + this._writeSummarySection(output, apiMember); + this._writeRemarksSection(output, apiMember); + } + + private _isApiProperty(apiItem: ApiItem): apiItem is ApiPropertyItem { + return apiItem.kind === ApiItemKind.Property || apiItem.kind === ApiItemKind.PropertySignature; + } + + private _isApiInterface(apiItem: ApiItem): apiItem is ApiInterface { + return apiItem.kind === ApiItemKind.Interface; + } + + private _liftPrefixExcerptTokens(apiItem: ApiInterface): ICustomExcerptToken[] { + const excerptTokens = parseExcerptTokens(this._apiModel, apiItem); + return liftPrefixExcerptTokens(excerptTokens); + } + + private _writeExcerptTokens(output: DocSection, customExcerptTokens: ICustomExcerptToken[]) { + const configuration: TSDocConfiguration = this._tsdocConfiguration; + + customExcerptTokens.forEach((customExcerptToken) => { + if (hyperlinkReferences.includes(customExcerptToken.text)) { + delete customExcerptToken.children; + delete customExcerptToken.interface; + } + }); + + for (const customExcerptToken of customExcerptTokens) { + const textNodes: DocPlainText[] = []; + + if (customExcerptToken.type === 'Prefix') { + const link = this._getLinkFromExcerptToken(customExcerptToken); + const linkMd = link ? `[${customExcerptToken.text}](${link})` : customExcerptToken.text; + output.appendNode( + new DocHeading({ + configuration: this._tsdocConfiguration, + title: `Prefix<${customExcerptToken.prefix}, ${linkMd}>`, + escaped: false, + }), + ); + if (!customExcerptToken.interface) { + textNodes.push( + new DocPlainText({ + configuration, + text: linkMd, + }), + ); + } + } + + const flattenTokens = (tokens: ICustomExcerptToken[]): ICustomExcerptToken[] => { + return tokens.flatMap((token) => { + if (Array.isArray(token.children) && token.children.length > 0) { + return flattenTokens(token.children); + } else { + return [token]; + } + }); + }; + + const formattedTokens = flattenTokens([customExcerptToken]).filter((token) => token.type !== 'Prefix'); + formattedTokens.forEach((token) => { + const link = this._getLinkFromExcerptToken(token); + const linkMd = link ? `[${token.text}](${link})` : token.text; + switch (token.type) { + case 'Omit': + case 'Pick': { + const fieldString = token.fields.join(','); + const helper = (key: string) => + linkMd + + this._intl(Keyword.LEFT_PARENTHESIS) + + getHelperIntl(key, this.locale) + + ' ' + + fieldString + + ' ' + + this._intl(Keyword.RIGHT_PARENTHESIS); + const text = token.type === 'Omit' ? helper('excludes') : helper('includes'); + textNodes.push( + new DocPlainText({ + configuration, + text, + }), ); - - if (defaultValueBlock) { - nodes.push( - // default value - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ - configuration, - text: 'Default: ', - }), - ]), - this._createStaticCode(defaultValueBlock.content.nodes[0] as DocSection), - ); + break; + } + case 'Default': { + if (!token.interface) { + const textNode = new DocPlainText({ + configuration, + text: linkMd, + }); + textNodes.push(textNode); } + break; + } + default: + break; + } + }); + + const content: DocParagraph[] = []; + + if (textNodes.length > 0) { + content.push( + new DocParagraph({ configuration }, [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: this._intl(Keyword.EXTENDS) + this._intl(Keyword.COLON) }), + ]), + new DocUnorderedList({ configuration }, textNodes), + ]), + ); + } + + if (customExcerptToken.type === 'Prefix') { + content.push( + new DocParagraph({ configuration }, [ + new DocPlainText({ + configuration, + text: getHelperIntl('prefixHelper', this.locale) + `(../../reference/g6.prefix.${this._getLang()}.md)`, + }), + ]), + ); + } + + if (content.length > 0) { + output.appendNode(new DocContainer({ configuration, status: 'info' }, content)); + } + + const cache = new Set(); + + if ( + (customExcerptToken.type === 'Prefix' || customExcerptToken.type === 'Default') && + customExcerptToken.interface + ) { + if (cache.has(customExcerptToken.interface.name)) continue; + this._writeOptions(output, customExcerptToken.interface, { + showTitle: false, + prefix: 'prefix' in customExcerptToken ? customExcerptToken.prefix : '', + }); + cache.add(customExcerptToken.interface.name); + } + + formattedTokens.forEach((newToken) => { + const accessors = findAccessorExcerptTokens(newToken, false); + for (const _token of accessors) { + if (_token.interface) { + if (cache.has(_token.interface.name)) continue; + this._writeOptions(output, _token.interface, { + showTitle: false, + prefix: (customExcerptToken as any).prefix, + }); + cache.add(_token.interface.name); } } - - output.appendNode(new DocNoteBox({ configuration }, [new DocParagraph({ configuration }, nodes)])); - - // summary - this._appendSummarySection(output, apiMember); - // remarks - this._writeRemarksSection(output, apiMember); - } + }); } } @@ -538,98 +805,90 @@ export class MarkdownDocumenter { groupMembers[apiCategory] ||= []; groupMembers[apiCategory].push(apiMember); } - if (!ApiCategoryDefined) { - groupMembers['undeclared'] ||= []; - groupMembers['undeclared'].push(apiMember); + } + if (!ApiCategoryDefined) { + groupMembers['undeclared'] ||= []; + groupMembers['undeclared'].push(apiMember); + } + } + } + } + + if (apiMembers.length > 0 && showTitle) output.appendNode(new DocHeading({ configuration, title: 'API' })); + + Object.entries(groupMembers).forEach(([category, apiMembers]) => { + if (category !== 'undeclared' && showSubTitle) { + const title = getApiCategoryIntl(category, this.locale); + output.appendNode(new DocHeading({ configuration, title, level: 1 })); + } + if (apiMembers.length > 0) { + for (const apiMember of apiMembers) { + switch (apiMember.kind) { + case ApiItemKind.Method: { + output.appendNode( + new DocHeading({ + configuration, + title: Utilities.getConciseSignature(apiMember, true), + level: 2, + suffix: (apiMember as ApiMethod).overloadIndex > 1 ? 'overload' : '', + }), + ); + + if (apiMember instanceof ApiDocumentedItem) { + if (apiMember.tsdocComment !== undefined) { + this._appendSection( + output, + this._localizeSection(apiMember.tsdocComment.summarySection, this.locale), + ); + } + } + if (apiMember instanceof ApiDeclaredItem) { + if (apiMember.excerpt.text.length > 0) { + output.appendNode( + new DocFencedCode({ + configuration, + code: apiMember.getExcerptWithModifiers(), + language: 'typescript', + }), + ); + } + this._writeHeritageTypes(output, apiMember); + } + + const detailSection = new DocSection({ configuration }); + + const hasParameterAndReturn = this._writeParameterTables( + detailSection, + apiMember as ApiParameterListMixin, + { showTitle: false }, + ); + + if (hasParameterAndReturn) { + output.appendNode( + new DocDetails({ configuration }, this._intl(Keyword.VIEW_PARAMETERS), detailSection.nodes), + ); + } + break; } } } } - - if (apiMembers.length > 0 && showTitle) output.appendNode(new DocHeading({ configuration, title: 'API' })); - - Object.entries(groupMembers).forEach(([category, apiMembers]) => { - if (category !== 'undeclared' && showSubTitle) { - const title = getApiCategoryName(category, this.locale); - output.appendNode(new DocHeading({ configuration, title, level: 1 })); - } - if (apiMembers.length > 0) { - for (const apiMember of apiMembers) { - switch (apiMember.kind) { - case ApiItemKind.Method: { - output.appendNode( - new DocHeading({ - configuration, - title: Utilities.getConciseSignature(apiMember, true), - level: 2, - }), - ); - - if (apiMember instanceof ApiDocumentedItem) { - if (apiMember.tsdocComment !== undefined) { - this._appendSection( - output, - this._localizeSection(apiMember.tsdocComment.summarySection, this.locale), - ); - } - } - if (apiMember instanceof ApiDeclaredItem) { - if (apiMember.excerpt.text.length > 0) { - output.appendNode( - new DocFencedCode({ - configuration, - code: apiMember.getExcerptWithModifiers(), - language: 'typescript', - }), - ); - } - this._writeHeritageTypes(output, apiMember); - } - - const detailSection = new DocSection({ configuration }); - - const hasParameterAndReturn = this._writeParameterTables( - detailSection, - apiMember as ApiParameterListMixin, - { showTitle: false }, - ); - - if (hasParameterAndReturn) { - output.appendNode( - new DocDetails({ configuration }, this._intl(Keyword.VIEW_PARAMETERS), detailSection.nodes), - ); - } - - this._writeRemarksSection(output, apiMember); - - break; - } - } - } - } - }); - } + }); } - private async _writeGraphOptionsPage(pageData: IPageData) { const configuration: TSDocConfiguration = this._tsdocConfiguration; const output: DocSection = new DocSection({ configuration }); this._appendPageTitle(output, 'GraphOptions', 0); - const apiInterfaces = pageData.apiItems.filter((apiItem) => apiItem instanceof ApiInterface) as ApiInterface[]; + const apiInterface = pageData.apiItems.find((apiItem) => apiItem instanceof ApiInterface) as ApiInterface; - const topApiInterface = apiInterfaces.find((apiInterface) => apiInterface.name === 'GraphOptions'); - - if (topApiInterface) { - this._writeRemarksSection(output, topApiInterface); + if (apiInterface) { + this._writeRemarksSection(output, apiInterface); + this._writeOptions(output, apiInterface, { showTitle: false, includeExcerptTokens: true }); } - if (apiInterfaces.length > 0) - apiInterfaces.forEach((apiInterface) => this._writeOptions(output, apiInterface, { showTitle: false })); - - const lang = this.locale === LocaleLanguage.EN ? 'en' : 'zh'; - const filename: string = path.join(this._outputFolder, 'graph', `option.${lang}.md`); + const filename: string = path.join(this._outputFolder, 'graph', `option.${this._getLang()}.md`); await this._writeFile(filename, output); } @@ -644,8 +903,7 @@ export class MarkdownDocumenter { if (apiClass) this._writeAPIMethods(output, apiClass, { showTitle: false, showSubTitle: true }); - const lang = this.locale === LocaleLanguage.EN ? 'en' : 'zh'; - const filename: string = path.join(this._outputFolder, 'graph', `method.${lang}.md`); + const filename: string = path.join(this._outputFolder, 'graph', `method.${this._getLang()}.md`); await this._writeFile(filename, output); } @@ -660,8 +918,7 @@ export class MarkdownDocumenter { if (apiClass) this._writeOptions(output, apiClass, { showTitle: false }); - const lang = this.locale === LocaleLanguage.EN ? 'en' : 'zh'; - const filename: string = path.join(this._outputFolder, 'graph', `property.${lang}.md`); + const filename: string = path.join(this._outputFolder, 'graph', `property.${this._getLang()}.md`); await this._writeFile(filename, output); } @@ -848,7 +1105,15 @@ export class MarkdownDocumenter { let exampleNumber: number = 1; for (const exampleBlock of exampleBlocks) { const heading: string = this._intl(Keyword.EXAMPLE) + (exampleBlocks.length > 1 ? ' ' + exampleNumber : ''); - output.appendNode(new DocHeading({ configuration, title: heading })); + const exampleTitle = + this.referenceLevel === 0 + ? new DocHeading({ configuration, title: heading }) + : new DocParagraph({ configuration }, [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: heading }), + ]), + ]); + output.appendNode(exampleTitle); this._appendSection(output, exampleBlock.content); @@ -931,32 +1196,32 @@ export class MarkdownDocumenter { const enumerationsTable: DocTable = new DocTable({ configuration, - headerTitles: ['Enumeration', this._intl(Keyword.DESCRIPTION)], + headerTitles: [this._intl(Keyword.ENUMERATION), this._intl(Keyword.DESCRIPTION)], }); const functionsTable: DocTable = new DocTable({ configuration, - headerTitles: ['Function', this._intl(Keyword.DESCRIPTION)], + headerTitles: [this._intl(Keyword.FUNCTION), this._intl(Keyword.DESCRIPTION)], }); const interfacesTable: DocTable = new DocTable({ configuration, - headerTitles: ['Interface', this._intl(Keyword.DESCRIPTION)], + headerTitles: [this._intl(Keyword.INTERFACE), this._intl(Keyword.DESCRIPTION)], }); const namespacesTable: DocTable = new DocTable({ configuration, - headerTitles: ['Namespace', this._intl(Keyword.DESCRIPTION)], + headerTitles: [this._intl(Keyword.NAMESPACE), this._intl(Keyword.DESCRIPTION)], }); const variablesTable: DocTable = new DocTable({ configuration, - headerTitles: ['Variable', this._intl(Keyword.DESCRIPTION)], + headerTitles: [this._intl(Keyword.VARIABLE), this._intl(Keyword.DESCRIPTION)], }); const typeAliasesTable: DocTable = new DocTable({ configuration, - headerTitles: ['Type Alias', this._intl(Keyword.DESCRIPTION)], + headerTitles: [this._intl(Keyword.TYPE_ALIAS), this._intl(Keyword.DESCRIPTION)], }); const apiMembers: ReadonlyArray = @@ -1113,7 +1378,7 @@ export class MarkdownDocumenter { const constructorsTable: DocTable = new DocTable({ configuration, - headerTitles: ['Constructor', this._intl(Keyword.MODIFIERS), this._intl(Keyword.DESCRIPTION)], + headerTitles: [this._intl(Keyword.CONSTRUCTOR), this._intl(Keyword.MODIFIERS), this._intl(Keyword.DESCRIPTION)], }); const propertiesTable: DocTable = new DocTable({ @@ -1544,6 +1809,22 @@ export class MarkdownDocumenter { } } + private _parseTypeAliasTokens(excerptTokens: ExcerptToken[]): ExcerptToken[] { + const tokens = excerptTokens.flatMap((token) => { + if (token.kind === ExcerptTokenKind.Reference && token.canonicalReference) { + const apiItemResult: IResolveDeclarationReferenceResult = this._apiModel.resolveDeclarationReference( + token.canonicalReference, + undefined, + ); + if (apiItemResult.resolvedApiItem instanceof ApiTypeAlias) { + return this._parseTypeAliasTokens(apiItemResult.resolvedApiItem.excerptTokens.slice(1, -1)); + } + } + return token; + }); + return tokens; + } + private _appendExcerptTokenWithHyperlinks(docNodeContainer: DocNodeContainer, token: ExcerptToken): void { const configuration: TSDocConfiguration = this._tsdocConfiguration; @@ -1560,6 +1841,18 @@ export class MarkdownDocumenter { ); if (apiItemResult.resolvedApiItem) { + if (apiItemResult.resolvedApiItem.kind === ApiItemKind.TypeAlias && this.referenceLevel > 0) { + const excerptTokens = (apiItemResult.resolvedApiItem as ApiTypeAlias).excerptTokens.slice(1, -1); + const typeAliasTokens = this._parseTypeAliasTokens(excerptTokens); + // If the type alias is a simple single-line type alias, then render it as plain text + // Otherwise, render it as a hyperlink + if (typeAliasTokens.every((token) => !token.text.includes('\n') && !token.text.includes('\r'))) { + return docNodeContainer.appendNode( + new DocPlainText({ configuration, text: typeAliasTokens.map((token) => token.text).join(' ') }), + ); + } + } + docNodeContainer.appendNode( new DocLinkTag({ configuration, @@ -1629,7 +1922,7 @@ export class MarkdownDocumenter { if (apiItem instanceof ApiDocumentedItem) { if (apiItem.tsdocComment !== undefined) { - this._appendSummarySection(section, apiItem); + this._writeSummarySection(section, apiItem); } } @@ -1745,14 +2038,7 @@ export class MarkdownDocumenter { private _writeBreadcrumb(output: DocSection, apiItem: ApiItem): void { const configuration: TSDocConfiguration = this._tsdocConfiguration; - output.appendNodeInParagraph( - new DocLinkTag({ - configuration, - tagName: '@link', - linkText: 'Home', - urlDestination: this._getLinkFilenameForApiItem(this._apiModel), - }), - ); + let init = true; for (const hierarchyItem of apiItem.getHierarchy()) { switch (hierarchyItem.kind) { @@ -1766,7 +2052,7 @@ export class MarkdownDocumenter { output.appendNodesInParagraph([ new DocPlainText({ configuration, - text: ' > ', + text: !init ? ' > ' : '', }), new DocLinkTag({ configuration, @@ -1775,6 +2061,7 @@ export class MarkdownDocumenter { urlDestination: this._getLinkFilenameForApiItem(hierarchyItem), }), ]); + init = false; } } } @@ -1852,7 +2139,6 @@ export class MarkdownDocumenter { private _appendStaticCodeNode(output: DocSection, docSection: DocSection): void { const content = this._extractContentFromSection(docSection); - output.appendNodeInParagraph( new DocCodeSpan({ configuration: this._tsdocConfiguration, @@ -1867,36 +2153,75 @@ export class MarkdownDocumenter { output.appendNode(new DocPageTitle({ configuration, key, locale: this.locale, order })); } - private _appendSummarySection(output: DocSection, apiItem: ApiItem): void { + /** + * Handle text nodes that contain a dash, should be treated as a unordered list + */ + private _handleTextNodes(node: DocNode): DocNode[] { + const configuration: TSDocConfiguration = this._tsdocConfiguration; + + if (node instanceof DocPlainText && node.text.includes(' - ')) { + const texts = node.text.split(/ - /g); + return [ + new DocPlainText({ configuration, text: texts[0] }), + ...(texts.slice(1) || []).flatMap((text) => [ + new DocPlainText({ configuration, text: '-' }), + new DocPlainText({ configuration, text }), + ]), + ]; + } + return [node]; + } + + private _parseParagraph(paragraph: DocParagraph): DocNode[][] { + const formattedNodes = paragraph.getChildNodes().flatMap((node) => this._handleTextNodes(node)); + + return formattedNodes.reduce((acc: DocNode[][], node: DocNode) => { + if (node instanceof DocPlainText && node.text === '-') { + acc.push([]); + } else if (acc.length) { + acc[acc.length - 1].push(node); + } else { + acc.push([node]); + } + return acc; + }, []); + } + + private _handleHtmlStartTag(ps: DocNode[], configuration: TSDocConfiguration, formattedSection: DocSection): boolean { + if (ps.length > 0 && ps[0] instanceof DocHtmlStartTag) { + const [_htmlStartTag, ...rest] = ps; + formattedSection.appendNode(new DocParagraph({ configuration }, rest)); + return true; + } + return false; + } + + private _writeSummarySection(output: DocSection, apiItem: ApiItem): void { const configuration: TSDocConfiguration = this._tsdocConfiguration; if (apiItem instanceof ApiDocumentedItem) { if (apiItem.tsdocComment !== undefined) { const localizedSection: DocSection = this._localizeSection(apiItem.tsdocComment.summarySection, this.locale); + const formattedSection = new DocSection({ configuration }); for (const nodes of localizedSection.getChildNodes()) { - for (const node of nodes.getChildNodes()) { - if (node instanceof DocPlainText) { - const texts = node.text.split(/ - /g); - const listText = texts.slice(1); + const paragraphs = this._parseParagraph(nodes as DocParagraph); - formattedSection.appendNode( - new DocParagraph({ configuration }, [new DocPlainText({ configuration, text: texts[0] })]), - ); + const latest: DocNode[][] = []; - if (listText.length > 0) { - formattedSection.appendNode( - new DocParagraph({ configuration }, [ - new DocUnorderedList( - { configuration }, - listText.map((text) => new DocPlainText({ configuration, text })), - ), - ]), - ); - } + for (const ps of paragraphs) { + if (!this._handleHtmlStartTag(ps, configuration, formattedSection)) { + latest.push(ps); } } + + formattedSection.appendNode( + new DocUnorderedList( + { configuration }, + latest.map((nodes) => new DocParagraph({ configuration }, nodes)), + ), + ); } this._appendAndMergeSection(output, formattedSection); @@ -1940,10 +2265,8 @@ export class MarkdownDocumenter { } private _getFilenameForApiItem(apiItem: ApiItem): string { - const lang = this.locale === LocaleLanguage.EN ? 'en' : 'zh'; - if (apiItem.kind === ApiItemKind.Model) { - return `index.${lang}.md`; + return `index.${this._getLang()}.md`; } let baseName: string = ''; @@ -1953,6 +2276,7 @@ export class MarkdownDocumenter { if (ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { if (hierarchyItem.overloadIndex > 1) { // Subtract one for compatibility with earlier releases of API Documenter. + // (This will get revamped when we fix GitHub issue #1308) qualifiedName += `_${hierarchyItem.overloadIndex - 1}`; } } @@ -1969,15 +2293,20 @@ export class MarkdownDocumenter { baseName += '.' + qualifiedName; } } - return baseName + `.${lang}.md`; + return baseName + `.${this._getLang()}.md`; } private _getLinkFilenameForApiItem(apiItem: ApiItem): string { - const prefix = !this.isReference ? '../reference/' : './'; + const relativeUrl: Record = { + 0: './', + 1: '../reference/', + 2: '../../reference/', + }; + const prefix = relativeUrl[this.referenceLevel]; return prefix + this._getFilenameForApiItem(apiItem); } - private _localizeSection(section: DocSection, language: 'zh-CN' | 'en-US'): DocSection { + private _localizeSection(section: DocSection, language: LocaleLanguage): DocSection { const configuration: TSDocConfiguration = this._tsdocConfiguration; const trimmed: DocSection = new DocSection({ configuration }); @@ -1986,9 +2315,14 @@ export class MarkdownDocumenter { for (const node of section.getChildNodes()) { if (node instanceof DocParagraph) { for (const childNode of node.getChildNodes()) { - if (childNode instanceof DocHtmlStartTag) { + if ( + childNode instanceof DocHtmlStartTag && + childNode.selfClosingTag && + (childNode.name === 'zh' || childNode.name === 'en') + ) { isSpecificLanguage = true; - const currentLanguage = childNode.selfClosingTag && childNode.name === 'zh' ? 'zh-CN' : 'en-US'; + const currentLanguage = + childNode.selfClosingTag && childNode.name === 'zh' ? LocaleLanguage.ZH : LocaleLanguage.EN; if (currentLanguage == language) { trimmed.appendNode(node); } @@ -2013,6 +2347,10 @@ export class MarkdownDocumenter { } private _intl(keyword: Keyword) { - return intl(keyword, this.locale); + return getKeywordIntl(keyword, this.locale); + } + + private _getLang() { + return this.locale === LocaleLanguage.ZH ? 'zh' : 'en'; } } diff --git a/packages/site/src/constants/index.ts b/packages/site/src/constants/index.ts index 6703203ad4..77469f5b61 100644 --- a/packages/site/src/constants/index.ts +++ b/packages/site/src/constants/index.ts @@ -1,3 +1,2 @@ -export * from './keywords'; -export * from './locale'; -export * from './page'; +export * from './link'; +export * from './locales'; diff --git a/packages/site/src/constants/link.ts b/packages/site/src/constants/link.ts new file mode 100644 index 0000000000..13d6b558b7 --- /dev/null +++ b/packages/site/src/constants/link.ts @@ -0,0 +1,15 @@ +export const GLinks: Record = { + BaseStyleProps: 'https://g.antv.antgroup.com/api/basic/display-object#%E7%BB%98%E5%9B%BE%E5%B1%9E%E6%80%A7', + DisplayObject: 'https://g.antv.antgroup.com/api/basic/display-object', + GroupStyleProps: 'https://g.antv.antgroup.com/api/basic/group', + TextStyleProps: 'https://g.antv.antgroup.com/api/basic/text', + CircleStyleProps: 'https://g.antv.antgroup.com/api/basic/circle', + EllipseStyleProps: 'https://g.antv.antgroup.com/api/basic/ellipse', + RectStyleProps: 'https://g.antv.antgroup.com/api/basic/rect', + ImageStyleProps: 'https://g.antv.antgroup.com/api/basic/image', + LineStyleProps: 'https://g.antv.antgroup.com/api/basic/line', + PolygonStyleProps: 'https://g.antv.antgroup.com/api/basic/polygon', + PolylineStyleProps: 'https://g.antv.antgroup.com/api/basic/polyline', + PathStyleProps: 'https://g.antv.antgroup.com/api/basic/path', + HTMLStyleProps: 'https://g.antv.antgroup.com/api/basic/html', +}; diff --git a/packages/site/src/constants/locale.ts b/packages/site/src/constants/locale.ts deleted file mode 100644 index c41e5525d3..0000000000 --- a/packages/site/src/constants/locale.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum LocaleLanguage { - EN = 'en-US', - ZH = 'zh-CN', -} diff --git a/packages/site/src/constants/api-category.ts b/packages/site/src/constants/locales/api-category.ts similarity index 63% rename from packages/site/src/constants/api-category.ts rename to packages/site/src/constants/locales/api-category.ts index 8e8b57da06..b6fc346a4a 100644 --- a/packages/site/src/constants/api-category.ts +++ b/packages/site/src/constants/locales/api-category.ts @@ -1,4 +1,4 @@ -import { LocaleLanguage } from './locale'; +import { getIntl } from './common'; const apiCategoryNames: Record = { option: ['Option', '图配置项'], @@ -19,12 +19,9 @@ const apiCategoryNames: Record = { }; /** - * Get the category name of the API - * @param categoryKey - The key of the category - * @param locale - The locale - * @returns The category name + * 获取 API 分类名称 + * @param categoryKey 分类 key + * @param locale 语言 + * @returns 分类名称 */ -export function getApiCategoryName(categoryKey: string, locale: LocaleLanguage) { - const [en, zh] = apiCategoryNames[categoryKey]; - return locale === LocaleLanguage.ZH ? zh : en; -} +export const getApiCategoryIntl = getIntl(apiCategoryNames); diff --git a/packages/site/src/constants/locales/common.ts b/packages/site/src/constants/locales/common.ts new file mode 100644 index 0000000000..56ccd536e3 --- /dev/null +++ b/packages/site/src/constants/locales/common.ts @@ -0,0 +1,11 @@ +export enum LocaleLanguage { + EN = 'en-US', + ZH = 'zh-CN', +} + +export const getIntl = (obj: Record) => { + return (key: string, language: LocaleLanguage) => { + const [en, zh] = obj[key]; + return language === LocaleLanguage.EN ? en : zh; + }; +}; diff --git a/packages/site/src/constants/locales/helper.ts b/packages/site/src/constants/locales/helper.ts new file mode 100644 index 0000000000..bdc3a19848 --- /dev/null +++ b/packages/site/src/constants/locales/helper.ts @@ -0,0 +1,13 @@ +import { getIntl } from './index'; + +export const helpers = { + prefixHelper: [ + 'More about `Prefix` generic usage, please check [here]', + '更多关于 `Prefix` 泛型的使用信息,请查看[这里]', + ], + basePropsStyleHelper: ['More about base style configuration, please check [here]', '了解通用样式配置,请点击[这里]'], + includes: ['Includes', '其中的'], + excludes: ['Excludes', '除了'], +}; + +export const getHelperIntl = getIntl(helpers); diff --git a/packages/site/src/constants/locales/index.ts b/packages/site/src/constants/locales/index.ts new file mode 100644 index 0000000000..41092e30f3 --- /dev/null +++ b/packages/site/src/constants/locales/index.ts @@ -0,0 +1,5 @@ +export * from './api-category'; +export * from './common'; +export * from './helper'; +export * from './keywords'; +export * from './page'; diff --git a/packages/site/src/constants/keywords.ts b/packages/site/src/constants/locales/keywords.ts similarity index 84% rename from packages/site/src/constants/keywords.ts rename to packages/site/src/constants/locales/keywords.ts index 4e330a3284..05647ab10b 100644 --- a/packages/site/src/constants/keywords.ts +++ b/packages/site/src/constants/locales/keywords.ts @@ -1,4 +1,4 @@ -import { LocaleLanguage } from './locale'; +import { getIntl } from './index'; export enum Keyword { ABSTRACT_CLASS = 'abstract-class', @@ -7,16 +7,19 @@ export enum Keyword { CLASSES = 'classes', COLON = 'colon', COMMA = 'comma', + CONSTRUCTOR = 'constructor', CONSTRUCTORS = 'constructors', DECORATOR = 'decorator', DEFAULT_VALUE = 'defaultValue', DESCRIPTION = 'description', + ENUMERATION = 'enumeration', ENUMERATION_MEMBERS = 'enumeration-members', ENUMERATIONS = 'enumerations', EVENTS = 'events', EXAMPLE = 'example', EXCEPTIONS = 'exceptions', EXTENDS = 'extends', + FUNCTION = 'function', FUNCTIONS = 'functions', IMPLEMENTS = 'implements', INTERFACE = 'interface', @@ -25,6 +28,7 @@ export enum Keyword { METHOD = 'method', METHODS = 'methods', MODIFIERS = 'modifiers', + NAMESPACE = 'namespace', NAMESPACES = 'namespaces', OPTIONAL = 'optional', OPTIONS = 'options', @@ -38,7 +42,9 @@ export enum Keyword { RETURNS = 'returns', RIGHT_PARENTHESIS = 'rightParenthesis', TYPE = 'type', + TYPE_ALIAS = 'type-alias', TYPE_ALIASES = 'type-aliases', + VARIABLE = 'variable', VARIABLES = 'variables', VIEW_PARAMETERS = 'view-parameters', } @@ -50,16 +56,19 @@ export const KeywordMapping: Record = { [Keyword.CLASSES]: ['Classes', '类'], [Keyword.COLON]: [':', ':'], [Keyword.COMMA]: [',', ','], + [Keyword.CONSTRUCTOR]: ['Constructor', '构造函数'], [Keyword.CONSTRUCTORS]: ['Constructors', '构造函数'], [Keyword.DECORATOR]: ['Decorator', '装饰器'], [Keyword.DEFAULT_VALUE]: ['Default Value', '默认值'], [Keyword.DESCRIPTION]: ['Description', '描述'], [Keyword.ENUMERATION_MEMBERS]: ['Enumeration Members', '枚举成员'], + [Keyword.ENUMERATION]: ['Enumeration', '枚举'], [Keyword.ENUMERATIONS]: ['Enumerations', '枚举'], [Keyword.EVENTS]: ['Events', '事件'], [Keyword.EXAMPLE]: ['Example', '示例'], [Keyword.EXCEPTIONS]: ['Exceptions', '异常'], [Keyword.EXTENDS]: ['Extends', '继承自'], + [Keyword.FUNCTION]: ['Function', '函数'], [Keyword.FUNCTIONS]: ['Functions', '函数'], [Keyword.IMPLEMENTS]: ['Implements', '实现自'], [Keyword.INTERFACE]: ['Interface', '接口'], @@ -68,6 +77,7 @@ export const KeywordMapping: Record = { [Keyword.METHOD]: ['Method', '方法'], [Keyword.METHODS]: ['Methods', '方法'], [Keyword.MODIFIERS]: ['Modifiers', '修饰符'], + [Keyword.NAMESPACE]: ['Namespace', '命名空间'], [Keyword.NAMESPACES]: ['Namespaces', '命名空间'], [Keyword.OPTIONAL]: ['Optional', '可选'], [Keyword.OPTIONS]: ['Options', '配置项'], @@ -80,13 +90,12 @@ export const KeywordMapping: Record = { [Keyword.REMARKS]: ['Remarks', '备注'], [Keyword.RETURNS]: ['Returns', '返回值'], [Keyword.RIGHT_PARENTHESIS]: [')', ')'], + [Keyword.TYPE_ALIAS]: ['Type Alias', '类型别名'], [Keyword.TYPE_ALIASES]: ['Type Aliases', '类型别名'], [Keyword.TYPE]: ['Type', '类型'], + [Keyword.VARIABLE]: ['Variable', '变量'], [Keyword.VARIABLES]: ['Variables', '变量'], [Keyword.VIEW_PARAMETERS]: ['View Parameters', '相关参数'], }; -export const intl = (keyword: Keyword, language: LocaleLanguage) => { - const [en, zh] = KeywordMapping[keyword]; - return language === LocaleLanguage.EN ? en : zh; -}; +export const getKeywordIntl = getIntl(KeywordMapping); diff --git a/packages/site/src/constants/page.ts b/packages/site/src/constants/locales/page.ts similarity index 60% rename from packages/site/src/constants/page.ts rename to packages/site/src/constants/locales/page.ts index 46dd3de5cf..9609ba865a 100644 --- a/packages/site/src/constants/page.ts +++ b/packages/site/src/constants/locales/page.ts @@ -1,8 +1,36 @@ +import { getIntl } from './common'; + export const PageTitle: Record = { // graph GraphOptions: ['Options', '配置项'], GraphMethods: ['API', '方法'], GraphProperties: ['Properties', '属性'], + // element + ElementMethods: ['API', '方法'], + // element/node + BaseNode: ['BaseNode', '节点通用样式属性'], + Circle: ['Circle', '圆形'], + Diamond: ['Diamond', '菱形'], + Donut: ['Donut', '甜甜圈'], + Ellipse: ['Ellipse', '椭圆形'], + Hexagon: ['Hexagon', '六边形'], + Html: ['Html', 'HTML'], + Image: ['Image', '图片'], + Rect: ['Rect', '矩形'], + Star: ['Star', '五角形'], + Triangle: ['Triangle', '三角形'], + // element/edge + BaseEdge: ['BaseEdge', '边通用样式属性'], + Cubic: ['Cubic', '三次贝塞尔曲线'], + CubicHorizontal: ['CubicHorizontal', '水平三次贝塞尔曲线'], + CubicVertical: ['CubicVertical', '垂直三次贝塞尔曲线'], + Line: ['Line', '直线'], + Polyline: ['Polyline', '折线'], + Quadratic: ['Quadratic', '二次贝塞尔曲线'], + // element/combo + BaseCombo: ['BaseCombo', '组合基础样式属性'], + CircleCombo: ['Circle', '圆形'], + RectCombo: ['Rect', '矩形'], // layout AntvDagreLayout: ['AntvDagre', '布局'], CircularLayout: ['Circular', '环形布局'], @@ -43,3 +71,12 @@ export const PageTitle: Record = { Tooltip: ['Tooltip', '提示框'], Watermark: ['Watermark', '水印'], }; + +// 节点、边、Combo 的中英文对照 +export const ElementLocale: Record = { + node: ['Node', '节点'], + edge: ['Edge', '边'], + combo: ['Combo', '组合'], +}; + +export const getElementIntl = getIntl(ElementLocale); diff --git a/packages/site/src/markdown/CustomMarkdownEmitter.ts b/packages/site/src/markdown/CustomMarkdownEmitter.ts index 2d5c8e5147..4bf1f43a5b 100644 --- a/packages/site/src/markdown/CustomMarkdownEmitter.ts +++ b/packages/site/src/markdown/CustomMarkdownEmitter.ts @@ -1,5 +1,6 @@ import type { ApiItem, ApiModel, IResolveDeclarationReferenceResult } from '@microsoft/api-extractor-model'; import type { DocLinkTag, DocNode, StringBuilder } from '@microsoft/tsdoc'; +import { DocContainer } from 'src/nodes/DocContainer'; import { CustomDocNodeKind } from '../nodes/CustomDocNodeKind'; import type { DocDetails } from '../nodes/DocDetails'; import type { DocEmphasisSpan } from '../nodes/DocEmphasisSpan'; @@ -56,7 +57,10 @@ export class CustomMarkdownEmitter extends MarkdownEmitter { prefix = '####'; } - writer.writeLine(prefix + ' ' + this.getEscapedText(docHeading.title)); + const _prefix = docHeading.prefix ? docHeading.prefix + ' ' : ''; + const _suffix = docHeading.suffix ? ' ' + docHeading.suffix : ''; + + writer.writeLine(prefix + ' ' + _prefix + this.getEscapedText(docHeading.title) + _suffix); writer.writeLine(); break; } @@ -147,7 +151,7 @@ export class CustomMarkdownEmitter extends MarkdownEmitter { writer.writeLine('---'); writer.writeLine('title: ' + docPageTitle.title); - if (docPageTitle.order) { + if (docPageTitle.order !== undefined) { writer.ensureNewLine(); writer.writeLine('order: ' + docPageTitle.order); } @@ -185,6 +189,16 @@ export class CustomMarkdownEmitter extends MarkdownEmitter { writer.writeLine(); break; } + case CustomDocNodeKind.Container: { + const container: DocContainer = docNode as DocContainer; + writer.ensureSkippedLine(); + writer.write(`:::${container.status}` + (container.title ? `{title=${container.title}}` : '')); + writer.ensureNewLine(); + this.writeNodes(container.getChildNodes(), context); + writer.write(':::'); + writer.ensureSkippedLine(); + break; + } default: super.writeNode(docNode, context, docNodeSiblings); } diff --git a/packages/site/src/markdown/MarkdownEmitter.ts b/packages/site/src/markdown/MarkdownEmitter.ts index 95180fd3cf..a30660c265 100644 --- a/packages/site/src/markdown/MarkdownEmitter.ts +++ b/packages/site/src/markdown/MarkdownEmitter.ts @@ -62,7 +62,7 @@ export class MarkdownEmitter { protected getEscapedText(text: string): string { const textWithBackslashes: string = text .replace(/\\/g, '\\\\') // first replace the escape character - .replace(/[*#[\]_|`~]/g, (x) => '\\' + x) // then escape any special characters + .replace(/[*#\\_|~]/g, (x) => '\\' + x) // then escape any special characters, except `[`,`]`,``` .replace(/---/g, '\\-\\-\\-') // hyphens only if it's 3 or more .replace(/&/g, '&') .replace(/) { + super(parameters); + this.content = new DocSection({ configuration: this.configuration }, sectionChildNodes); + this.status = parameters.status || 'info'; + this.title = parameters.title; + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.Container; + } + + /** @override */ + protected onGetChildNodes(): ReadonlyArray { + return [this.content]; + } +} diff --git a/packages/site/src/nodes/DocHeading.ts b/packages/site/src/nodes/DocHeading.ts index d8fc6718f2..1ad291acf3 100644 --- a/packages/site/src/nodes/DocHeading.ts +++ b/packages/site/src/nodes/DocHeading.ts @@ -8,6 +8,9 @@ import { CustomDocNodeKind } from './CustomDocNodeKind'; export interface IDocHeadingParameters extends IDocNodeParameters { title: string; level?: number; + escaped?: boolean; + prefix?: string; + suffix?: string; } /** @@ -16,11 +19,17 @@ export interface IDocHeadingParameters extends IDocNodeParameters { export class DocHeading extends DocNode { public readonly title: string; public readonly level: number; + public readonly escaped: boolean; + public readonly prefix: string; + public readonly suffix: string; public constructor(parameters: IDocHeadingParameters) { super(parameters); this.title = parameters.title; this.level = parameters.level !== undefined ? parameters.level : 1; + this.escaped = parameters.escaped || true; + this.prefix = parameters.prefix || ''; + this.suffix = parameters.suffix || ''; if (this.level < 1 || this.level > 5) { throw new Error('IDocHeadingParameters.level must be a number between 1 and 5'); diff --git a/packages/site/src/nodes/DocPageTitle.ts b/packages/site/src/nodes/DocPageTitle.ts index 4f4d37ee6c..459b9956bc 100644 --- a/packages/site/src/nodes/DocPageTitle.ts +++ b/packages/site/src/nodes/DocPageTitle.ts @@ -1,7 +1,6 @@ import type { IDocNodeParameters } from '@microsoft/tsdoc'; import { DocNode } from '@microsoft/tsdoc'; -import { LocaleLanguage } from '../constants/locale'; -import { PageTitle } from '../constants/page'; +import { LocaleLanguage, PageTitle } from '../constants/locales'; import { CustomDocNodeKind } from './CustomDocNodeKind'; /** diff --git a/packages/site/src/utils/Utilities.ts b/packages/site/src/utils/Utilities.ts index e546c9a9b1..98bf2ae43d 100644 --- a/packages/site/src/utils/Utilities.ts +++ b/packages/site/src/utils/Utilities.ts @@ -23,7 +23,6 @@ export class Utilities { /** * Converts bad filename characters to underscores. * @param name - The name to convert. - * @returns The safe filename. */ public static getSafeFilenameForName(name: string): string { // TODO: This can introduce naming collisions. diff --git a/packages/site/src/utils/excerpt-token.ts b/packages/site/src/utils/excerpt-token.ts new file mode 100644 index 0000000000..c44eb54b48 --- /dev/null +++ b/packages/site/src/utils/excerpt-token.ts @@ -0,0 +1,139 @@ +import { ApiInterface, ApiModel, ExcerptTokenKind, type ExcerptToken } from '@microsoft/api-extractor-model'; +import { camelCase } from 'lodash'; + +export type BaseExcerptToken = { + text: string; + canonicalReference?: ExcerptToken['canonicalReference']; + interface?: ApiInterface; + children?: ICustomExcerptToken[]; + parent?: ICustomExcerptToken; +}; + +export type ICustomExcerptToken = + | ({ + type: 'Prefix'; + prefix: string; + } & BaseExcerptToken) + | ({ + type: 'Omit' | 'Pick'; + fields: string[]; + } & BaseExcerptToken) + | ({ type: 'Default' } & BaseExcerptToken); + +/** + * 将 ExcerptToken 按 'Prefix'、'Omit'、'Pick'、'Default' 分类 + * @param apiModel + * @param apiItem + * @param parentToken + * @param flatten + * @returns + */ +export function parseExcerptTokens( + apiModel: ApiModel, + apiItem: ApiInterface, + parentToken?: ICustomExcerptToken, + flatten?: boolean, +): ICustomExcerptToken[] { + const customExcerptTokens: ICustomExcerptToken[] = []; + let flag = false; + + for (const [index, token] of apiItem.excerptTokens.entries()) { + let customToken: ICustomExcerptToken | undefined = undefined; + if (token.kind === ExcerptTokenKind.Reference) { + const excludeTags = ['Record', 'Partial']; + if (excludeTags.includes(token.text)) continue; + if (token.text === 'Prefix') { + const content = apiItem.excerptTokens[index + 1].text; + const prefixMatch = content.match(/<\s*'(\w+)'/)!; + const referenceToken = apiItem.excerptTokens[index + 2]; + customToken = { + type: token.text, + prefix: prefixMatch[1], + fields: undefined, + text: referenceToken.text, + canonicalReference: referenceToken.canonicalReference, + } as ICustomExcerptToken; + flag = true; + } else if (['Omit', 'Pick'].includes(token.text)) { + const content = apiItem.excerptTokens[index + 3].text; + const referenceToken = apiItem.excerptTokens[index + 2]; + const fields = (content.match(/'(\w+)'/g) || []).map((x) => x.replace(/'/g, '')); + customToken = { + type: token.text, + fields: fields, + text: referenceToken.text, + canonicalReference: referenceToken.canonicalReference, + } as ICustomExcerptToken; + flag = true; + } else { + if (!flag) { + customToken = { type: 'Default', text: token.text, canonicalReference: token.canonicalReference }; + } + flag = false; + } + } + + if (customToken && customToken.canonicalReference) { + const resolvedApiItem = apiModel.resolveDeclarationReference( + customToken.canonicalReference, + undefined, + ).resolvedApiItem; + if (resolvedApiItem instanceof ApiInterface) { + customToken.interface = resolvedApiItem; + customToken.children = parseExcerptTokens(apiModel, resolvedApiItem, customToken); + } + customToken.parent = parentToken; + customExcerptTokens.push(customToken); + } + } + + return customExcerptTokens; +} + +export const liftPrefixExcerptTokens = (items: ICustomExcerptToken[]): ICustomExcerptToken[] => { + let topLevelPrefixes: ICustomExcerptToken[] = []; + + for (const item of items) { + if (item.type === 'Prefix') { + const newItem = { ...item, prefix: getAccessorExcerptTokensPrefixes(item) }; + topLevelPrefixes.push(newItem); + } + + if (item.children && item.children.length > 0) { + const childPrefixes = liftPrefixExcerptTokens(item.children); + topLevelPrefixes = topLevelPrefixes.concat(childPrefixes); + } + } + + return topLevelPrefixes; +}; + +/** + * + * @param targetToken + * @param includeSelf + */ +export function findAccessorExcerptTokens(targetToken: ICustomExcerptToken, includeSelf = true): ICustomExcerptToken[] { + let path: ICustomExcerptToken[] = [targetToken]; + let currentToken = targetToken.parent; + + while (currentToken) { + path = [currentToken, ...path]; + currentToken = currentToken.parent; + } + + return includeSelf ? path : path.slice(1); +} + +/** + * + * @param targetToken + */ +export function getAccessorExcerptTokensPrefixes(targetToken: ICustomExcerptToken) { + const tokens = findAccessorExcerptTokens(targetToken); + const tokenString = tokens + .map((token) => (token as any).prefix || '') + .filter(Boolean) + .join(' '); + return camelCase(tokenString); +} diff --git a/packages/site/src/utils/parser.ts b/packages/site/src/utils/parser.ts index ddd73b11b5..6ce3f52838 100644 --- a/packages/site/src/utils/parser.ts +++ b/packages/site/src/utils/parser.ts @@ -1,10 +1,9 @@ import type { DocBlock, DocComment, DocSection } from '@microsoft/tsdoc'; /** - * Get the block tag by name - * @param tagName - The name of the block tag - * @param docComment - The doc comment - * @returns The block tag + * + * @param tagName + * @param docComment */ export function getBlockTagByName(tagName: string, docComment: DocComment): DocSection | undefined { const tag = docComment.customBlocks.find((customBlock: DocBlock) => customBlock.blockTag.tagName === tagName);