/* releaseNotesModal.tsx - ESP3D WebUI component file Copyright (c) 2025 Mike Melancon. All rights reserved. This code is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with This code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ import { useUiContextFn } from "../../contexts" import type { GitHubRelease } from "../../types/github.types" import type { ModalManager } from "../../types/modals.types" import { BookOpen } from "preact-feather" interface ReleaseNotesModalParams { modals: ModalManager releases: GitHubRelease[] githubUrl: string } const showReleaseNotesModal = ({ modals, releases, githubUrl }: ReleaseNotesModalParams): void => { let showPrereleases = false const getFilteredReleases = (): GitHubRelease[] => { return showPrereleases ? releases : releases.filter((r) => !r.prerelease) } const updateReleasesList = () => { const filteredReleases = getFilteredReleases() const container = document.getElementById("release-notes-container") if (!container) return // Clear and rebuild the releases list container.innerHTML = "" filteredReleases.forEach((release, index) => { const releaseDiv = document.createElement("div") releaseDiv.className = index > 0 ? "mt-4" : "" const headerDiv = document.createElement("div") headerDiv.className = "d-flex justify-content-between align-items-start" const titleDiv = document.createElement("div") const title = document.createElement("h5") title.className = "text-primary mb-1" title.innerHTML = `${release.name}${index === 0 ? '(Latest)' : ""}${release.prerelease ? '(Pre-release)' : ""}` const dateSmall = document.createElement("small") dateSmall.className = "text-muted" dateSmall.textContent = `Released on ${formatDate(release.published_at)}` titleDiv.appendChild(title) titleDiv.appendChild(dateSmall) headerDiv.appendChild(titleDiv) releaseDiv.appendChild(headerDiv) if (release.body) { const bodyDiv = document.createElement("div") bodyDiv.className = "mt-2" bodyDiv.innerHTML = getTruncatedBody(release.body) releaseDiv.appendChild(bodyDiv) } if (index < filteredReleases.length - 1) { const hr = document.createElement("hr") releaseDiv.appendChild(hr) } container.appendChild(releaseDiv) }) } const openGitHub = (e?: Event) => { if (e) e.stopPropagation() useUiContextFn.haptic() const modalIndex = modals.getModalIndex("release-notes") if (modalIndex !== -1) { modals.removeModal(modalIndex) } (window as any).open(githubUrl, "_blank") } const closeModal = (e?: Event) => { if (e) e.stopPropagation() useUiContextFn.haptic() const modalIndex = modals.getModalIndex("release-notes") if (modalIndex !== -1) { modals.removeModal(modalIndex) } } const formatDate = (dateString: string): string => { const date = new Date(dateString) return date.toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric", }) } // Simple markdown to HTML converter for common patterns const markdownToHtml = (markdown: string): string => { let html = markdown // Code blocks ```code``` - protect these first const codeBlocks: string[] = [] html = html.replace(/```([^`]+)```/g, (match, code) => { codeBlocks.push(code) return `\x00CODEBLOCK${codeBlocks.length - 1}\x00` }) // Inline code `code` const inlineCodes: string[] = [] html = html.replace(/`([^`]+)`/g, (match, code) => { inlineCodes.push(code) return `\x00INLINECODE${inlineCodes.length - 1}\x00` }) // Headers (##, ###, etc) html = html.replace(/^### (.*$)/gim, "
${code}`)
})
// Restore inline code
inlineCodes.forEach((code, index) => {
html = html.replace(`\x00INLINECODE${index}\x00`, `${code}`)
})
return html
}
// Truncate release body to first few lines for preview
const getTruncatedBody = (body: string): string => {
const lines = body.split("\n").filter((line) => line.trim() !== "")
const truncated = lines.slice(0, 10).join("\n")
return markdownToHtml(truncated)
}
const filteredReleases = getFilteredReleases()
if (modals.getModalIndex("release-notes") === -1) {
modals.addModal({
id: "release-notes",
title: (
),
content: (