mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-03 23:56:44 +02:00
Add canonicals to pages migrated to docs.mermaidchart.com
This commit is contained in:
149
packages/mermaid/src/docs/.vitepress/canonical-config.ts
Normal file
149
packages/mermaid/src/docs/.vitepress/canonical-config.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import type { CanonicalUrlConfig } from './canonical-urls.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canonical URL configuration for Mermaid documentation
|
||||||
|
*
|
||||||
|
* This file contains the configuration for generating canonical URLs
|
||||||
|
* for the Mermaid documentation site.
|
||||||
|
*/
|
||||||
|
export const canonicalConfig: CanonicalUrlConfig = {
|
||||||
|
// Base URL for the Mermaid documentation site
|
||||||
|
baseUrl: 'https://docs.mermaidchart.com',
|
||||||
|
|
||||||
|
// Disable automatic generation - only use specificCanonicalUrls
|
||||||
|
autoGenerate: false,
|
||||||
|
|
||||||
|
// Patterns for pages to exclude from automatic canonical URL generation
|
||||||
|
excludePatterns: [
|
||||||
|
// Exclude the main index page (handled by VitePress home layout)
|
||||||
|
'index.md',
|
||||||
|
|
||||||
|
// Exclude any draft or temporary files
|
||||||
|
'draft-*.md',
|
||||||
|
'**/draft-*.md',
|
||||||
|
'temp-*.md',
|
||||||
|
'**/temp-*.md',
|
||||||
|
'*.draft.md',
|
||||||
|
'**/*.draft.md',
|
||||||
|
|
||||||
|
// Exclude any test files
|
||||||
|
'*.test.md',
|
||||||
|
'**/*.test.md',
|
||||||
|
'*.spec.md',
|
||||||
|
'**/*.spec.md',
|
||||||
|
|
||||||
|
// You can add more patterns here as needed
|
||||||
|
// Examples:
|
||||||
|
// '**/internal/**', // Exclude internal documentation
|
||||||
|
// '**/archive/**', // Exclude archived content
|
||||||
|
],
|
||||||
|
|
||||||
|
// URL transformation rules
|
||||||
|
transformations: {
|
||||||
|
// Remove index.md from URLs (e.g., /intro/index.md -> /intro/)
|
||||||
|
//removeIndex: true,
|
||||||
|
|
||||||
|
// Remove .md extension from URLs (e.g., /syntax/flowchart.md -> /syntax/flowchart)
|
||||||
|
removeMarkdownExtension: true,
|
||||||
|
|
||||||
|
// Custom path transformations
|
||||||
|
customTransforms: [
|
||||||
|
// Example: Redirect old paths to new paths
|
||||||
|
// { pattern: /^old-syntax\//, replacement: 'syntax/' },
|
||||||
|
// Example: Handle special cases
|
||||||
|
// { pattern: /^config\/setup\/README$/, replacement: 'config/setup/' },
|
||||||
|
// Add your custom transformations here
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pages that should have specific canonical URLs
|
||||||
|
*
|
||||||
|
* Since autoGenerate is set to false, ONLY pages listed here will get canonical URLs.
|
||||||
|
*
|
||||||
|
* Usage: Add entries to this object where the key is the relative path
|
||||||
|
* of the markdown file and the value is the desired canonical URL.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* - 'intro/index.md': 'https://docs.mermaidchart.com/intro/index.html'
|
||||||
|
* - 'syntax/flowchart.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/flowchart.html'
|
||||||
|
* - 'config/configuration.md': 'https://docs.mermaidchart.com/mermaid-oss/config/configuration.html'
|
||||||
|
*/
|
||||||
|
export const specificCanonicalUrls: Record<string, string> = {
|
||||||
|
// Add your specific canonical URLs here
|
||||||
|
// Example:
|
||||||
|
// 'syntax/flowchart.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/flowchart.html',
|
||||||
|
|
||||||
|
// Intro section
|
||||||
|
'intro/index.md': 'https://docs.mermaidchart.com/intro/index.html',
|
||||||
|
'intro/getting-started.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/intro/getting-started.html',
|
||||||
|
'intro/syntax-reference.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/intro/syntax-reference.html',
|
||||||
|
|
||||||
|
// Syntax section
|
||||||
|
'syntax/flowchart.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/flowchart.html',
|
||||||
|
'syntax/sequenceDiagram.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/syntax/sequenceDiagram.html',
|
||||||
|
'syntax/classDiagram.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/classDiagram.html',
|
||||||
|
'syntax/stateDiagram.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/stateDiagram.html',
|
||||||
|
'syntax/entityRelationshipDiagram.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/syntax/entityRelationshipDiagram.html',
|
||||||
|
'syntax/userJourney.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/userJourney.html',
|
||||||
|
'syntax/gantt.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/gantt.html',
|
||||||
|
'syntax/pie.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/pie.html',
|
||||||
|
'syntax/quadrantChart.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/quadrantChart.html',
|
||||||
|
'syntax/requirementDiagram.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/syntax/requirementDiagram.html',
|
||||||
|
'syntax/mindmap.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/mindmap.html',
|
||||||
|
'syntax/timeline.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/timeline.html',
|
||||||
|
'syntax/gitgraph.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/gitgraph.html',
|
||||||
|
'syntax/c4.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/c4.html',
|
||||||
|
'syntax/sankey.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/sankey.html',
|
||||||
|
'syntax/xyChart.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/xyChart.html',
|
||||||
|
'syntax/block.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/block.html',
|
||||||
|
'syntax/packet.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/packet.html',
|
||||||
|
'syntax/kanban.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/kanban.html',
|
||||||
|
'syntax/architecture.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/architecture.html',
|
||||||
|
'syntax/radar.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/radar.html',
|
||||||
|
'syntax/examples.md': 'https://docs.mermaidchart.com/mermaid-oss/syntax/examples.html',
|
||||||
|
|
||||||
|
// Config section
|
||||||
|
'config/configuration.md': 'https://docs.mermaidchart.com/mermaid-oss/config/configuration.html',
|
||||||
|
'config/usage.md': 'https://docs.mermaidchart.com/mermaid-oss/config/usage.html',
|
||||||
|
'config/icons.md': 'https://docs.mermaidchart.com/mermaid-oss/config/icons.html',
|
||||||
|
'config/directives.md': 'https://docs.mermaidchart.com/mermaid-oss/config/directives.html',
|
||||||
|
'config/theming.md': 'https://docs.mermaidchart.com/mermaid-oss/config/theming.html',
|
||||||
|
'config/math.md': 'https://docs.mermaidchart.com/mermaid-oss/config/math.html',
|
||||||
|
'config/accessibility.md': 'https://docs.mermaidchart.com/mermaid-oss/config/accessibility.html',
|
||||||
|
'config/mermaidCLI.md': 'https://docs.mermaidchart.com/mermaid-oss/config/mermaidCLI.html',
|
||||||
|
'config/faq.md': 'https://docs.mermaidchart.com/mermaid-oss/config/faq.html',
|
||||||
|
|
||||||
|
// Ecosystem section
|
||||||
|
'ecosystem/mermaid-chart.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/ecosystem/mermaid-chart.html',
|
||||||
|
'ecosystem/tutorials.md': 'https://docs.mermaidchart.com/mermaid-oss/ecosystem/tutorials.html',
|
||||||
|
'ecosystem/integrations-community.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/ecosystem/integrations-community.html',
|
||||||
|
'ecosystem/integrations-create.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/ecosystem/integrations-create.html',
|
||||||
|
|
||||||
|
// Community section
|
||||||
|
'community/intro.md': 'https://docs.mermaidchart.com/mermaid-oss/community/intro.html',
|
||||||
|
'community/contributing.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/community/contributing.html',
|
||||||
|
'community/new-diagram.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/community/new-diagram.html',
|
||||||
|
'community/questions-and-suggestions.md':
|
||||||
|
'https://docs.mermaidchart.com/mermaid-oss/community/questions-and-suggestions.html',
|
||||||
|
'community/security.md': 'https://docs.mermaidchart.com/mermaid-oss/community/security.html',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to get canonical URL for a specific page
|
||||||
|
* This can be used in frontmatter or for manual overrides
|
||||||
|
*/
|
||||||
|
export function getCanonicalUrl(relativePath: string): string | undefined {
|
||||||
|
return specificCanonicalUrls[relativePath];
|
||||||
|
}
|
198
packages/mermaid/src/docs/.vitepress/canonical-urls.ts
Normal file
198
packages/mermaid/src/docs/.vitepress/canonical-urls.ts
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import type { PageData } from 'vitepress';
|
||||||
|
import { canonicalConfig, specificCanonicalUrls } from './canonical-config.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for canonical URL generation
|
||||||
|
*/
|
||||||
|
export interface CanonicalUrlConfig {
|
||||||
|
/** Base URL for the site (e.g., 'https://mermaid.js.org') */
|
||||||
|
baseUrl: string;
|
||||||
|
/** Whether to automatically generate canonical URLs for pages without explicit ones */
|
||||||
|
autoGenerate: boolean;
|
||||||
|
/** Pages to exclude from automatic canonical URL generation (glob patterns supported) */
|
||||||
|
excludePatterns?: string[];
|
||||||
|
/** Custom URL transformations */
|
||||||
|
transformations?: {
|
||||||
|
/** Remove index.md from URLs */
|
||||||
|
removeIndex?: boolean;
|
||||||
|
/** Remove .md extension from URLs */
|
||||||
|
removeMarkdownExtension?: boolean;
|
||||||
|
/** Custom path transformations */
|
||||||
|
customTransforms?: {
|
||||||
|
pattern: RegExp;
|
||||||
|
replacement: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default configuration for canonical URLs
|
||||||
|
*/
|
||||||
|
const defaultConfig: CanonicalUrlConfig = {
|
||||||
|
baseUrl: 'https://mermaid.js.org',
|
||||||
|
autoGenerate: true,
|
||||||
|
excludePatterns: [
|
||||||
|
// Exclude the home page as it's handled separately
|
||||||
|
'index.md',
|
||||||
|
// Exclude any temporary or draft files
|
||||||
|
'**/draft-*',
|
||||||
|
'**/temp-*',
|
||||||
|
],
|
||||||
|
transformations: {
|
||||||
|
removeIndex: true,
|
||||||
|
removeMarkdownExtension: true,
|
||||||
|
customTransforms: [
|
||||||
|
// Transform any special cases here
|
||||||
|
// Example: { pattern: /^old-path\//, replacement: 'new-path/' }
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a path matches any of the exclude patterns
|
||||||
|
*/
|
||||||
|
function shouldExcludePath(relativePath: string, excludePatterns: string[] = []): boolean {
|
||||||
|
return excludePatterns.some((pattern) => {
|
||||||
|
// Convert glob pattern to regex
|
||||||
|
const regexPattern = pattern
|
||||||
|
.replace(/\*\*/g, '.*')
|
||||||
|
.replace(/\*/g, '[^/]*')
|
||||||
|
.replace(/\?/g, '.')
|
||||||
|
.replace(/\./g, '\\.');
|
||||||
|
const regex = new RegExp(`^${regexPattern}$`);
|
||||||
|
return regex.test(relativePath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a relative path to a canonical URL path
|
||||||
|
*/
|
||||||
|
function transformPath(relativePath: string, config: CanonicalUrlConfig): string {
|
||||||
|
let transformedPath = relativePath;
|
||||||
|
|
||||||
|
// Apply built-in transformations
|
||||||
|
if (config.transformations?.removeMarkdownExtension) {
|
||||||
|
transformedPath = transformedPath.replace(/\.md$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.transformations?.removeIndex) {
|
||||||
|
transformedPath = transformedPath.replace(/\/index$/, '/');
|
||||||
|
if (transformedPath === 'index') {
|
||||||
|
transformedPath = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply custom transformations
|
||||||
|
if (config.transformations?.customTransforms) {
|
||||||
|
for (const transform of config.transformations.customTransforms) {
|
||||||
|
transformedPath = transformedPath.replace(transform.pattern, transform.replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure path starts with /
|
||||||
|
if (transformedPath && !transformedPath.startsWith('/')) {
|
||||||
|
transformedPath = '/' + transformedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle root path
|
||||||
|
if (!transformedPath) {
|
||||||
|
transformedPath = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a canonical URL for a page
|
||||||
|
*/
|
||||||
|
function generateCanonicalUrl(relativePath: string, config: CanonicalUrlConfig): string {
|
||||||
|
const transformedPath = transformPath(relativePath, config);
|
||||||
|
return config.baseUrl + transformedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VitePress transformPageData hook to add canonical URLs
|
||||||
|
*/
|
||||||
|
export function addCanonicalUrls(pageData: PageData): void {
|
||||||
|
const config = canonicalConfig;
|
||||||
|
|
||||||
|
// Check for specific canonical URLs first
|
||||||
|
const specificUrl = specificCanonicalUrls[pageData.relativePath];
|
||||||
|
if (specificUrl) {
|
||||||
|
addCanonicalToHead(pageData, specificUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if canonical URL is already explicitly set in frontmatter
|
||||||
|
if (pageData.frontmatter.canonical) {
|
||||||
|
// If it's already a full URL, use as-is
|
||||||
|
if (pageData.frontmatter.canonical.startsWith('http')) {
|
||||||
|
addCanonicalToHead(pageData, pageData.frontmatter.canonical);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If it's a relative path, convert to absolute URL
|
||||||
|
const canonicalUrl = config.baseUrl + pageData.frontmatter.canonical;
|
||||||
|
addCanonicalToHead(pageData, canonicalUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if canonicalPath is set in frontmatter
|
||||||
|
if (pageData.frontmatter.canonicalPath) {
|
||||||
|
const canonicalUrl = config.baseUrl + pageData.frontmatter.canonicalPath;
|
||||||
|
addCanonicalToHead(pageData, canonicalUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if auto-generation is disabled
|
||||||
|
if (!config.autoGenerate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if path should be excluded
|
||||||
|
if (shouldExcludePath(pageData.relativePath, config.excludePatterns)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate canonical URL
|
||||||
|
const canonicalUrl = generateCanonicalUrl(pageData.relativePath, config);
|
||||||
|
addCanonicalToHead(pageData, canonicalUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add canonical URL to page head
|
||||||
|
*/
|
||||||
|
function addCanonicalToHead(pageData: PageData, canonicalUrl: string): void {
|
||||||
|
// Initialize head array if it doesn't exist
|
||||||
|
pageData.frontmatter.head = pageData.frontmatter.head || [];
|
||||||
|
|
||||||
|
// Check if canonical link already exists
|
||||||
|
const hasCanonical = pageData.frontmatter.head.some(
|
||||||
|
(item: any) => Array.isArray(item) && item[0] === 'link' && item[1]?.rel === 'canonical'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add canonical link if it doesn't exist
|
||||||
|
if (!hasCanonical) {
|
||||||
|
pageData.frontmatter.head.push(['link', { rel: 'canonical', href: canonicalUrl }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to create a custom configuration
|
||||||
|
* This can be used to override the default configuration
|
||||||
|
*/
|
||||||
|
export function createCanonicalUrlConfig(
|
||||||
|
customConfig: Partial<CanonicalUrlConfig>
|
||||||
|
): CanonicalUrlConfig {
|
||||||
|
return {
|
||||||
|
...defaultConfig,
|
||||||
|
...customConfig,
|
||||||
|
transformations: {
|
||||||
|
...defaultConfig.transformations,
|
||||||
|
...customConfig.transformations,
|
||||||
|
customTransforms: [
|
||||||
|
...(defaultConfig.transformations?.customTransforms || []),
|
||||||
|
...(customConfig.transformations?.customTransforms || []),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@@ -2,6 +2,7 @@ import type { MarkdownOptions } from 'vitepress';
|
|||||||
import { defineConfig } from 'vitepress';
|
import { defineConfig } from 'vitepress';
|
||||||
import packageJson from '../../../package.json' assert { type: 'json' };
|
import packageJson from '../../../package.json' assert { type: 'json' };
|
||||||
import MermaidExample from './mermaid-markdown-all.js';
|
import MermaidExample from './mermaid-markdown-all.js';
|
||||||
|
import { addCanonicalUrls } from './canonical-urls.js';
|
||||||
|
|
||||||
const allMarkdownTransformers: MarkdownOptions = {
|
const allMarkdownTransformers: MarkdownOptions = {
|
||||||
// the shiki theme to highlight code blocks
|
// the shiki theme to highlight code blocks
|
||||||
@@ -25,6 +26,7 @@ export default defineConfig({
|
|||||||
// ignore all localhost links
|
// ignore all localhost links
|
||||||
/^https?:\/\/localhost/,
|
/^https?:\/\/localhost/,
|
||||||
],
|
],
|
||||||
|
transformPageData: addCanonicalUrls,
|
||||||
head: [
|
head: [
|
||||||
['link', { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
|
['link', { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
|
||||||
['meta', { property: 'og:title', content: 'Mermaid' }],
|
['meta', { property: 'og:title', content: 'Mermaid' }],
|
||||||
|
Reference in New Issue
Block a user