mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-25 14:13:40 +09:00
Comment excerpts in activity feeds previously used either the first 200 display characters of a comment or, for review comments, its first physical line. That excerpt is rendered as Markdown in the feed, so if the excerpt began with a leading blank lines or structural markdown syntax, the excerpt would render as empty or produce broken output. For example, a review comment beginning with a code fence stored only the opening fence, which rendered as an empty code block. This commit instead renders feed excerpts as prose only, dropping code, math, tables, images and HTML, which also fixes already stored excerpts. New excerpts start at the first prose line, and review comments get the same excerpt as issue comments. This produces meaningful excerpts in more cases while preserving their original Markdown. --- For a comment that contains the following: ```` ``` some code ``` hello ```` This previously rendered as: <img width="816" height="118" alt="Screenshot 2026-09-08 at 5 06 47 PM" src="https://github.com/user-attachments/assets/9d125363-72de-46bf-b47a-961245a79c5f" /> And now renders as: <img width="807" height="89" alt="Screenshot 2026-09-08 at 5 08 30 PM" src="https://github.com/user-attachments/assets/d2dcdc6d-d7a9-437f-8e9c-b845fe99fa5e" /> --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: bircni <bircni@icloud.com>
94 lines
2.9 KiB
Go
94 lines
2.9 KiB
Go
// Copyright 2024 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package markup
|
|
|
|
import (
|
|
"html/template"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gitea.dev/modules/httplib"
|
|
"gitea.dev/modules/log"
|
|
|
|
"golang.org/x/net/html"
|
|
)
|
|
|
|
type RenderCodePreviewOptions struct {
|
|
FullURL string
|
|
OwnerName string
|
|
RepoName string
|
|
CommitID string
|
|
FilePath string
|
|
|
|
LineStart, LineStop int
|
|
}
|
|
|
|
func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosStop int, htm template.HTML, err error) {
|
|
m := globalVars().codePreviewPattern.FindStringSubmatchIndex(node.Data)
|
|
if m == nil {
|
|
return 0, 0, "", nil
|
|
}
|
|
|
|
opts := RenderCodePreviewOptions{
|
|
FullURL: node.Data[m[0]:m[1]],
|
|
OwnerName: node.Data[m[2]:m[3]],
|
|
RepoName: node.Data[m[4]:m[5]],
|
|
CommitID: node.Data[m[6]:m[7]],
|
|
FilePath: node.Data[m[8]:m[9]],
|
|
}
|
|
if !httplib.IsCurrentGiteaSiteURL(ctx, opts.FullURL) {
|
|
return 0, 0, "", nil
|
|
}
|
|
u, err := url.Parse(opts.FilePath)
|
|
if err != nil {
|
|
return 0, 0, "", err
|
|
}
|
|
opts.FilePath = strings.TrimPrefix(u.Path, "/")
|
|
|
|
lineStartStr, lineStopStr, _ := strings.Cut(node.Data[m[10]:m[11]], "-")
|
|
lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
|
|
lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
|
|
opts.LineStart, opts.LineStop = lineStart, lineStop
|
|
h, err := DefaultRenderHelperFuncs.RenderRepoFileCodePreview(ctx, opts)
|
|
return m[0], m[1], h, err
|
|
}
|
|
|
|
func codePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|
if ctx.RenderOptions.FeedExcerpt {
|
|
return
|
|
}
|
|
nodeStop := node.NextSibling
|
|
for node != nodeStop {
|
|
if node.Type != html.TextNode {
|
|
node = node.NextSibling
|
|
continue
|
|
}
|
|
urlPosStart, urlPosEnd, renderedCodeBlock, err := renderCodeBlock(ctx, node)
|
|
if err != nil || renderedCodeBlock == "" {
|
|
if err != nil {
|
|
log.Error("Unable to render code preview: %v", err)
|
|
}
|
|
node = node.NextSibling
|
|
continue
|
|
}
|
|
next := node.NextSibling
|
|
textBefore := node.Data[:urlPosStart]
|
|
textAfter := node.Data[urlPosEnd:]
|
|
// "textBefore" could be empty if there is only a URL in the text node, then an empty node (p, or li) will be left here.
|
|
// However, the empty node can't be simply removed, because:
|
|
// 1. the following processors will still try to access it (need to double-check undefined behaviors)
|
|
// 2. the new node is inserted as "<p>{TextBefore}<div NewNode/>{TextAfter}</p>" (the parent could also be "li")
|
|
// then it is resolved as: "<p>{TextBefore}</p><div NewNode/><p>{TextAfter}</p>",
|
|
// so unless it could correctly replace the parent "p/li" node, it is very difficult to eliminate the "TextBefore" empty node.
|
|
node.Data = textBefore
|
|
renderedCodeNode := &html.Node{Type: html.RawNode, Data: string(ctx.RenderInternal.ProtectSafeAttrs(renderedCodeBlock))}
|
|
node.Parent.InsertBefore(renderedCodeNode, next)
|
|
if textAfter != "" {
|
|
node.Parent.InsertBefore(&html.Node{Type: html.TextNode, Data: textAfter}, next)
|
|
}
|
|
node = next
|
|
}
|
|
}
|