# HG changeset patch # User Mike Becker # Date 1767726534 -3600 # Node ID 109850e92e95a54ec6721e2219625122c4c5fe68 # Parent 179bda934121ff3d6d40dad21114f2866e71e155 add Markdown preview - resolves #774 diff -r 179bda934121 -r 109850e92e95 src/main/kotlin/de/uapcore/lightpit/Constants.kt --- a/src/main/kotlin/de/uapcore/lightpit/Constants.kt Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/Constants.kt Tue Jan 06 20:08:54 2026 +0100 @@ -29,7 +29,7 @@ /** * A data in yyyy-mm-dd format to identify the release. */ - const val VERSION_DATE = "2025-10-09" + const val VERSION_DATE = "2026-01-06" /** * The path where the JSP files reside. diff -r 179bda934121 -r 109850e92e95 src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt --- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Tue Jan 06 20:08:54 2026 +0100 @@ -221,6 +221,11 @@ response.writer.write(json) } + fun renderText(text: String, type: String = "plain") { + response.contentType = "text/${type}; charset=utf-8" + response.writer.write(text) + } + fun render(page: String? = null) { page?.let { contentPage = it } forward("site") diff -r 179bda934121 -r 109850e92e95 src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt --- a/src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt Tue Jan 06 20:08:54 2026 +0100 @@ -1,5 +1,11 @@ package de.uapcore.lightpit.logic +import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension +import com.vladsch.flexmark.ext.tables.TablesExtension +import com.vladsch.flexmark.html.HtmlRenderer +import com.vladsch.flexmark.parser.Parser +import com.vladsch.flexmark.util.data.MutableDataSet +import com.vladsch.flexmark.util.data.SharedDataKeys import de.uapcore.lightpit.HttpRequest import de.uapcore.lightpit.dao.DataAccessObject import de.uapcore.lightpit.dateOptValidator @@ -283,4 +289,25 @@ // always pretend that the operation was successful - if there was nothing to remove, it's okay http.renderCommit("${pathInfos.issuesHref}${issue.id}") -} \ No newline at end of file +} + +fun formatMarkdown(text: String?): String { + if (text == null) return "" + + val options = MutableDataSet() + .set(SharedDataKeys.EXTENSIONS, listOf(TablesExtension.create(), StrikethroughExtension.create())) + val parser = Parser.builder(options).build() + val renderer = HtmlRenderer.builder( + options + .set(HtmlRenderer.ESCAPE_HTML, true) + ).build() + + val formatEmojis = { text: String -> + text + .replace("(/)", "✅") + .replace("(x)", "❌") + .replace("(!)", "⚡") + } + + return renderer.render(parser.parse(formatEmojis(text))) +} diff -r 179bda934121 -r 109850e92e95 src/main/kotlin/de/uapcore/lightpit/servlet/IssuesServlet.kt --- a/src/main/kotlin/de/uapcore/lightpit/servlet/IssuesServlet.kt Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/IssuesServlet.kt Tue Jan 06 20:08:54 2026 +0100 @@ -17,6 +17,7 @@ get("/", this::issues) get("/search", this::issueSearch) + post("/preview-markdown", this::previewMarkdown) get("/%issue", this::issue) get("/%issue/edit", this::issueForm) get("/%issue/progress", this::issueProgress) @@ -90,6 +91,10 @@ ) } + private fun previewMarkdown(http: HttpRequest, dao: DataAccessObject) { + http.renderText(formatMarkdown(http.body), "markdown") + } + private fun issueForm(http: HttpRequest, dao: DataAccessObject) { val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) if (issue == null) { diff -r 179bda934121 -r 109850e92e95 src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Tue Jan 06 20:08:54 2026 +0100 @@ -25,17 +25,12 @@ package de.uapcore.lightpit.viewmodel -import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension -import com.vladsch.flexmark.ext.tables.TablesExtension -import com.vladsch.flexmark.html.HtmlRenderer -import com.vladsch.flexmark.parser.Parser -import com.vladsch.flexmark.util.data.MutableDataSet -import com.vladsch.flexmark.util.data.SharedDataKeys import de.uapcore.lightpit.HttpRequest import de.uapcore.lightpit.OptionalPathInfo import de.uapcore.lightpit.dao.DataAccessObject import de.uapcore.lightpit.entities.* import de.uapcore.lightpit.logic.compareEtaTo +import de.uapcore.lightpit.logic.formatMarkdown import de.uapcore.lightpit.types.* import java.sql.Timestamp import kotlin.math.roundToInt @@ -132,19 +127,8 @@ val commitLinks: List val openedVariant: Variant? = ((pathInfos as? PathInfosFull)?.variantInfo as? OptionalPathInfo.Specific)?.elem - private val parser: Parser - private val renderer: HtmlRenderer - init { - val options = MutableDataSet() - .set(SharedDataKeys.EXTENSIONS, listOf(TablesExtension.create(), StrikethroughExtension.create())) - parser = Parser.builder(options).build() - renderer = HtmlRenderer.builder( - options - .set(HtmlRenderer.ESCAPE_HTML, true) - ).build() - - issue.description = formatMarkdown(issue.description ?: "") + issue.description = formatMarkdown(issue.description) for (comment in comments) { comment.commentFormatted = formatMarkdown(comment.comment) } @@ -165,14 +149,6 @@ append(hash) toString() } - - private fun formatEmojis(text: String) = text - .replace("(/)", "✅") - .replace("(x)", "❌") - .replace("(!)", "⚡") - - private fun formatMarkdown(text: String) = - renderer.render(parser.parse(formatEmojis(text))) } class IssueEditView( diff -r 179bda934121 -r 109850e92e95 src/main/resources/localization/strings.properties --- a/src/main/resources/localization/strings.properties Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/resources/localization/strings.properties Tue Jan 06 20:08:54 2026 +0100 @@ -33,6 +33,7 @@ button.component.create=New Component button.component.edit=Edit Component button.dismiss=Dismiss +button.edit=Edit button.issue.all=All Issues button.issue.create.another=Create another Issue button.issue.create=New Issue @@ -41,6 +42,7 @@ button.issue.progress=Progress button.issue.resolve=Resolve button.okay=OK +button.preview=Preview button.project.create=New Project button.project.edit=Edit Project button.remove=Remove diff -r 179bda934121 -r 109850e92e95 src/main/resources/localization/strings_de.properties --- a/src/main/resources/localization/strings_de.properties Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/resources/localization/strings_de.properties Tue Jan 06 20:08:54 2026 +0100 @@ -33,6 +33,7 @@ button.component.create=Neue Komponente button.component.edit=Komponente Bearbeiten button.dismiss=Nicht Jetzt +button.edit=Bearbeiten button.issue.all=Alle Vorg\u00e4nge button.issue.create.another=Weiteren Vorgang erstellen button.issue.create=Neuer Vorgang @@ -41,6 +42,7 @@ button.issue.progress=In Arbeit button.issue.resolve=Erledigt button.okay=OK +button.preview=Vorschau button.project.create=Neues Projekt button.project.edit=Projekt Bearbeiten button.remove=Entfernen diff -r 179bda934121 -r 109850e92e95 src/main/webapp/WEB-INF/changelogs/changelog-de.jspf --- a/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Tue Jan 06 20:08:54 2026 +0100 @@ -34,6 +34,7 @@
  • Schaltflächen hinzugefügt, die schnelleren Zugang zu den Editoren für Versionen, Komponenten und Varianten bieten.
  • Es können nun neue Vorgänge direkt mit einer Verknüpfung zu einem existierenden Vorgang erstellt werden.
  • Neuen Filter "zeige nur nicht-blockierte" hinzugefügt.
  • +
  • Vorschau für Markdown hinzugefügt.
  • Vorgänge können nun auch direkt über die Vorgangsnummer (anstatt Raute + Nummer) verlinkt werden.
  • Die Vorschläge in den Suchfeldern für Vorgänge sind nun absteigend nach Vorgangsnummer sortiert.
  • Die Standardkategorie für neue Vorgänge in veröffentlichten Versionen ist nun "Fehler" anstelle von "Feature".
  • diff -r 179bda934121 -r 109850e92e95 src/main/webapp/WEB-INF/changelogs/changelog.jspf --- a/src/main/webapp/WEB-INF/changelogs/changelog.jspf Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/webapp/WEB-INF/changelogs/changelog.jspf Tue Jan 06 20:08:54 2026 +0100 @@ -34,6 +34,7 @@
  • Add buttons and hover-icons to quickly access the editor for versions, components, and variants.
  • Add the possibility to create new related issues with one click.
  • Add new filter "show only non-blocked".
  • +
  • Add markdown preview.
  • Change that you can now relate issues by just submitting their number (instead of hash + number).
  • Change that issues suggested by the search boxes are now sorted by ID in descending order.
  • Change that the default category for new issues in released versions is Bug instead of Feature.
  • diff -r 179bda934121 -r 109850e92e95 src/main/webapp/WEB-INF/jsp/issue-form.jsp --- a/src/main/webapp/WEB-INF/jsp/issue-form.jsp Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/webapp/WEB-INF/jsp/issue-form.jsp Tue Jan 06 20:08:54 2026 +0100 @@ -139,7 +139,11 @@ - + + + + + <%@include file="../jspf/markdown-editor.jspf" %> @@ -193,7 +197,12 @@ - + + + + + <%@include file="../jspf/markdown-editor.jspf" %> + diff -r 179bda934121 -r 109850e92e95 src/main/webapp/WEB-INF/jsp/issue-view.jsp --- a/src/main/webapp/WEB-INF/jsp/issue-view.jsp Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/webapp/WEB-INF/jsp/issue-view.jsp Tue Jan 06 20:08:54 2026 +0100 @@ -334,7 +334,12 @@ - + @@ -383,7 +388,11 @@ diff -r 179bda934121 -r 109850e92e95 src/main/webapp/WEB-INF/jspf/markdown-editor.jspf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/WEB-INF/jspf/markdown-editor.jspf Tue Jan 06 20:08:54 2026 +0100 @@ -0,0 +1,22 @@ +<%-- + mde_id: String + mde_name: String + mde_rows: String + mde_value: String +--%> +
    +
    + + +
    + +
    + +
    + + + + + diff -r 179bda934121 -r 109850e92e95 src/main/webapp/issue-editor.js --- a/src/main/webapp/issue-editor.js Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/webapp/issue-editor.js Tue Jan 06 20:08:54 2026 +0100 @@ -56,6 +56,44 @@ } } +function initMarkdownEditor(id) { + const btn_preview = document.getElementById(`${id}-btn-preview`); + const btn_edit = document.getElementById(`${id}-btn-edit`); + const textarea = document.getElementById(`${id}`); + const preview = document.getElementById(`${id}-preview`); + btn_preview.addEventListener('click', () => { + textarea.style.display = 'none'; + preview.style.display = 'block'; + textarea.dataset.original = textarea.value; + btn_preview.style.display = 'none'; + btn_edit.style.display = 'inline-block'; + const req = new XMLHttpRequest(); + req.addEventListener("load", (evt) => { + if (evt.target.status === 200) { + preview.innerHTML = evt.target.responseText; + } else { + // revert the button click as fallback + setTimeout(() => btn_edit.click(), 100); + } + }); + let url = baseHref + 'issues/preview-markdown'; + req.open("POST", url); + req.setRequestHeader("Content-Type", "text/plain"); + req.send(textarea.value); + }); + btn_edit.addEventListener('click', () => { + textarea.readOnly = false; + textarea.value = textarea.dataset.original; + textarea.style.display = 'block'; + preview.style.display = 'none'; + textarea.focus(); + btn_preview.style.display = 'inline-block'; + btn_edit.style.display = 'none'; + }); + btn_edit.style.display = 'none'; + preview.style.display = 'none'; +} + window.addEventListener("load", () => { toggleVariantStatus(); const sbox = document.getElementById('linkable-issues'); diff -r 179bda934121 -r 109850e92e95 src/main/webapp/projects.css --- a/src/main/webapp/projects.css Tue Jan 06 19:14:24 2026 +0100 +++ b/src/main/webapp/projects.css Tue Jan 06 20:08:54 2026 +0100 @@ -174,6 +174,22 @@ color: #556080; } +div.mde-toolbar { + margin-bottom: .25em; +} + +div.mde-toolbar button { + font-size: small; +} + +div.mde-preview { + border-style: solid; + border-width: 2px; + border-color: silver; + padding: .25em; + border-radius: 8px; +} + span.eta-overdue { color: red; }
    + + + + <%@include file="../jspf/markdown-editor.jspf" %> +
    - + + + + + <%@include file="../jspf/markdown-editor.jspf" %>