diff --git a/src/client/NewsMarkdown.ts b/src/client/NewsMarkdown.ts new file mode 100644 index 000000000..88dcbf0ea --- /dev/null +++ b/src/client/NewsMarkdown.ts @@ -0,0 +1,30 @@ +const GITHUB_PR_URL_REGEX = + /(? + `[#${prNumber}](https://github.com/openfrontio/OpenFrontIO/pull/${prNumber})`, + ) + .replace( + GITHUB_COMPARE_URL_REGEX, + (_match, comparison) => + `[${comparison}](https://github.com/openfrontio/OpenFrontIO/compare/${comparison})`, + ) + .replace( + GITHUB_MENTION_REGEX, + (_match, prefix, username) => + `${prefix}[@${username}](https://github.com/${username})`, + ) + ); +} diff --git a/src/client/NewsModal.ts b/src/client/NewsModal.ts index fe0d622f3..dc34a06bd 100644 --- a/src/client/NewsModal.ts +++ b/src/client/NewsModal.ts @@ -6,6 +6,7 @@ import { translateText } from "../client/Utils"; import "./components/baseComponents/Modal"; import { BaseModal } from "./components/BaseModal"; import { modalHeader } from "./components/ui/ModalHeader"; +import { normalizeNewsMarkdown } from "./NewsMarkdown"; import changelog from "/changelog.md?url"; import megaphone from "/images/Megaphone.svg?url"; @@ -67,23 +68,9 @@ export class NewsModal extends BaseModal { this.initialized = true; fetch(`${changelog}?v=${encodeURIComponent(version.trim())}`) .then((response) => (response.ok ? response.text() : "Failed to load")) - .then((markdown) => - markdown - // Convert bold header lines (e.g. "**Title**") into real Markdown headers - // Exclude lines starting with - or * to avoid converting bullet points - .replace(/^([^\-*\s].*?) \*\*(.+?)\*\*$/gm, "## $1 $2") - .replace( - /(? - `[#${prNumber}](https://github.com/openfrontio/OpenFrontIO/pull/${prNumber})`, - ) - .replace( - /(? - `[${comparison}](https://github.com/openfrontio/OpenFrontIO/compare/${comparison})`, - ), - ) - .then((markdown) => (this.markdown = markdown)); + .then((markdown) => normalizeNewsMarkdown(markdown)) + .then((markdown) => (this.markdown = markdown)) + .catch(() => (this.markdown = "Failed to load")); } } } diff --git a/tests/client/NewsMarkdown.test.ts b/tests/client/NewsMarkdown.test.ts new file mode 100644 index 000000000..94f83332b --- /dev/null +++ b/tests/client/NewsMarkdown.test.ts @@ -0,0 +1,49 @@ +import { normalizeNewsMarkdown } from "../../src/client/NewsMarkdown"; + +describe("normalizeNewsMarkdown", () => { + it("converts openfront pull request URLs to short markdown links", () => { + const input = + "Fix attack logic in https://github.com/openfrontio/OpenFrontIO/pull/1234"; + + const result = normalizeNewsMarkdown(input); + + expect(result).toContain( + "[#1234](https://github.com/openfrontio/OpenFrontIO/pull/1234)", + ); + }); + + it("converts openfront compare URLs to markdown links", () => { + const input = + "Full Changelog: https://github.com/openfrontio/OpenFrontIO/compare/v1.0.0...v1.1.0"; + + const result = normalizeNewsMarkdown(input); + + expect(result).toContain( + "[v1.0.0...v1.1.0](https://github.com/openfrontio/OpenFrontIO/compare/v1.0.0...v1.1.0)", + ); + }); + + it("converts github @mentions to profile links", () => { + const input = "- Feature by @evanpelle in release notes"; + + const result = normalizeNewsMarkdown(input); + + expect(result).toContain("[@evanpelle](https://github.com/evanpelle)"); + }); + + it("does not convert existing markdown-linked mentions", () => { + const input = "Credit [@evanpelle](https://github.com/evanpelle)"; + + const result = normalizeNewsMarkdown(input); + + expect(result).toBe(input); + }); + + it("does not convert email addresses", () => { + const input = "Contact support@openfront.io for help"; + + const result = normalizeNewsMarkdown(input); + + expect(result).toBe(input); + }); +});