mirror of
https://github.com/go-gitea/gitea
synced 2025-12-07 13:28:25 +00:00
Add support for 3D/CAD file formats preview (#34794)
Fix #34775 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
co-authored by
wxiaoguang
parent
f74a13610d
commit
176962c03e
@@ -244,7 +244,7 @@ func editFileOpenExisting(ctx *context.Context) (prefetch []byte, dataRc io.Read
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if fInfo.isLFSFile {
|
||||
if fInfo.isLFSFile() {
|
||||
lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
_ = dataRc.Close()
|
||||
@@ -298,7 +298,7 @@ func EditFile(ctx *context.Context) {
|
||||
ctx.Data["FileSize"] = fInfo.fileSize
|
||||
|
||||
// Only some file types are editable online as text.
|
||||
if fInfo.isLFSFile {
|
||||
if fInfo.isLFSFile() {
|
||||
ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
|
||||
} else if !fInfo.st.IsRepresentableAsText() {
|
||||
ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
|
||||
|
||||
@@ -267,8 +267,10 @@ func LFSFileGet(ctx *context.Context) {
|
||||
buf = buf[:n]
|
||||
|
||||
st := typesniffer.DetectContentType(buf)
|
||||
// FIXME: there is no IsPlainText set, but template uses it
|
||||
ctx.Data["IsTextFile"] = st.IsText()
|
||||
ctx.Data["FileSize"] = meta.Size
|
||||
// FIXME: the last field is the URL-base64-encoded filename, it should not be "direct"
|
||||
ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s/%s.git/info/lfs/objects/%s/%s", setting.AppURL, url.PathEscape(ctx.Repo.Repository.OwnerName), url.PathEscape(ctx.Repo.Repository.Name), url.PathEscape(meta.Oid), "direct")
|
||||
switch {
|
||||
case st.IsRepresentableAsText():
|
||||
@@ -309,8 +311,6 @@ func LFSFileGet(ctx *context.Context) {
|
||||
}
|
||||
ctx.Data["LineNums"] = gotemplate.HTML(output.String())
|
||||
|
||||
case st.IsPDF():
|
||||
ctx.Data["IsPDFFile"] = true
|
||||
case st.IsVideo():
|
||||
ctx.Data["IsVideoFile"] = true
|
||||
case st.IsAudio():
|
||||
|
||||
+28
-25
@@ -59,60 +59,63 @@ const (
|
||||
)
|
||||
|
||||
type fileInfo struct {
|
||||
isTextFile bool
|
||||
isLFSFile bool
|
||||
fileSize int64
|
||||
lfsMeta *lfs.Pointer
|
||||
st typesniffer.SniffedType
|
||||
fileSize int64
|
||||
lfsMeta *lfs.Pointer
|
||||
st typesniffer.SniffedType
|
||||
}
|
||||
|
||||
func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte, io.ReadCloser, *fileInfo, error) {
|
||||
dataRc, err := blob.DataAsync()
|
||||
func (fi *fileInfo) isLFSFile() bool {
|
||||
return fi.lfsMeta != nil && fi.lfsMeta.Oid != ""
|
||||
}
|
||||
|
||||
func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) (buf []byte, dataRc io.ReadCloser, fi *fileInfo, err error) {
|
||||
dataRc, err = blob.DataAsync()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
const prefetchSize = lfs.MetaFileMaxSize
|
||||
|
||||
buf = make([]byte, prefetchSize)
|
||||
n, _ := util.ReadAtMost(dataRc, buf)
|
||||
buf = buf[:n]
|
||||
|
||||
st := typesniffer.DetectContentType(buf)
|
||||
isTextFile := st.IsText()
|
||||
fi = &fileInfo{fileSize: blob.Size(), st: typesniffer.DetectContentType(buf)}
|
||||
|
||||
// FIXME: what happens when README file is an image?
|
||||
if !isTextFile || !setting.LFS.StartServer {
|
||||
return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil
|
||||
if !fi.st.IsText() || !setting.LFS.StartServer {
|
||||
return buf, dataRc, fi, nil
|
||||
}
|
||||
|
||||
pointer, _ := lfs.ReadPointerFromBuffer(buf)
|
||||
if !pointer.IsValid() { // fallback to plain file
|
||||
return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil
|
||||
if !pointer.IsValid() { // fallback to a plain file
|
||||
return buf, dataRc, fi, nil
|
||||
}
|
||||
|
||||
meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid)
|
||||
if err != nil { // fallback to plain file
|
||||
if err != nil { // fallback to a plain file
|
||||
log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err)
|
||||
return buf, dataRc, &fileInfo{isTextFile, false, blob.Size(), nil, st}, nil
|
||||
return buf, dataRc, fi, nil
|
||||
}
|
||||
|
||||
dataRc.Close()
|
||||
|
||||
// close the old dataRc and open the real LFS target
|
||||
_ = dataRc.Close()
|
||||
dataRc, err = lfs.ReadMetaObject(pointer)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
buf = make([]byte, 1024)
|
||||
buf = make([]byte, prefetchSize)
|
||||
n, err = util.ReadAtMost(dataRc, buf)
|
||||
if err != nil {
|
||||
dataRc.Close()
|
||||
return nil, nil, nil, err
|
||||
_ = dataRc.Close()
|
||||
return nil, nil, fi, err
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
st = typesniffer.DetectContentType(buf)
|
||||
|
||||
return buf, dataRc, &fileInfo{st.IsText(), true, meta.Size, &meta.Pointer, st}, nil
|
||||
fi.st = typesniffer.DetectContentType(buf)
|
||||
fi.fileSize = blob.Size()
|
||||
fi.lfsMeta = &meta.Pointer
|
||||
return buf, dataRc, fi, nil
|
||||
}
|
||||
|
||||
func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
|
||||
|
||||
+171
-179
@@ -23,6 +23,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/typesniffer"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
@@ -40,7 +41,128 @@ func prepareLatestCommitInfo(ctx *context.Context) bool {
|
||||
return loadLatestCommitData(ctx, commit)
|
||||
}
|
||||
|
||||
func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
|
||||
func prepareFileViewLfsAttrs(ctx *context.Context) (*attribute.Attributes, bool) {
|
||||
attrsMap, err := attribute.CheckAttributes(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, attribute.CheckAttributeOpts{
|
||||
Filenames: []string{ctx.Repo.TreePath},
|
||||
Attributes: []string{attribute.LinguistGenerated, attribute.LinguistVendored, attribute.LinguistLanguage, attribute.GitlabLanguage},
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("attribute.CheckAttributes", err)
|
||||
return nil, false
|
||||
}
|
||||
attrs := attrsMap[ctx.Repo.TreePath]
|
||||
if attrs == nil {
|
||||
// this case shouldn't happen, just in case.
|
||||
setting.PanicInDevOrTesting("no attributes found for %s", ctx.Repo.TreePath)
|
||||
attrs = attribute.NewAttributes()
|
||||
}
|
||||
ctx.Data["IsVendored"], ctx.Data["IsGenerated"] = attrs.GetVendored().Value(), attrs.GetGenerated().Value()
|
||||
return attrs, true
|
||||
}
|
||||
|
||||
func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte, utf8Reader io.Reader) bool {
|
||||
markupType := markup.DetectMarkupTypeByFileName(filename)
|
||||
if markupType == "" {
|
||||
markupType = markup.DetectRendererType(filename, sniffedType, prefetchBuf)
|
||||
}
|
||||
if markupType == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
ctx.Data["HasSourceRenderedToggle"] = true
|
||||
|
||||
if ctx.FormString("display") == "source" {
|
||||
return false
|
||||
}
|
||||
|
||||
ctx.Data["MarkupType"] = markupType
|
||||
metas := ctx.Repo.Repository.ComposeRepoFileMetas(ctx)
|
||||
metas["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL()
|
||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||
}).
|
||||
WithMarkupType(markupType).
|
||||
WithRelativePath(ctx.Repo.TreePath).
|
||||
WithMetas(metas)
|
||||
|
||||
var err error
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, utf8Reader)
|
||||
if err != nil {
|
||||
ctx.ServerError("Render", err)
|
||||
return true
|
||||
}
|
||||
// to prevent iframe from loading third-party url
|
||||
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
|
||||
return true
|
||||
}
|
||||
|
||||
func handleFileViewRenderSource(ctx *context.Context, filename string, attrs *attribute.Attributes, fInfo *fileInfo, utf8Reader io.Reader) bool {
|
||||
if ctx.FormString("display") == "rendered" || !fInfo.st.IsRepresentableAsText() {
|
||||
return false
|
||||
}
|
||||
|
||||
if !fInfo.st.IsText() {
|
||||
if ctx.FormString("display") == "" {
|
||||
// not text but representable as text, e.g. SVG
|
||||
// since there is no "display" is specified, let other renders to handle
|
||||
return false
|
||||
}
|
||||
ctx.Data["HasSourceRenderedToggle"] = true
|
||||
}
|
||||
|
||||
buf, _ := io.ReadAll(utf8Reader)
|
||||
// The Open Group Base Specification: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
|
||||
// empty: 0 lines; "a": 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
|
||||
// Gitea uses the definition (like most modern editors):
|
||||
// empty: 0 lines; "a": 1 line; "a\n": 2 lines; "a\nb": 2 lines;
|
||||
// When rendering, the last empty line is not rendered in UI, while the line-number is still counted, to tell users that the file contains a trailing EOL.
|
||||
// To make the UI more consistent, it could use an icon mark to indicate that there is no trailing EOL, and show line-number as the rendered lines.
|
||||
// This NumLines is only used for the display on the UI: "xxx lines"
|
||||
if len(buf) == 0 {
|
||||
ctx.Data["NumLines"] = 0
|
||||
} else {
|
||||
ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
|
||||
}
|
||||
|
||||
language := attrs.GetLanguage().Value()
|
||||
fileContent, lexerName, err := highlight.File(filename, language, buf)
|
||||
ctx.Data["LexerName"] = lexerName
|
||||
if err != nil {
|
||||
log.Error("highlight.File failed, fallback to plain text: %v", err)
|
||||
fileContent = highlight.PlainText(buf)
|
||||
}
|
||||
status := &charset.EscapeStatus{}
|
||||
statuses := make([]*charset.EscapeStatus, len(fileContent))
|
||||
for i, line := range fileContent {
|
||||
statuses[i], fileContent[i] = charset.EscapeControlHTML(line, ctx.Locale)
|
||||
status = status.Or(statuses[i])
|
||||
}
|
||||
ctx.Data["EscapeStatus"] = status
|
||||
ctx.Data["FileContent"] = fileContent
|
||||
ctx.Data["LineEscapeStatus"] = statuses
|
||||
return true
|
||||
}
|
||||
|
||||
func handleFileViewRenderImage(ctx *context.Context, fInfo *fileInfo, prefetchBuf []byte) bool {
|
||||
if !fInfo.st.IsImage() {
|
||||
return false
|
||||
}
|
||||
if fInfo.st.IsSvgImage() && !setting.UI.SVG.Enabled {
|
||||
return false
|
||||
}
|
||||
if fInfo.st.IsSvgImage() {
|
||||
ctx.Data["HasSourceRenderedToggle"] = true
|
||||
} else {
|
||||
img, _, err := image.DecodeConfig(bytes.NewReader(prefetchBuf))
|
||||
if err == nil { // ignore the error for the formats that are not supported by image.DecodeConfig
|
||||
ctx.Data["ImageSize"] = fmt.Sprintf("%dx%dpx", img.Width, img.Height)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func prepareFileView(ctx *context.Context, entry *git.TreeEntry) {
|
||||
ctx.Data["IsViewFile"] = true
|
||||
ctx.Data["HideRepoInfo"] = true
|
||||
|
||||
@@ -86,11 +208,8 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
|
||||
}
|
||||
}
|
||||
|
||||
isDisplayingSource := ctx.FormString("display") == "source"
|
||||
isDisplayingRendered := !isDisplayingSource
|
||||
|
||||
// Don't call any other repository functions depends on git.Repository until the dataRc closed to
|
||||
// avoid create unnecessary temporary cat file.
|
||||
// avoid creating an unnecessary temporary cat file.
|
||||
buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob)
|
||||
if err != nil {
|
||||
ctx.ServerError("getFileReader", err)
|
||||
@@ -98,207 +217,62 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
|
||||
}
|
||||
defer dataRc.Close()
|
||||
|
||||
if fInfo.isLFSFile {
|
||||
if fInfo.isLFSFile() {
|
||||
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
|
||||
}
|
||||
|
||||
isRepresentableAsText := fInfo.st.IsRepresentableAsText()
|
||||
if !isRepresentableAsText {
|
||||
// If we can't show plain text, always try to render.
|
||||
isDisplayingSource = false
|
||||
isDisplayingRendered = true
|
||||
if !prepareFileViewEditorButtons(ctx) {
|
||||
return
|
||||
}
|
||||
ctx.Data["IsLFSFile"] = fInfo.isLFSFile
|
||||
|
||||
ctx.Data["IsLFSFile"] = fInfo.isLFSFile()
|
||||
ctx.Data["FileSize"] = fInfo.fileSize
|
||||
ctx.Data["IsTextFile"] = fInfo.isTextFile
|
||||
ctx.Data["IsRepresentableAsText"] = isRepresentableAsText
|
||||
ctx.Data["IsDisplayingSource"] = isDisplayingSource
|
||||
ctx.Data["IsDisplayingRendered"] = isDisplayingRendered
|
||||
ctx.Data["IsRepresentableAsText"] = fInfo.st.IsRepresentableAsText()
|
||||
ctx.Data["IsExecutable"] = entry.IsExecutable()
|
||||
ctx.Data["CanCopyContent"] = fInfo.st.IsRepresentableAsText() || fInfo.st.IsImage()
|
||||
|
||||
isTextSource := fInfo.isTextFile || isDisplayingSource
|
||||
ctx.Data["IsTextSource"] = isTextSource
|
||||
if isTextSource {
|
||||
ctx.Data["CanCopyContent"] = true
|
||||
}
|
||||
|
||||
// Check LFS Lock
|
||||
lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
|
||||
ctx.Data["LFSLock"] = lfsLock
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTreePathLock", err)
|
||||
attrs, ok := prepareFileViewLfsAttrs(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if lfsLock != nil {
|
||||
u, err := user_model.GetUserByID(ctx, lfsLock.OwnerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTreePathLock", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["LFSLockOwner"] = u.Name
|
||||
ctx.Data["LFSLockOwnerHomeLink"] = u.HomeLink()
|
||||
ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
|
||||
}
|
||||
|
||||
// read all needed attributes which will be used later
|
||||
// there should be no performance different between reading 2 or 4 here
|
||||
attrsMap, err := attribute.CheckAttributes(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, attribute.CheckAttributeOpts{
|
||||
Filenames: []string{ctx.Repo.TreePath},
|
||||
Attributes: []string{attribute.LinguistGenerated, attribute.LinguistVendored, attribute.LinguistLanguage, attribute.GitlabLanguage},
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("attribute.CheckAttributes", err)
|
||||
return
|
||||
}
|
||||
attrs := attrsMap[ctx.Repo.TreePath]
|
||||
if attrs == nil {
|
||||
// this case shouldn't happen, just in case.
|
||||
setting.PanicInDevOrTesting("no attributes found for %s", ctx.Repo.TreePath)
|
||||
attrs = attribute.NewAttributes()
|
||||
}
|
||||
// TODO: in the future maybe we need more accurate flags, for example:
|
||||
// * IsRepresentableAsText: some files are text, some are not
|
||||
// * IsRenderableXxx: some files are rendered by backend "markup" engine, some are rendered by frontend (pdf, 3d)
|
||||
// * DefaultViewMode: when there is no "display" query parameter, which view mode should be used by default, source or rendered
|
||||
|
||||
utf8Reader := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||
switch {
|
||||
case isRepresentableAsText:
|
||||
if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
break
|
||||
}
|
||||
|
||||
if fInfo.st.IsSvgImage() {
|
||||
ctx.Data["IsImageFile"] = true
|
||||
ctx.Data["CanCopyContent"] = true
|
||||
ctx.Data["HasSourceRenderedToggle"] = true
|
||||
}
|
||||
|
||||
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||
|
||||
shouldRenderSource := ctx.FormString("display") == "source"
|
||||
readmeExist := util.IsReadmeFileName(blob.Name())
|
||||
ctx.Data["ReadmeExist"] = readmeExist
|
||||
|
||||
markupType := markup.DetectMarkupTypeByFileName(blob.Name())
|
||||
if markupType == "" {
|
||||
markupType = markup.DetectRendererType(blob.Name(), bytes.NewReader(buf))
|
||||
}
|
||||
if markupType != "" {
|
||||
ctx.Data["HasSourceRenderedToggle"] = true
|
||||
}
|
||||
if markupType != "" && !shouldRenderSource {
|
||||
ctx.Data["IsMarkup"] = true
|
||||
ctx.Data["MarkupType"] = markupType
|
||||
metas := ctx.Repo.Repository.ComposeRepoFileMetas(ctx)
|
||||
metas["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL()
|
||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||
}).
|
||||
WithMarkupType(markupType).
|
||||
WithRelativePath(ctx.Repo.TreePath).
|
||||
WithMetas(metas)
|
||||
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||
if err != nil {
|
||||
ctx.ServerError("Render", err)
|
||||
return
|
||||
}
|
||||
// to prevent iframe load third-party url
|
||||
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
|
||||
} else {
|
||||
buf, _ := io.ReadAll(rd)
|
||||
|
||||
// The Open Group Base Specification: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
|
||||
// empty: 0 lines; "a": 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
|
||||
// Gitea uses the definition (like most modern editors):
|
||||
// empty: 0 lines; "a": 1 line; "a\n": 2 lines; "a\nb": 2 lines;
|
||||
// When rendering, the last empty line is not rendered in UI, while the line-number is still counted, to tell users that the file contains a trailing EOL.
|
||||
// To make the UI more consistent, it could use an icon mark to indicate that there is no trailing EOL, and show line-number as the rendered lines.
|
||||
// This NumLines is only used for the display on the UI: "xxx lines"
|
||||
if len(buf) == 0 {
|
||||
ctx.Data["NumLines"] = 0
|
||||
} else {
|
||||
ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
|
||||
}
|
||||
|
||||
language := attrs.GetLanguage().Value()
|
||||
fileContent, lexerName, err := highlight.File(blob.Name(), language, buf)
|
||||
ctx.Data["LexerName"] = lexerName
|
||||
if err != nil {
|
||||
log.Error("highlight.File failed, fallback to plain text: %v", err)
|
||||
fileContent = highlight.PlainText(buf)
|
||||
}
|
||||
status := &charset.EscapeStatus{}
|
||||
statuses := make([]*charset.EscapeStatus, len(fileContent))
|
||||
for i, line := range fileContent {
|
||||
statuses[i], fileContent[i] = charset.EscapeControlHTML(line, ctx.Locale)
|
||||
status = status.Or(statuses[i])
|
||||
}
|
||||
ctx.Data["EscapeStatus"] = status
|
||||
ctx.Data["FileContent"] = fileContent
|
||||
ctx.Data["LineEscapeStatus"] = statuses
|
||||
}
|
||||
|
||||
case fInfo.st.IsPDF():
|
||||
ctx.Data["IsPDFFile"] = true
|
||||
case fInfo.fileSize >= setting.UI.MaxDisplayFileSize:
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, utf8Reader):
|
||||
// it also sets ctx.Data["FileContent"] and more
|
||||
ctx.Data["IsMarkup"] = true
|
||||
case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, utf8Reader):
|
||||
// it also sets ctx.Data["FileContent"] and more
|
||||
ctx.Data["IsDisplayingSource"] = true
|
||||
case handleFileViewRenderImage(ctx, fInfo, buf):
|
||||
ctx.Data["IsImageFile"] = true
|
||||
case fInfo.st.IsVideo():
|
||||
ctx.Data["IsVideoFile"] = true
|
||||
case fInfo.st.IsAudio():
|
||||
ctx.Data["IsAudioFile"] = true
|
||||
case fInfo.st.IsImage() && (setting.UI.SVG.Enabled || !fInfo.st.IsSvgImage()):
|
||||
ctx.Data["IsImageFile"] = true
|
||||
ctx.Data["CanCopyContent"] = true
|
||||
default:
|
||||
if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
break
|
||||
}
|
||||
|
||||
// TODO: this logic duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go"
|
||||
// It is used by "external renders", markupRender will execute external programs to get rendered content.
|
||||
if markupType := markup.DetectMarkupTypeByFileName(blob.Name()); markupType != "" {
|
||||
rd := io.MultiReader(bytes.NewReader(buf), dataRc)
|
||||
ctx.Data["IsMarkup"] = true
|
||||
ctx.Data["MarkupType"] = markupType
|
||||
|
||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||
}).
|
||||
WithMarkupType(markupType).
|
||||
WithRelativePath(ctx.Repo.TreePath)
|
||||
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
||||
if err != nil {
|
||||
ctx.ServerError("Render", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// unable to render anything, show the "view raw" or let frontend handle it
|
||||
}
|
||||
|
||||
ctx.Data["IsVendored"], ctx.Data["IsGenerated"] = attrs.GetVendored().Value(), attrs.GetGenerated().Value()
|
||||
|
||||
if fInfo.st.IsImage() && !fInfo.st.IsSvgImage() {
|
||||
img, _, err := image.DecodeConfig(bytes.NewReader(buf))
|
||||
if err == nil {
|
||||
// There are Image formats go can't decode
|
||||
// Instead of throwing an error in that case, we show the size only when we can decode
|
||||
ctx.Data["ImageSize"] = fmt.Sprintf("%dx%dpx", img.Width, img.Height)
|
||||
}
|
||||
}
|
||||
|
||||
prepareToRenderButtons(ctx, lfsLock)
|
||||
}
|
||||
|
||||
func prepareToRenderButtons(ctx *context.Context, lfsLock *git_model.LFSLock) {
|
||||
func prepareFileViewEditorButtons(ctx *context.Context) bool {
|
||||
// archived or mirror repository, the buttons should not be shown
|
||||
if !ctx.Repo.Repository.CanEnableEditor() {
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
// The buttons should not be shown if it's not a branch
|
||||
if !ctx.Repo.RefFullName.IsBranch() {
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
|
||||
@@ -306,7 +280,24 @@ func prepareToRenderButtons(ctx *context.Context, lfsLock *git_model.LFSLock) {
|
||||
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
|
||||
ctx.Data["CanDeleteFile"] = true
|
||||
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
|
||||
ctx.Data["LFSLock"] = lfsLock
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTreePathLock", err)
|
||||
return false
|
||||
}
|
||||
if lfsLock != nil {
|
||||
u, err := user_model.GetUserByID(ctx, lfsLock.OwnerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTreePathLock", err)
|
||||
return false
|
||||
}
|
||||
ctx.Data["LFSLockOwner"] = u.Name
|
||||
ctx.Data["LFSLockOwnerHomeLink"] = u.HomeLink()
|
||||
ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
|
||||
}
|
||||
|
||||
// it's a lfs file and the user is not the owner of the lock
|
||||
@@ -315,4 +306,5 @@ func prepareToRenderButtons(ctx *context.Context, lfsLock *git_model.LFSLock) {
|
||||
ctx.Data["EditFileTooltip"] = util.Iif(isLFSLocked, ctx.Tr("repo.editor.this_file_locked"), ctx.Tr("repo.editor.edit_this_file"))
|
||||
ctx.Data["CanDeleteFile"] = !isLFSLocked
|
||||
ctx.Data["DeleteFileTooltip"] = util.Iif(isLFSLocked, ctx.Tr("repo.editor.this_file_locked"), ctx.Tr("repo.editor.delete_this_file"))
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) {
|
||||
if entry.IsDir() {
|
||||
prepareToRenderDirectory(ctx)
|
||||
} else {
|
||||
prepareToRenderFile(ctx, entry)
|
||||
prepareFileView(ctx, entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,24 +161,23 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil
|
||||
}
|
||||
defer dataRc.Close()
|
||||
|
||||
ctx.Data["FileIsText"] = fInfo.isTextFile
|
||||
ctx.Data["FileIsText"] = fInfo.st.IsText()
|
||||
ctx.Data["FileTreePath"] = path.Join(ctx.Repo.TreePath, subfolder, readmeFile.Name())
|
||||
ctx.Data["FileSize"] = fInfo.fileSize
|
||||
ctx.Data["IsLFSFile"] = fInfo.isLFSFile
|
||||
ctx.Data["IsLFSFile"] = fInfo.isLFSFile()
|
||||
|
||||
if fInfo.isLFSFile {
|
||||
if fInfo.isLFSFile() {
|
||||
filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.Name()))
|
||||
ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.Link(), url.PathEscape(fInfo.lfsMeta.Oid), url.PathEscape(filenameBase64))
|
||||
}
|
||||
|
||||
if !fInfo.isTextFile {
|
||||
if !fInfo.st.IsText() {
|
||||
return
|
||||
}
|
||||
|
||||
if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
|
||||
// Pretend that this is a normal text file to display 'This file is too large to be shown'
|
||||
ctx.Data["IsFileTooLarge"] = true
|
||||
ctx.Data["IsTextFile"] = true
|
||||
return
|
||||
}
|
||||
|
||||
@@ -212,7 +211,7 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil
|
||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
|
||||
}
|
||||
|
||||
if !fInfo.isLFSFile && ctx.Repo.Repository.CanEnableEditor() {
|
||||
if !fInfo.isLFSFile() && ctx.Repo.Repository.CanEnableEditor() {
|
||||
ctx.Data["CanEditReadmeFile"] = true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user