Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions data/onPostBuild/transpileMdxToMarkdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
removeScriptTags,
removeAnchorTags,
removeJsxComments,
convertMethodSignatureToCode,
convertImagePathsToGitHub,
convertDocsLinksToMarkdown,
convertJsxLinkProps,
Expand Down Expand Up @@ -306,6 +307,50 @@ import Baz from 'qux';
});
});

describe('convertMethodSignatureToCode', () => {
it('should convert simple MethodSignature to inline code', () => {
const input = '<MethodSignature>rooms.get(name, options)</MethodSignature>';
const output = convertMethodSignatureToCode(input);
expect(output).toBe('`rooms.get(name, options)`');
});

it('should convert template literal MethodSignature to inline code', () => {
const input = '<MethodSignature>{`rooms.get<RoomOptions>(name, options)`}</MethodSignature>';
const output = convertMethodSignatureToCode(input);
expect(output).toBe('`rooms.get<RoomOptions>(name, options)`');
});

it('should handle MethodSignature with angle brackets in template literal', () => {
const input = '<MethodSignature>{`Map<string, Room>`}</MethodSignature>';
const output = convertMethodSignatureToCode(input);
expect(output).toBe('`Map<string, Room>`');
});

it('should handle multiple MethodSignatures', () => {
const input = `## Method A
<MethodSignature>methodA()</MethodSignature>

## Method B
<MethodSignature>{\`methodB<T>()\`}</MethodSignature>`;
const output = convertMethodSignatureToCode(input);
expect(output).toContain('`methodA()`');
expect(output).toContain('`methodB<T>()`');
expect(output).not.toContain('<MethodSignature>');
});

it('should preserve MethodSignature in code blocks', () => {
const input = '```jsx\n<MethodSignature>preserve this</MethodSignature>\n```';
const output = convertMethodSignatureToCode(input);
expect(output).toContain('<MethodSignature>preserve this</MethodSignature>');
});

it('should handle surrounding content', () => {
const input = 'Before\n\n<MethodSignature>method()</MethodSignature>\n\nAfter';
const output = convertMethodSignatureToCode(input);
expect(output).toBe('Before\n\n`method()`\n\nAfter');
});
});

describe('convertImagePathsToGitHub', () => {
const githubBase = 'https://raw.githubusercontent.com/ably/docs/main/src';

Expand Down
34 changes: 27 additions & 7 deletions data/onPostBuild/transpileMdxToMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,22 @@ function removeJsxComments(content: string): string {
return transformNonCodeBlocks(content, (text) => text.replace(/\{\/\*[\s\S]*?\*\/\}/g, ''));
}

/**
* Convert MethodSignature components to inline code
* Handles both simple content and template literal syntax
* <MethodSignature>content</MethodSignature> → `content`
* <MethodSignature>{`content`}</MethodSignature> → `content`
*/
function convertMethodSignatureToCode(content: string): string {
return transformNonCodeBlocks(content, (text) =>
text
// Template literal syntax: <MethodSignature>{`content`}</MethodSignature>
.replace(/<MethodSignature>\{`([^`]*)`\}<\/MethodSignature>/g, '`$1`')
// Simple syntax: <MethodSignature>content</MethodSignature>
.replace(/<MethodSignature>([^<{]+)<\/MethodSignature>/g, '`$1`'),
);
}

/**
* Convert image paths to GitHub raw URLs
* Handles relative (../), absolute (/images/), and direct (images/) paths
Expand Down Expand Up @@ -456,25 +472,28 @@ function transformMdxToMarkdown(
// Stage 5: Remove JSX comments
content = removeJsxComments(content);

// Stage 6: Strip hidden attribute from tables (makes them visible in markdown)
// Stage 6: Convert MethodSignature components to inline code
content = convertMethodSignatureToCode(content);

// Stage 7: Strip hidden attribute from tables (makes them visible in markdown)
content = stripHiddenFromTables(content);

// Stage 7: Convert image paths to GitHub URLs
// Stage 8: Convert image paths to GitHub URLs
content = convertImagePathsToGitHub(content);

// Stage 8: Convert relative URLs to absolute URLs
// Stage 9: Convert relative URLs to absolute URLs
content = convertRelativeUrls(content, siteUrl);

// Stage 9: Convert quoted /docs/ URLs to markdown links (for JSX props like link: '/docs/...')
// Stage 10: Convert quoted /docs/ URLs to markdown links (for JSX props like link: '/docs/...')
content = convertJsxLinkProps(content, siteUrl);

// Stage 10: Convert /docs/ links to .md extension and remove ?lang= params
// Stage 11: Convert /docs/ links to .md extension and remove ?lang= params
content = convertDocsLinksToMarkdown(content);

// Stage 11: Replace template variables
// Stage 12: Replace template variables
content = replaceTemplateVariables(content);

// Stage 12: Prepend title as markdown heading
// Stage 13: Prepend title as markdown heading
const finalContent = `# ${title}\n\n${intro ? `${intro}\n\n` : ''}${content}`;

return { content: finalContent, title, intro };
Expand Down Expand Up @@ -592,6 +611,7 @@ export {
removeScriptTags,
removeAnchorTags,
removeJsxComments,
convertMethodSignatureToCode,
stripHiddenFromTables,
convertImagePathsToGitHub,
convertDocsLinksToMarkdown,
Expand Down
2 changes: 2 additions & 0 deletions src/components/Layout/MDXWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Table, NestedTableProvider } from './mdx/NestedTable';
import { Tiles } from './mdx/tiles';
import { PageHeader } from './mdx/PageHeader';
import Admonition from './mdx/Admonition';
import { MethodSignature } from './mdx/MethodSignature';

import { Frontmatter, PageContextType } from './Layout';
import { ActivePage } from './utils/nav';
Expand Down Expand Up @@ -285,6 +286,7 @@ const MDXWrapper: React.FC<MDXWrapperProps> = ({ children, pageContext, location
th: Table.Head,
td: Table.Cell,
Tiles,
MethodSignature,
}}
>
<PageHeader title={title} intro={intro} />
Expand Down
38 changes: 38 additions & 0 deletions src/components/Layout/mdx/MethodSignature.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import cn from '@ably/ui/core/utils/cn';

interface MethodSignatureProps {
children: React.ReactNode;
className?: string;
}

/**
* A component for displaying method signatures prominently in API reference docs.
* Uses orange styling consistent with NestedTable property names.
* Features an L-shaped connector line linking to the parent header.
*
* Note: Must be placed immediately after an h2/h3 heading with standard margins.
* The connector line positioning assumes this layout relationship.
*
* Usage in MDX:
* <MethodSignature>rooms.get(name, options)</MethodSignature>
*
* For signatures containing special characters like < > { }, use a template literal:
* <MethodSignature>{`rooms.get<RoomOptions>(name, options)`}</MethodSignature>
*/
export const MethodSignature: React.FC<MethodSignatureProps> = ({ children, className }) => {
return (
<div className="relative pl-3 my-4">
{/* L-shaped connector line */}
<div className="absolute left-0 -top-8 h-[calc(50%+2rem)] w-3 border-l border-b border-orange-300 dark:border-orange-900 rounded-bl-md" />
<code
className={cn(
'bg-orange-100 dark:bg-orange-1000 border border-orange-300 dark:border-orange-900 px-3 py-1.5 rounded text-sm font-mono text-neutral-1000 dark:text-neutral-300 inline-block',
className,
)}
>
{children}
</code>
</div>
);
};