Compare commits

..

4 Commits

Author SHA1 Message Date
omkarht
9aadbe63dd Merge branch 'develop' into fix/7210-gitgraph-directives 2025-12-18 18:16:43 +05:30
omkarht
e5d3f720a0 test: add tests for gitGraph config directives
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-12-08 12:58:47 +05:30
omkarht
e0317ac764 chore: add changeset
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-12-04 19:45:18 +05:30
omkarht
bbb9c04862 7210 : fix for gitgraph showBranches and showCommitLabel directives 2025-12-04 17:17:35 +05:30
6 changed files with 717 additions and 38 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix(gitgraph): pass gitGraphConfig to renderer functions for applying directives properly.

View File

@@ -1569,4 +1569,514 @@ gitGraph TB:
{}
);
});
describe('showBranches and showCommitLabel directives', () => {
it('77: should show branch lines when showBranches is true (default)', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
commit id: "5"
commit id: "6"
`,
{}
);
});
it('78: should hide branch lines when showBranches is false', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: false
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
commit id: "5"
commit id: "6"
`,
{}
);
});
it('79: should show commit labels when showCommitLabel is true (default)', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
commit id: "5"
commit id: "6"
`,
{}
);
});
it('80: should hide commit labels when showCommitLabel is false', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: false
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
commit id: "5"
commit id: "6"
`,
{}
);
});
it('81: should show both branches and commit labels when both directives are true (default)', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
commit id: "5"
commit id: "6"
`,
{}
);
});
it('82: should hide both branches and commit labels when both directives are false', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: false
showCommitLabel: false
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
commit id: "5"
commit id: "6"
`,
{}
);
});
it('83: should show branch lines with merge commits when showBranches is true', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
merge develop
commit id: "5"
commit id: "6"
`,
{}
);
});
it('84: should hide branch lines with merge commits when showBranches is false', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: false
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
merge develop
commit id: "5"
commit id: "6"
`,
{}
);
});
it('85: should show commit labels with tags when showCommitLabel is true', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1" tag: "v1.0"
commit id: "2"
branch develop
checkout develop
commit id: "3" tag: "v1.1"
commit id: "4"
checkout main
merge develop tag: "v2.0"
commit id: "5"
`,
{}
);
});
it('86: should hide commit labels with tags when showCommitLabel is false', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: false
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1" tag: "v1.0"
commit id: "2"
branch develop
checkout develop
commit id: "3" tag: "v1.1"
commit id: "4"
checkout main
merge develop tag: "v2.0"
commit id: "5"
`,
{}
);
});
it('87: should show branches with TB orientation when showBranches is true', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph TB:
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
commit id: "5"
`,
{}
);
});
it('88: should hide branches with TB orientation when showBranches is false', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: false
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph TB:
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
commit id: "5"
`,
{}
);
});
it('89: should show commit labels with BT orientation when showCommitLabel is true', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph BT:
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
commit id: "5"
`,
{}
);
});
it('90: should hide commit labels with BT orientation when showCommitLabel is false', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: false
rotateCommitLabel: false
parallelCommits: false
---
gitGraph BT:
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout main
commit id: "5"
`,
{}
);
});
it('91: should render with rotateCommitLabel set to true', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: true
parallelCommits: false
---
gitGraph
commit id: "Alpha"
commit id: "Beta"
branch develop
checkout develop
commit id: "Gamma"
commit id: "Delta"
checkout main
commit id: "Epsilon"
`,
{}
);
});
it('92: should render with rotateCommitLabel set to false', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "Alpha"
commit id: "Beta"
branch develop
checkout develop
commit id: "Gamma"
commit id: "Delta"
checkout main
commit id: "Epsilon"
`,
{}
);
});
it('93: should render with parallelCommits set to true', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: true
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
branch feature
checkout develop
commit id: "3"
checkout feature
commit id: "4"
checkout main
commit id: "5"
checkout develop
commit id: "6"
checkout feature
commit id: "7"
`,
{}
);
});
it('94: should render with parallelCommits set to false', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
branch feature
checkout develop
commit id: "3"
checkout feature
commit id: "4"
checkout main
commit id: "5"
checkout develop
commit id: "6"
checkout feature
commit id: "7"
`,
{}
);
});
it('95: should render with custom mainBranchName', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
mainBranchName: 'trunk'
---
gitGraph
commit id: "1"
commit id: "2"
branch develop
checkout develop
commit id: "3"
commit id: "4"
checkout trunk
commit id: "5"
commit id: "6"
`,
{}
);
});
it('96: should render with custom mainBranchOrder', () => {
imgSnapshotTest(
`---
config:
gitGraph:
showBranches: true
showCommitLabel: true
rotateCommitLabel: false
parallelCommits: false
mainBranchOrder: 2
---
gitGraph
commit id: "1"
branch feature1
branch feature2
checkout feature1
commit id: "2"
checkout feature2
commit id: "3"
checkout main
commit id: "4"
`,
{}
);
});
});
});

View File

@@ -1357,4 +1357,35 @@ describe('when parsing a gitGraph', function () {
logWarnSpy.mockRestore();
});
describe('gitGraph config directives', () => {
it('should expose getConfig method', () => {
expect(db.getConfig).toBeDefined();
expect(typeof db.getConfig).toBe('function');
});
it('should return config with showBranches property', () => {
const config = db.getConfig();
expect(config).toBeDefined();
expect(config).toHaveProperty('showBranches');
});
it('should return config with showCommitLabel property', () => {
const config = db.getConfig();
expect(config).toBeDefined();
expect(config).toHaveProperty('showCommitLabel');
});
it('should return config with rotateCommitLabel property', () => {
const config = db.getConfig();
expect(config).toBeDefined();
expect(config).toHaveProperty('rotateCommitLabel');
});
it('should return config with parallelCommits property', () => {
const config = db.getConfig();
expect(config).toBeDefined();
expect(config).toHaveProperty('parallelCommits');
});
});
});

View File

@@ -1,11 +1,12 @@
import { select } from 'd3';
import { getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI.js';
import { setupGraphViewbox } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js';
import utils from '../../utils.js';
import type { DrawDefinition } from '../../diagram-api/types.js';
import type d3 from 'd3';
import type { Commit, GitGraphDBRenderProvider, DiagramOrientation } from './gitGraphTypes.js';
import { commitType } from './gitGraphTypes.js';
import type { GitGraphDiagramConfig } from '../../config.type.js';
interface BranchPosition {
pos: number;
@@ -21,8 +22,6 @@ interface CommitPositionOffset extends CommitPosition {
posWithOffset: number;
}
const DEFAULT_CONFIG = getConfig();
const DEFAULT_GITGRAPH_CONFIG = DEFAULT_CONFIG?.gitGraph;
const LAYOUT_OFFSET = 10;
const COMMIT_STEP = 40;
const PX = 4;
@@ -287,12 +286,13 @@ const drawCommitLabel = (
gLabels: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
commit: Commit,
commitPosition: CommitPositionOffset,
pos: number
pos: number,
gitGraphConfig: GitGraphDiagramConfig
) => {
if (
commit.type !== commitType.CHERRY_PICK &&
((commit.customId && commit.type === commitType.MERGE) || commit.type !== commitType.MERGE) &&
DEFAULT_GITGRAPH_CONFIG?.showCommitLabel
gitGraphConfig.showCommitLabel
) {
const wrapper = gLabels.append('g');
const labelBkg = wrapper.insert('rect').attr('class', 'commit-label-bkg');
@@ -322,7 +322,7 @@ const drawCommitLabel = (
text.attr('x', commitPosition.posWithOffset - bbox.width / 2);
}
if (DEFAULT_GITGRAPH_CONFIG.rotateCommitLabel) {
if (gitGraphConfig.rotateCommitLabel) {
if (dir === 'TB' || dir === 'BT') {
text.attr(
'transform',
@@ -514,16 +514,14 @@ const getCommitPosition = (
const drawCommits = (
svg: d3.Selection<d3.BaseType, unknown, HTMLElement, any>,
commits: Map<string, Commit>,
modifyGraph: boolean
modifyGraph: boolean,
gitGraphConfig: GitGraphDiagramConfig
) => {
if (!DEFAULT_GITGRAPH_CONFIG) {
throw new Error('GitGraph config not found');
}
const gBullets = svg.append('g').attr('class', 'commit-bullets');
const gLabels = svg.append('g').attr('class', 'commit-labels');
let pos = dir === 'TB' || dir === 'BT' ? defaultPos : 0;
const keys = [...commits.keys()];
const isParallelCommits = DEFAULT_GITGRAPH_CONFIG?.parallelCommits ?? false;
const isParallelCommits = gitGraphConfig.parallelCommits ?? false;
const sortKeys = (a: string, b: string) => {
const seqA = commits.get(a)?.seq;
@@ -555,7 +553,7 @@ const drawCommits = (
const commitSymbolType = commit.customType ?? commit.type;
const branchIndex = branchPos.get(commit.branch)?.index ?? 0;
drawCommitBullet(gBullets, commit, commitPosition, typeClass, branchIndex, commitSymbolType);
drawCommitLabel(gLabels, commit, commitPosition, pos);
drawCommitLabel(gLabels, commit, commitPosition, pos, gitGraphConfig);
drawCommitTags(gLabels, commit, commitPosition, pos);
}
if (dir === 'TB' || dir === 'BT') {
@@ -812,7 +810,8 @@ const drawArrows = (
const drawBranches = (
svg: d3.Selection<d3.BaseType, unknown, HTMLElement, any>,
branches: { name: string }[]
branches: { name: string }[],
gitGraphConfig: GitGraphDiagramConfig
) => {
const g = svg.append('g');
branches.forEach((branch, index) => {
@@ -859,14 +858,14 @@ const drawBranches = (
.attr('class', 'branchLabelBkg label' + adjustIndexForTheme)
.attr('rx', 4)
.attr('ry', 4)
.attr('x', -bbox.width - 4 - (DEFAULT_GITGRAPH_CONFIG?.rotateCommitLabel === true ? 30 : 0))
.attr('x', -bbox.width - 4 - (gitGraphConfig.rotateCommitLabel === true ? 30 : 0))
.attr('y', -bbox.height / 2 + 8)
.attr('width', bbox.width + 18)
.attr('height', bbox.height + 4);
label.attr(
'transform',
'translate(' +
(-bbox.width - 14 - (DEFAULT_GITGRAPH_CONFIG?.rotateCommitLabel === true ? 30 : 0)) +
(-bbox.width - 14 - (gitGraphConfig.rotateCommitLabel === true ? 30 : 0)) +
', ' +
(pos - bbox.height / 2 - 1) +
')'
@@ -899,11 +898,13 @@ export const draw: DrawDefinition = function (txt, id, ver, diagObj) {
clear();
log.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
if (!DEFAULT_GITGRAPH_CONFIG) {
throw new Error('GitGraph config not found');
}
const rotateCommitLabel = DEFAULT_GITGRAPH_CONFIG.rotateCommitLabel ?? false;
const db = diagObj.db as GitGraphDBRenderProvider;
if (!db.getConfig) {
log.error('getConfig method is not available on db');
return;
}
const gitGraphConfig = db.getConfig();
const rotateCommitLabel = gitGraphConfig.rotateCommitLabel ?? false;
allCommitsDict = db.getCommits();
const branches = db.getBranchesAsObjArray();
dir = db.getDirection();
@@ -924,27 +925,22 @@ export const draw: DrawDefinition = function (txt, id, ver, diagObj) {
g.remove();
});
drawCommits(diagram, allCommitsDict, false);
if (DEFAULT_GITGRAPH_CONFIG.showBranches) {
drawBranches(diagram, branches);
drawCommits(diagram, allCommitsDict, false, gitGraphConfig);
if (gitGraphConfig.showBranches) {
drawBranches(diagram, branches, gitGraphConfig);
}
drawArrows(diagram, allCommitsDict);
drawCommits(diagram, allCommitsDict, true);
drawCommits(diagram, allCommitsDict, true, gitGraphConfig);
utils.insertTitle(
diagram,
'gitTitleText',
DEFAULT_GITGRAPH_CONFIG.titleTopMargin ?? 0,
gitGraphConfig.titleTopMargin ?? 0,
db.getDiagramTitle()
);
// Setup the view box and size of the svg element
setupGraphViewbox(
undefined,
diagram,
DEFAULT_GITGRAPH_CONFIG.diagramPadding,
DEFAULT_GITGRAPH_CONFIG.useMaxWidth
);
setupGraphViewbox(undefined, diagram, gitGraphConfig.diagramPadding, gitGraphConfig.useMaxWidth);
};
export default {
@@ -1307,7 +1303,6 @@ if (import.meta.vitest) {
branchPos.set('main', { pos: 0, index: 0 });
branchPos.set('develop', { pos: 107.49609375, index: 1 });
branchPos.set('feature', { pos: 225.70703125, index: 2 });
DEFAULT_GITGRAPH_CONFIG!.parallelCommits = true;
commits.forEach((commit, key) => {
if (commit.parents.length > 0) {
curPos = calculateCommitPosition(commit);
@@ -1335,7 +1330,6 @@ if (import.meta.vitest) {
});
});
});
DEFAULT_GITGRAPH_CONFIG!.parallelCommits = false;
it('add', () => {
commitPos.set('parent1', { x: 1, y: 1 });
commitPos.set('parent2', { x: 2, y: 2 });

View File

@@ -8,7 +8,7 @@ import type { CanonicalUrlConfig } from './canonical-urls.js';
*/
export const canonicalConfig: CanonicalUrlConfig = {
// Base URL for the Mermaid documentation site
baseUrl: 'https://mermaid.ai/open-source',
baseUrl: 'https://docs.mermaidchart.com',
// Disable automatic generation - only use specificCanonicalUrls
autoGenerate: false,
@@ -57,6 +57,93 @@ export const canonicalConfig: CanonicalUrlConfig = {
},
};
/**
* 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 `https://mermaid.ai/open-source/${relativePath}`;
return specificCanonicalUrls[relativePath];
}

View File

@@ -1,5 +1,5 @@
import type { PageData } from 'vitepress';
import { canonicalConfig } from './canonical-config.js';
import { canonicalConfig, specificCanonicalUrls } from './canonical-config.js';
/**
* Configuration for canonical URL generation
@@ -48,15 +48,31 @@ const defaultConfig: CanonicalUrlConfig = {
},
};
/**
* 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
*/
export function transformPath(relativePath: string, config: CanonicalUrlConfig): string {
function transformPath(relativePath: string, config: CanonicalUrlConfig): string {
let transformedPath = relativePath;
// Apply built-in transformations
if (config.transformations?.removeMarkdownExtension) {
transformedPath = transformedPath.replace(/\.md$/, '.html');
transformedPath = transformedPath.replace(/\.md$/, '');
}
if (config.transformations?.removeIndex) {
@@ -100,9 +116,45 @@ function generateCanonicalUrl(relativePath: string, config: CanonicalUrlConfig):
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);
transformPath(pageData.relativePath, config);
addCanonicalToHead(pageData, canonicalUrl);
}