nx/nx-dev/ui-markdoc/src/index.ts
Nicholas Cunningham 5be5ad8a74
fix(nx-dev): fix markdoc table data alignment (#28274)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

Currently, our table heading generated from markdown files `.md` are
positioned `center` while the data `td` are positioned `left`.

It doesn't look uniform and on larger tables, it can be jarring.

Examples: 
- https://nx.dev/ci/reference/release-notes#helm-package-compatibility
- https://nx.dev/reference/releases#supported-versions
-
https://nx.dev/blog/reliable-ci-a-new-execution-model-fixing-both-flakiness-and-slowness#problem-in-numbers

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Both heading and data should have the same alignment to be symmetrical. 

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2024-10-03 10:10:00 -06:00

180 lines
5.5 KiB
TypeScript

import {
Node,
parse,
RenderableTreeNode,
renderers,
Tokenizer,
transform,
} from '@markdoc/markdoc';
import { load as yamlLoad } from '@zkochan/js-yaml';
import React, { ReactNode } from 'react';
import { Heading } from './lib/nodes/heading.component';
import { getHeadingSchema } from './lib/nodes/heading.schema';
import { getImageSchema } from './lib/nodes/image.schema';
import { CustomLink } from './lib/nodes/link.component';
import { link } from './lib/nodes/link.schema';
import { Callout } from './lib/tags/callout.component';
import { callout } from './lib/tags/callout.schema';
import { CallToAction } from './lib/tags/call-to-action.component';
import { callToAction } from './lib/tags/call-to-action.schema';
import { Card, Cards, LinkCard } from './lib/tags/cards.component';
import { card, cards, linkCard } from './lib/tags/cards.schema';
import { GithubRepository } from './lib/tags/github-repository.component';
import { githubRepository } from './lib/tags/github-repository.schema';
import { StackblitzButton } from './lib/tags/stackblitz-button.component';
import { stackblitzButton } from './lib/tags/stackblitz-button.schema';
import { Graph } from './lib/tags/graph.component';
import { graph } from './lib/tags/graph.schema';
import { Iframe } from './lib/tags/iframe.component';
import { iframe } from './lib/tags/iframe.schema';
import { InstallNxConsole } from './lib/tags/install-nx-console.component';
import { installNxConsole } from './lib/tags/install-nx-console.schema';
import { Persona, Personas } from './lib/tags/personas.component';
import { persona, personas } from './lib/tags/personas.schema';
import { ProjectDetails } from './lib/tags/project-details.component';
import { projectDetails } from './lib/tags/project-details.schema';
import {
ShortEmbeds,
shortEmbeds,
shortVideo,
ShortVideo,
} from './lib/tags/short-embed';
import { SideBySide } from './lib/tags/side-by-side.component';
import { sideBySide } from './lib/tags/side-by-side.schema';
import { Tab, Tabs } from './lib/tags/tabs.component';
import { tab, tabs } from './lib/tags/tabs.schema';
import { Tweet, tweet } from '@nx/nx-dev/ui-common';
import { YouTube, youtube } from '@nx/nx-dev/ui-common';
import {
TerminalVideo,
terminalVideo,
} from './lib/tags/terminal-video.component';
import { VideoLink, videoLink } from './lib/tags/video-link.component';
// import { SvgAnimation, svgAnimation } from './lib/tags/svg-animation.component';
import { Pill } from './lib/tags/pill.component';
import { pill } from './lib/tags/pill.schema';
import { fence } from './lib/nodes/fence.schema';
import { FenceWrapper } from './lib/nodes/fence-wrapper.component';
import { VideoPlayer, videoPlayer } from './lib/tags/video-player.component';
import { td } from './lib/nodes/td.schema';
// TODO fix this export
export { GithubRepository } from './lib/tags/github-repository.component';
export const getMarkdocCustomConfig = (
documentFilePath: string,
headingClass: string = ''
): { config: any; components: any } => ({
config: {
nodes: {
fence,
heading: getHeadingSchema(headingClass),
image: getImageSchema(documentFilePath),
link,
td,
},
tags: {
callout,
'call-to-action': callToAction,
card,
cards,
'link-card': linkCard,
'github-repository': githubRepository,
'stackblitz-button': stackblitzButton,
graph,
iframe,
'install-nx-console': installNxConsole,
'video-player': videoPlayer,
persona,
personas,
'project-details': projectDetails,
pill,
'short-embeds': shortEmbeds,
'short-video': shortVideo,
'side-by-side': sideBySide,
tab,
tabs,
'terminal-video': terminalVideo,
tweet,
youtube,
'video-link': videoLink,
// 'svg-animation': svgAnimation,
},
},
components: {
Callout,
CallToAction,
Card,
Cards,
LinkCard,
CustomLink,
FenceWrapper,
GithubRepository,
StackblitzButton,
Graph,
Heading,
Iframe,
InstallNxConsole,
Persona,
Personas,
ProjectDetails,
Pill,
ShortEmbeds,
ShortVideo,
SideBySide,
Tab,
Tabs,
TerminalVideo,
Tweet,
YouTube,
VideoLink,
VideoPlayer,
// SvgAnimation,
},
});
const tokenizer = new Tokenizer({
// Per https://markdoc.dev/docs/syntax#comments this will be on by default in a future version
allowComments: true,
});
const parseMarkdown: (markdown: string) => Node = (markdown) => {
const tokens = tokenizer.tokenize(markdown);
return parse(tokens);
};
export const extractFrontmatter = (
documentContent: string
): Record<string, any> => {
const ast = parseMarkdown(documentContent);
const frontmatter = ast.attributes['frontmatter']
? (yamlLoad(ast.attributes['frontmatter']) as Record<string, any>)
: {};
return frontmatter;
};
export const renderMarkdown: (
documentContent: string,
options: { filePath: string; headingClass?: string }
) => {
metadata: Record<string, any>;
node: ReactNode;
treeNode: RenderableTreeNode;
} = (documentContent, options = { filePath: '' }) => {
const ast = parseMarkdown(documentContent);
const configuration = getMarkdocCustomConfig(
options.filePath,
options.headingClass
);
const treeNode = transform(ast, configuration.config);
return {
metadata: ast.attributes['frontmatter']
? (yamlLoad(ast.attributes['frontmatter']) as Record<string, any>)
: {},
node: renderers.react(transform(ast, configuration.config), React, {
components: configuration.components,
}),
treeNode,
};
};