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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ pnpm-lock.yaml
bun.lock

seo
.planning/
2 changes: 1 addition & 1 deletion components/footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ export const footerTranslations: Record<

// Generate the footer links with translated text and URLs
const getFooterLinks = (lang: languagesType) => {
const translations = footerTranslations[lang];
const translations = footerTranslations[lang] || footerTranslations['en'];

const servicesLinks = [...FooterLinksData.services.links];
if (lang === 'zh-cn') {
Expand Down
118 changes: 40 additions & 78 deletions components/mdx/mermaid.tsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,43 @@
'use client';

import type { MermaidConfig } from 'mermaid';
import { useEffect, useId, useRef, useState } from 'react';

type MermaidProps = {
chart: string;
};

function detectDarkTheme(): boolean {
if (typeof document === 'undefined') return false;
return document.documentElement.classList.contains('dark');
}

function sanitizeMermaidId(value: string): string {
return value.replaceAll(':', '');
}

function normalizeMermaidChart(value: string): string {
return value.replaceAll('\\n', '\n');
}

function buildMermaidConfig(isDark: boolean): MermaidConfig {
return {
startOnLoad: false,
securityLevel: 'loose',
fontFamily: 'inherit',
themeCSS: 'margin: 1.5rem auto 0;',
theme: isDark ? 'dark' : 'default',
};
}

export function Mermaid({ chart }: MermaidProps): JSX.Element {
const id = useId();
const [svg, setSvg] = useState('');
const containerRef = useRef<HTMLDivElement>(null);
const [isDark, setIsDark] = useState(() => detectDarkTheme());

useEffect(() => {
if (typeof document === 'undefined') return;

const updateTheme = () => {
setIsDark(detectDarkTheme());
};

updateTheme();

const observer = new MutationObserver(updateTheme);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class'],
});

return () => observer.disconnect();
}, []);

useEffect(() => {
void renderChart().catch((error) => {
console.error('Error while rendering mermaid', error);
});

async function renderChart(): Promise<void> {
const container = containerRef.current;
if (!container) return;

const mermaidConfig = buildMermaidConfig(isDark);
const { default: mermaid } = await import('mermaid');

mermaid.initialize(mermaidConfig);
const { svg } = await mermaid.render(
// Strip invalid characters for the `id` attribute.
sanitizeMermaidId(id),
normalizeMermaidChart(chart),
container,
);
setSvg(svg);
}
}, [chart, id, isDark]);

return <div ref={containerRef} dangerouslySetInnerHTML={{ __html: svg }} />;
}
import type { MermaidConfig } from 'mermaid';
import { useTheme } from 'next-themes';

export function Mermaid({ chart }: { chart: string }) {
const id = useId();
const [svg, setSvg] = useState('');
const containerRef = useRef<HTMLDivElement>(null!);
const { resolvedTheme } = useTheme();

useEffect(() => {
void renderChart();

async function renderChart() {
const mermaidConfig: MermaidConfig = {
startOnLoad: false,
securityLevel: 'loose',
fontFamily: 'inherit',
themeCSS: 'margin: 1.5rem auto 0;',
theme: resolvedTheme === 'dark' ? 'dark' : 'default',
};

const { default: mermaid } = await import('mermaid');

try {
mermaid.initialize(mermaidConfig);
const { svg } = await mermaid.render(
// strip invalid characters for `id` attribute
id.replaceAll(':', ''),
chart.replaceAll('\\n', '\n'),
containerRef.current,
);
setSvg(svg);
} catch (error) {
console.error('Error while rendering mermaid', error);
}
}
}, [chart, id, resolvedTheme]);

return <div ref={containerRef} dangerouslySetInnerHTML={{ __html: svg }} />;
}
23 changes: 23 additions & 0 deletions components/ui/icons.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
import React, { SVGProps } from 'react';
import type { StaticImageData } from 'next/image';
import NodejsSvg from '@/assets/stacks-appicons/nodejs.svg';
import GolangSvg from '@/assets/stacks-appicons/golang.svg';
import JavaSvg from '@/assets/stacks-appicons/java.svg';
import PHPSvg from '@/assets/stacks-appicons/php.svg';
import PythonSvg from '@/assets/stacks-appicons/python.svg';
import RustSvg from '@/assets/stacks-appicons/rust.svg';

const createStackIcon = (svg: StaticImageData, alt: string) => {
const Icon = () => (
// eslint-disable-next-line @next/next/no-img-element
<img src={svg.src} alt={alt} width={20} height={20} />
);
Icon.displayName = `${alt}Icon`;
return Icon;
};

export const GradientCircleCheck = (
props: React.DetailedHTMLProps<
Expand Down Expand Up @@ -483,6 +499,13 @@ export function WhatsAppIcon(props: SVGProps<SVGSVGElement>) {
);
}

export const NodejsIcon = createStackIcon(NodejsSvg, 'Node.js');
export const GoIcon = createStackIcon(GolangSvg, 'Go');
export const JavaIcon = createStackIcon(JavaSvg, 'Java');
export const PHPIcon = createStackIcon(PHPSvg, 'PHP');
export const PythonIcon = createStackIcon(PythonSvg, 'Python');
export const RustIcon = createStackIcon(RustSvg, 'Rust');

export function PerplexityIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
Expand Down
Loading
Loading