From 5a00a668ecd2a4edf7b67b7ce59936e888227a12 Mon Sep 17 00:00:00 2001 From: fds2610-bot Date: Mon, 21 Sep 2026 13:05:50 +0200 Subject: [PATCH] Ticket-Details: Repo und klickbare Ticket-Nummer in der Ueberschrift, Beschreibung auf 10 Zeilen gekuerzt Co-Authored-By: Claude Sonnet 5 --- html/css/office.css | 10 ++++++++++ html/js/office-render.js | 26 ++++++++++++++++++-------- html/js/office-scene.js | 24 +++++++++++++++++++++++- tests/frontend/office-scene.test.js | 21 +++++++++++++++++++++ 4 files changed, 72 insertions(+), 9 deletions(-) diff --git a/html/css/office.css b/html/css/office.css index a98c18e..1774cb5 100644 --- a/html/css/office.css +++ b/html/css/office.css @@ -741,7 +741,17 @@ header.office-header h1 { font-size: 0.9rem; } +.office-modal-item .office-modal-description { + white-space: pre-line; +} + +.office-modal-repo { + color: #64748b; + font-size: 0.85rem; +} + .office-modal-item-heading { + flex-wrap: wrap; display: flex; align-items: center; gap: 8px; diff --git a/html/js/office-render.js b/html/js/office-render.js index b00cc44..1c04cd3 100644 --- a/html/js/office-render.js +++ b/html/js/office-render.js @@ -1419,6 +1419,23 @@ function renderTicketModalBody(body, content) { const heading = document.createElement("div"); heading.className = "office-modal-item-heading"; + if (item.repo) { + const repoEl = document.createElement("span"); + repoEl.className = "office-modal-repo"; + repoEl.textContent = item.repo; + heading.appendChild(repoEl); + } + if (item.number) { + const numEl = item.link ? document.createElement("a") : document.createElement("span"); + numEl.className = "office-modal-ticket-number"; + numEl.textContent = `#${item.number}`; + if (item.link) { + numEl.href = item.link; + numEl.target = "_blank"; + numEl.rel = "noopener noreferrer"; + } + heading.appendChild(numEl); + } const titleEl = document.createElement("strong"); titleEl.textContent = item.title; heading.appendChild(titleEl); @@ -1433,17 +1450,10 @@ function renderTicketModalBody(body, content) { if (item.description) { const desc = document.createElement("p"); + desc.className = "office-modal-description"; desc.textContent = item.description; li.appendChild(desc); } - if (item.link) { - const a = document.createElement("a"); - a.href = item.link; - a.target = "_blank"; - a.rel = "noopener noreferrer"; - a.textContent = "Auf Codeberg öffnen"; - li.appendChild(a); - } list.appendChild(li); } body.appendChild(list); diff --git a/html/js/office-scene.js b/html/js/office-scene.js index 6a3d0a4..877b752 100644 --- a/html/js/office-scene.js +++ b/html/js/office-scene.js @@ -190,6 +190,22 @@ export function groupTicketsByColumn(tickets = []) { return groups; } +// Issue #88: Beschreibungen im Ticket-Modal werden auf diese Zeilenzahl gekuerzt. +export const TICKET_DESCRIPTION_MAX_LINES = 10; + +/** + * Kuerzt einen Text auf maxLines Zeilen; ist er laenger, ersetzt "..." den Rest. + * + * @param {string} text + * @param {number} [maxLines] + * @returns {string} + */ +export function truncateDescriptionLines(text, maxLines = TICKET_DESCRIPTION_MAX_LINES) { + const lines = String(text || "").replace(/\s+$/, "").split(/\r?\n/); + if (lines.length <= maxLines) return lines.join("\n"); + return `${lines.slice(0, maxLines).join("\n")}\n...`; +} + /** * Baut das Inhaltsmodell fuer das Board-Spalten-Modal (Visualization.md 4.2/4.5): echter * Titel/Beschreibung je Ticket, plus Link auf die Codeberg-Quelle sofern das Ticket per @@ -210,9 +226,15 @@ export function buildTicketModalContent(columnKey, tickets = [], opts = {}) { items: tickets.map((ticket) => ({ id: ticket.id, title: ticket.title, - description: ticket.description || "", + description: truncateDescriptionLines(ticket.description), type: ticket.type || null, color: ticket.color || null, + // Issue #88: Repo und Ticket-Nummer stehen in der Ueberschrift, die Nummer ist der Link. + repo: + ticket.source === "codeberg" && ticket.external_ref + ? ticket.repo || `${codebergOwner}/${codebergRepo}` + : null, + number: ticket.source === "codeberg" && ticket.external_ref ? String(ticket.external_ref) : null, link: ticket.source === "codeberg" && ticket.external_ref ? `https://codeberg.org/${ticket.repo || `${codebergOwner}/${codebergRepo}`}/issues/${ticket.external_ref}` diff --git a/tests/frontend/office-scene.test.js b/tests/frontend/office-scene.test.js index 3670383..d81fa2e 100644 --- a/tests/frontend/office-scene.test.js +++ b/tests/frontend/office-scene.test.js @@ -904,3 +904,24 @@ test("systemsWithoutHostConfig kommt ohne Hosts und ohne system_ids zurecht", () assert.deepEqual(systemsWithoutHostConfig([{ id: "a" }], [{ hostname: "a" }]), [{ id: "a" }]); assert.deepEqual(systemsWithoutHostConfig(), []); }); + +test("buildTicketModalContent liefert Repo und Ticket-Nummer fuer die Ueberschrift (Issue #88)", () => { + const content = buildTicketModalContent("backlog", [ + { id: 1, title: "A", source: "codeberg", external_ref: "3", repo: "fds2610/Steuerkram" }, + { id: 2, title: "B", source: "api", external_ref: null }, + ]); + assert.equal(content.items[0].repo, "fds2610/Steuerkram"); + assert.equal(content.items[0].number, "3"); + assert.equal(content.items[1].repo, null); + assert.equal(content.items[1].number, null); +}); + +test("buildTicketModalContent kuerzt Beschreibungen auf 10 Zeilen mit '...' (Issue #88)", () => { + const lines = Array.from({ length: 12 }, (_, i) => `Zeile ${i + 1}`); + const [long, exact] = buildTicketModalContent("backlog", [ + { id: 1, title: "A", description: lines.join("\n") }, + { id: 2, title: "B", description: lines.slice(0, 10).join("\n") }, + ]).items; + assert.equal(long.description, `${lines.slice(0, 10).join("\n")}\n...`); + assert.equal(exact.description, lines.slice(0, 10).join("\n")); +});