diff --git a/internal/gitea/client.go b/internal/gitea/client.go index 66f346d..105165d 100644 --- a/internal/gitea/client.go +++ b/internal/gitea/client.go @@ -751,6 +751,45 @@ func (c *Client) PostComment(ctx context.Context, token, owner, repo string, ind return &comment, nil } +// RenderMarkdown renders raw markdown text to HTML using the Gitea API. +// Falls back to the raw text if the API call fails. +func (c *Client) RenderMarkdown(ctx context.Context, token, text string) (string, error) { + payload, err := json.Marshal(map[string]string{ + "Text": text, + "Mode": "gfm", + }) + if err != nil { + return text, fmt.Errorf("marshaling markdown request: %w", err) + } + + url := c.baseURL + "/api/v1/markdown" + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(string(payload))) + if err != nil { + return text, fmt.Errorf("creating markdown request: %w", err) + } + + req.Header.Set("Authorization", "token "+token) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "text/html") + + resp, err := c.httpClient.Do(req) + if err != nil { + return text, fmt.Errorf("executing markdown request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return text, fmt.Errorf("markdown API error %d", resp.StatusCode) + } + + rendered, err := io.ReadAll(resp.Body) + if err != nil { + return text, fmt.Errorf("reading markdown response: %w", err) + } + + return string(rendered), nil +} + // priorityScore returns a numeric score for sorting (lower = higher priority). func priorityScore(labels []string) int { for _, l := range labels { diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index a69232f..b592438 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -367,6 +367,17 @@ func (h *Handler) IssueDetail(w http.ResponseWriter, r *http.Request) { labels = nil } + // Render markdown body if present. + var renderedBody template.HTML + if issue.Body != "" { + rendered, err := h.Client.RenderMarkdown(r.Context(), token, issue.Body) + if err != nil { + slog.Warn("failed to render issue body markdown, using plain text", "error", err) + } else { + renderedBody = template.HTML(rendered) + } + } + // Build the content HTML using the template. tmpl, err := template.ParseFiles("internal/templates/issue_detail.html") if err != nil { @@ -377,12 +388,14 @@ func (h *Handler) IssueDetail(w http.ResponseWriter, r *http.Request) { type templateData struct { Issue *giteaclient.Issue + RenderedBody template.HTML Comments []giteaclient.Comment AvailableLabels []giteaclient.Label } data := templateData{ Issue: issue, + RenderedBody: renderedBody, Comments: comments, AvailableLabels: labels, } @@ -418,6 +431,17 @@ func (h *Handler) PullDetail(w http.ResponseWriter, r *http.Request) { return } + // Render markdown body if present. + var renderedBody template.HTML + if pr.Body != "" { + rendered, err := h.Client.RenderMarkdown(r.Context(), token, pr.Body) + if err != nil { + slog.Warn("failed to render PR body markdown, using plain text", "error", err) + } else { + renderedBody = template.HTML(rendered) + } + } + // Build the content HTML using the template. tmpl, err := template.ParseFiles("internal/templates/pull_detail.html") if err != nil { @@ -427,11 +451,13 @@ func (h *Handler) PullDetail(w http.ResponseWriter, r *http.Request) { } type templateData struct { - Pull *giteaclient.PullRequest + Pull *giteaclient.PullRequest + RenderedBody template.HTML } data := templateData{ - Pull: pr, + Pull: pr, + RenderedBody: renderedBody, } var buf strings.Builder diff --git a/internal/templates/issue_detail.html b/internal/templates/issue_detail.html index 5ec2b62..8ad3038 100644 --- a/internal/templates/issue_detail.html +++ b/internal/templates/issue_detail.html @@ -9,7 +9,9 @@ {{.Name}} {{end}} - {{if .Issue.Body}} + {{if .RenderedBody}} +