diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 000000000..2825e2b1c
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,18 @@
+coverage:
+ status:
+ project:
+ default:
+ # Allow project coverage to decrease, but require patch coverage
+ target: auto
+ threshold: 100%
+ if_ci_failed: error
+ patch:
+ default:
+ # Require all new code to be covered
+ target: 100%
+ threshold: 0%
+
+comment:
+ layout: "reach,diff,flags,tree"
+ behavior: default
+ require_changes: false
diff --git a/cypress/components/StyledMarkdown.cy.tsx b/cypress/components/StyledMarkdown.cy.tsx
new file mode 100644
index 000000000..d30710b07
--- /dev/null
+++ b/cypress/components/StyledMarkdown.cy.tsx
@@ -0,0 +1,109 @@
+import React from 'react';
+import StyledMarkdown from '~/components/StyledMarkdown';
+
+describe('StyledMarkdown - Reference Links', () => {
+ it('should correctly transform standard reference links', () => {
+ const markdown = `
+[JSON Schema][json-schema]
+
+[json-schema]: https://json-schema.org/
+`;
+ cy.mount();
+ cy.get('a').should('have.attr', 'href', 'https://json-schema.org/');
+ cy.get('a').should('have.text', 'JSON Schema');
+ });
+
+ it('should correctly transform implicit reference links', () => {
+ const markdown = `
+[json-schema][]
+
+[json-schema]: https://json-schema.org/
+`;
+ cy.mount();
+ cy.get('a').should('have.attr', 'href', 'https://json-schema.org/');
+ // For implicit links [json-schema][], the text is 'json-schema'
+ cy.get('a').should('have.text', 'json-schema');
+ });
+
+ it('should correctly transform collapsed reference links', () => {
+ const markdown = `
+[json-schema]
+
+[json-schema]: https://json-schema.org/
+`;
+ cy.mount();
+ cy.get('a').should('have.attr', 'href', 'https://json-schema.org/');
+ cy.get('a').should('have.text', 'json-schema');
+ });
+
+ it('should correctly handle images as reference links', () => {
+ const markdown = `
+![JSON Schema Logo][logo]
+
+[logo]: https://json-schema.org/img/logo-blue.svg
+`;
+ cy.mount();
+ cy.get('img').should(
+ 'have.attr',
+ 'src',
+ 'https://json-schema.org/img/logo-blue.svg',
+ );
+ cy.get('img').should('have.attr', 'alt', 'JSON Schema Logo');
+ });
+
+ it('should not break standard inline links', () => {
+ const markdown = `
+[Inline Link](https://example.com)
+`;
+ cy.mount();
+ cy.get('a').should('have.attr', 'href', 'https://example.com');
+ cy.get('a').should('have.text', 'Inline Link');
+ });
+
+ it('should handle reference links with an optional space', () => {
+ const markdown = `
+[JSON Schema] [json-schema]
+
+[json-schema]: https://json-schema.org/
+`;
+ cy.mount();
+ cy.get('a').should('have.attr', 'href', 'https://json-schema.org/');
+ cy.get('a').should('have.text', 'JSON Schema');
+ });
+
+ it('should handle implicit reference links with an optional space', () => {
+ const markdown = `
+[json-schema] []
+
+[json-schema]: https://json-schema.org/
+`;
+ cy.mount();
+ cy.get('a').should('have.attr', 'href', 'https://json-schema.org/');
+ cy.get('a').should('have.text', 'json-schema');
+ });
+
+ it('should prioritize the second bracket as the link ID', () => {
+ const markdown = `
+[Text][actual-id]
+
+[text]: https://wrong.com/
+[actual-id]: https://right.com/
+`;
+ cy.mount();
+ cy.get('a').should('have.attr', 'href', 'https://right.com/');
+ cy.get('a').should('have.text', 'Text');
+ });
+
+ it('should ignore links without definitions', () => {
+ const markdown = `
+[Missing Definition]
+[Missing Implicit][]
+[Link][missing-id]
+`;
+ cy.mount();
+ // These should NOT be transformed into anchor tags if they are just raw text now
+ // But since the regex returns the original string if no link is found,
+ // they will be rendered as plain text by the markdown parser.
+ cy.get('a').should('not.exist');
+ });
+});
diff --git a/lib/markdownUtils.ts b/lib/markdownUtils.ts
index b0d89691e..3f386a409 100644
--- a/lib/markdownUtils.ts
+++ b/lib/markdownUtils.ts
@@ -25,13 +25,14 @@ export function transformMarkdownLinks(markdown: string): string {
// Replace reference-style links with inline links
return markdown.replace(
- /\[([^\]]+)\]\[([^\]]*)\]/g,
- (_, text: string, id: string) => {
- const link = linkDefinitions[id.toLowerCase()];
+ /!?\[([^\]]+)\](?:\s?\[([^\]]*)\])?(?!\()/g,
+ (match: string, text: string, id: string | undefined) => {
+ const linkId = (id !== undefined && id !== '' ? id : text).toLowerCase();
+ const link = linkDefinitions[linkId];
if (link) {
- return `[${text}](${link})`;
+ return (match.startsWith('!') ? '!' : '') + `[${text}](${link})`;
}
- return _; // Return the original string if no link is found
+ return match; // Return the original string if no link is found
},
);
}