Add setting to restrict count of lines being displayed & only highlight those lines
This commit is contained in:
parent
069d87b80f
commit
2b6546adc9
9 changed files with 117 additions and 28 deletions
|
@ -2338,6 +2338,8 @@ LEVEL = Info
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
|
;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits)
|
||||||
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
|
;MERMAID_MAX_SOURCE_CHARACTERS = 5000
|
||||||
|
;; Set the maximum number of lines allowed for a filepreview. (Set to -1 to disable limits; set to 0 to disable the feature)
|
||||||
|
;FILEPREVIEW_MAX_LINES = 50
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"html/template"
|
"html/template"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -12,6 +13,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/charset"
|
"code.gitea.io/gitea/modules/charset"
|
||||||
|
"code.gitea.io/gitea/modules/highlight"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
@ -31,9 +33,15 @@ type FilePreview struct {
|
||||||
filePath string
|
filePath string
|
||||||
start int
|
start int
|
||||||
end int
|
end int
|
||||||
|
isTruncated bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview {
|
func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Locale) *FilePreview {
|
||||||
|
if (setting.FilePreviewMaxLines == 0) {
|
||||||
|
// Feature is disabled
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
preview := &FilePreview{}
|
preview := &FilePreview{}
|
||||||
|
|
||||||
m := filePreviewPattern.FindStringSubmatchIndex(node.Data)
|
m := filePreviewPattern.FindStringSubmatchIndex(node.Data)
|
||||||
|
@ -63,18 +71,20 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
|
||||||
preview.end = m[1]
|
preview.end = m[1]
|
||||||
|
|
||||||
projPathSegments := strings.Split(projPath, "/")
|
projPathSegments := strings.Split(projPath, "/")
|
||||||
fileContent, err := DefaultProcessorHelper.GetRepoFileContent(
|
var language string
|
||||||
|
fileBlob, err := DefaultProcessorHelper.GetRepoFileBlob(
|
||||||
ctx.Ctx,
|
ctx.Ctx,
|
||||||
projPathSegments[len(projPathSegments)-2],
|
projPathSegments[len(projPathSegments)-2],
|
||||||
projPathSegments[len(projPathSegments)-1],
|
projPathSegments[len(projPathSegments)-1],
|
||||||
commitSha, preview.filePath,
|
commitSha, preview.filePath,
|
||||||
|
&language,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
lineSpecs := strings.Split(hash, "-")
|
lineSpecs := strings.Split(hash, "-")
|
||||||
lineCount := len(fileContent)
|
// lineCount := len(fileContent)
|
||||||
|
|
||||||
commitLinkBuffer := new(bytes.Buffer)
|
commitLinkBuffer := new(bytes.Buffer)
|
||||||
err = html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
|
err = html.Render(commitLinkBuffer, createLink(node.Data[m[0]:m[5]], commitSha[0:7], "text black"))
|
||||||
|
@ -82,28 +92,31 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
|
||||||
log.Error("failed to render commitLink: %v", err)
|
log.Error("failed to render commitLink: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(lineSpecs) == 1 {
|
var startLine, endLine int
|
||||||
line, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
|
|
||||||
if line < 1 || line > lineCount {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
preview.fileContent = fileContent[line-1 : line]
|
if len(lineSpecs) == 1 {
|
||||||
|
startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
|
||||||
|
endLine = startLine
|
||||||
|
// if line < 1 || line > lineCount {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// preview.fileContent = fileContent[line-1 : line]
|
||||||
preview.subTitle = locale.Tr(
|
preview.subTitle = locale.Tr(
|
||||||
"markup.filepreview.line", line,
|
"markup.filepreview.line", startLine,
|
||||||
template.HTML(commitLinkBuffer.String()),
|
template.HTML(commitLinkBuffer.String()),
|
||||||
)
|
)
|
||||||
|
|
||||||
preview.lineOffset = line - 1
|
preview.lineOffset = startLine - 1
|
||||||
} else {
|
} else {
|
||||||
startLine, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
|
startLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[0], "L"))
|
||||||
endLine, _ := strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
|
endLine, _ = strconv.Atoi(strings.TrimPrefix(lineSpecs[1], "L"))
|
||||||
|
|
||||||
if startLine < 1 || endLine < 1 || startLine > lineCount || endLine > lineCount || endLine < startLine {
|
// if startLine < 1 || endLine < 1 || startLine > lineCount || endLine > lineCount || endLine < startLine {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
preview.fileContent = fileContent[startLine-1 : endLine]
|
// preview.fileContent = fileContent[startLine-1 : endLine]
|
||||||
preview.subTitle = locale.Tr(
|
preview.subTitle = locale.Tr(
|
||||||
"markup.filepreview.lines", startLine, endLine,
|
"markup.filepreview.lines", startLine, endLine,
|
||||||
template.HTML(commitLinkBuffer.String()),
|
template.HTML(commitLinkBuffer.String()),
|
||||||
|
@ -112,6 +125,50 @@ func NewFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca
|
||||||
preview.lineOffset = startLine - 1
|
preview.lineOffset = startLine - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lineCount := endLine - (startLine-1)
|
||||||
|
if startLine < 1 || endLine < 1 || lineCount < 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.FilePreviewMaxLines > 0 && lineCount > setting.FilePreviewMaxLines {
|
||||||
|
preview.isTruncated = true
|
||||||
|
lineCount = setting.FilePreviewMaxLines
|
||||||
|
}
|
||||||
|
|
||||||
|
dataRc, err := fileBlob.DataAsync()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer dataRc.Close()
|
||||||
|
|
||||||
|
reader := bufio.NewReader(dataRc)
|
||||||
|
|
||||||
|
// skip all lines until we find our startLine
|
||||||
|
for i := 1; i < startLine; i++ {
|
||||||
|
_, err := reader.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// capture the lines we're interested in
|
||||||
|
lineBuffer := new(bytes.Buffer)
|
||||||
|
for i := 0; i < lineCount; i++ {
|
||||||
|
buf, err := reader.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lineBuffer.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// highlight the file...
|
||||||
|
fileContent, _, err := highlight.File(fileBlob.Name(), language, lineBuffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
log.Error("highlight.File failed, fallback to plain text: %v", err)
|
||||||
|
fileContent = highlight.PlainText(lineBuffer.Bytes())
|
||||||
|
}
|
||||||
|
preview.fileContent = fileContent
|
||||||
|
|
||||||
return preview
|
return preview
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,6 +315,20 @@ func (p *FilePreview) CreateHTML(locale translation.Locale) *html.Node {
|
||||||
Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}},
|
Attr: []html.Attribute{{Key: "class", Val: "file-preview-box"}},
|
||||||
}
|
}
|
||||||
node.AppendChild(header)
|
node.AppendChild(header)
|
||||||
|
|
||||||
|
if (p.isTruncated) {
|
||||||
|
warning := &html.Node{
|
||||||
|
Type: html.ElementNode,
|
||||||
|
Data: atom.Div.String(),
|
||||||
|
Attr: []html.Attribute{{Key: "class", Val: "ui warning message tw-text-left"}},
|
||||||
|
}
|
||||||
|
warning.AppendChild(&html.Node{
|
||||||
|
Type: html.TextNode,
|
||||||
|
Data: locale.TrString("markup.filepreview.truncated"),
|
||||||
|
})
|
||||||
|
node.AppendChild(warning)
|
||||||
|
}
|
||||||
|
|
||||||
node.AppendChild(twrapper)
|
node.AppendChild(twrapper)
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
|
@ -1059,7 +1059,7 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
if ctx.Metas == nil {
|
if ctx.Metas == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if DefaultProcessorHelper.GetRepoFileContent == nil {
|
if DefaultProcessorHelper.GetRepoFileBlob == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
// "html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -32,7 +32,7 @@ const (
|
||||||
|
|
||||||
type ProcessorHelper struct {
|
type ProcessorHelper struct {
|
||||||
IsUsernameMentionable func(ctx context.Context, username string) bool
|
IsUsernameMentionable func(ctx context.Context, username string) bool
|
||||||
GetRepoFileContent func(ctx context.Context, ownerName, repoName, commitSha, filePath string) ([]template.HTML, error)
|
GetRepoFileBlob func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error)
|
||||||
|
|
||||||
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
|
ElementDir string // the direction of the elements, eg: "ltr", "rtl", "auto", default to no direction attribute
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,7 @@ func createDefaultPolicy() *bluemonday.Policy {
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ambiguous-code-point$")).OnElements("span")
|
||||||
policy.AllowAttrs("data-tooltip-content").OnElements("span")
|
policy.AllowAttrs("data-tooltip-content").OnElements("span")
|
||||||
policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a")
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("muted|(text black)")).OnElements("a")
|
||||||
|
policy.AllowAttrs("class").Matching(regexp.MustCompile("^ui warning message tw-text-left$")).OnElements("div")
|
||||||
|
|
||||||
// Allow generally safe attributes
|
// Allow generally safe attributes
|
||||||
generalSafeAttrs := []string{
|
generalSafeAttrs := []string{
|
||||||
|
|
|
@ -15,6 +15,7 @@ var (
|
||||||
ExternalMarkupRenderers []*MarkupRenderer
|
ExternalMarkupRenderers []*MarkupRenderer
|
||||||
ExternalSanitizerRules []MarkupSanitizerRule
|
ExternalSanitizerRules []MarkupSanitizerRule
|
||||||
MermaidMaxSourceCharacters int
|
MermaidMaxSourceCharacters int
|
||||||
|
FilePreviewMaxLines int
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -62,6 +63,7 @@ func loadMarkupFrom(rootCfg ConfigProvider) {
|
||||||
mustMapSetting(rootCfg, "markdown", &Markdown)
|
mustMapSetting(rootCfg, "markdown", &Markdown)
|
||||||
|
|
||||||
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
|
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
|
||||||
|
FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50)
|
||||||
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
|
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
|
||||||
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)
|
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)
|
||||||
|
|
||||||
|
|
|
@ -3711,3 +3711,4 @@ submodule = Submodule
|
||||||
[markup]
|
[markup]
|
||||||
filepreview.line = Line %[1]d in %[2]s
|
filepreview.line = Line %[1]d in %[2]s
|
||||||
filepreview.lines = Lines %[1]d to %[2]d in %[3]s
|
filepreview.lines = Lines %[1]d to %[2]d in %[3]s
|
||||||
|
filepreview.truncated = Preview has been truncated
|
||||||
|
|
|
@ -6,15 +6,17 @@ package markup
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"io"
|
// "html/template"
|
||||||
|
// "io"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/perm/access"
|
"code.gitea.io/gitea/models/perm/access"
|
||||||
"code.gitea.io/gitea/models/repo"
|
"code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/models/user"
|
"code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/highlight"
|
// "code.gitea.io/gitea/modules/highlight"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
gitea_context "code.gitea.io/gitea/services/context"
|
gitea_context "code.gitea.io/gitea/services/context"
|
||||||
|
@ -39,7 +41,7 @@ func ProcessorHelper() *markup.ProcessorHelper {
|
||||||
// when using gitea context (web context), use user's visibility and user's permission to check
|
// when using gitea context (web context), use user's visibility and user's permission to check
|
||||||
return user.IsUserVisibleToViewer(giteaCtx, mentionedUser, giteaCtx.Doer)
|
return user.IsUserVisibleToViewer(giteaCtx, mentionedUser, giteaCtx.Doer)
|
||||||
},
|
},
|
||||||
GetRepoFileContent: func(ctx context.Context, ownerName, repoName, commitSha, filePath string) ([]template.HTML, error) {
|
GetRepoFileBlob: func(ctx context.Context, ownerName, repoName, commitSha, filePath string, language *string) (*git.Blob, error) {
|
||||||
repo, err := repo.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
|
repo, err := repo.GetRepositoryByOwnerAndName(ctx, ownerName, repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -70,17 +72,21 @@ func ProcessorHelper() *markup.ProcessorHelper {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
language, err := file_service.TryGetContentLanguage(gitRepo, commitSha, filePath)
|
if language != nil {
|
||||||
|
*language, err = file_service.TryGetContentLanguage(gitRepo, commitSha, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to get file language for %-v:%s. Error: %v", repo, filePath, err)
|
log.Error("Unable to get file language for %-v:%s. Error: %v", repo, filePath, err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
blob, err := commit.GetBlobByPath(filePath)
|
blob, err := commit.GetBlobByPath(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dataRc, err := blob.DataAsync()
|
return blob, nil
|
||||||
|
|
||||||
|
/*dataRc, err := blob.DataAsync()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -97,7 +103,7 @@ func ProcessorHelper() *markup.ProcessorHelper {
|
||||||
fileContent = highlight.PlainText(buf)
|
fileContent = highlight.PlainText(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileContent, nil
|
return fileContent, nil*/
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,12 @@
|
||||||
background: var(--color-box-header);
|
background: var(--color-box-header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markup .file-preview-box .warning {
|
||||||
|
border-radius: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: .5rem .5rem .5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.markup .file-preview-box .header > a {
|
.markup .file-preview-box .header > a {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue