mirror of
https://github.com/go-gitea/gitea.git
synced 2025-04-08 17:05:45 +02:00
Merge branch 'main' into fix-tests
This commit is contained in:
commit
84932b03b1
@ -1294,6 +1294,9 @@ LEVEL = Info
|
||||
;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css"
|
||||
;THEMES =
|
||||
;;
|
||||
;; The icons for file list (basic/material), this is a temporary option which will be replaced by a user setting in the future.
|
||||
;FILE_ICON_THEME = material
|
||||
;;
|
||||
;; All available reactions users can choose on issues/prs and comments.
|
||||
;; Values can be emoji alias (:smile:) or a unicode emoji.
|
||||
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
|
||||
|
@ -17,7 +17,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@ -139,24 +138,3 @@ func Int64sToStrings(ints []int64) []string {
|
||||
}
|
||||
return strs
|
||||
}
|
||||
|
||||
// EntryIcon returns the octicon name for displaying files/directories
|
||||
func EntryIcon(entry *git.TreeEntry) string {
|
||||
switch {
|
||||
case entry.IsLink():
|
||||
te, err := entry.FollowLink()
|
||||
if err != nil {
|
||||
return "file-symlink-file"
|
||||
}
|
||||
if te.IsDir() {
|
||||
return "file-directory-symlink"
|
||||
}
|
||||
return "file-symlink-file"
|
||||
case entry.IsDir():
|
||||
return "file-directory-fill"
|
||||
case entry.IsSubModule():
|
||||
return "file-submodule"
|
||||
}
|
||||
|
||||
return "file"
|
||||
}
|
||||
|
27
modules/fileicon/basic.go
Normal file
27
modules/fileicon/basic.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package fileicon
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
)
|
||||
|
||||
func BasicThemeIcon(entry *git.TreeEntry) template.HTML {
|
||||
svgName := "octicon-file"
|
||||
switch {
|
||||
case entry.IsLink():
|
||||
svgName = "octicon-file-symlink-file"
|
||||
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
|
||||
svgName = "octicon-file-directory-symlink"
|
||||
}
|
||||
case entry.IsDir():
|
||||
svgName = "octicon-file-directory-fill"
|
||||
case entry.IsSubModule():
|
||||
svgName = "octicon-file-submodule"
|
||||
}
|
||||
return svg.RenderHTML(svgName)
|
||||
}
|
141
modules/fileicon/material.go
Normal file
141
modules/fileicon/material.go
Normal file
@ -0,0 +1,141 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package fileicon
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/options"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
)
|
||||
|
||||
type materialIconRulesData struct {
|
||||
FileNames map[string]string `json:"fileNames"`
|
||||
FolderNames map[string]string `json:"folderNames"`
|
||||
FileExtensions map[string]string `json:"fileExtensions"`
|
||||
}
|
||||
|
||||
type MaterialIconProvider struct {
|
||||
once sync.Once
|
||||
rules *materialIconRulesData
|
||||
svgs map[string]string
|
||||
}
|
||||
|
||||
var materialIconProvider MaterialIconProvider
|
||||
|
||||
func DefaultMaterialIconProvider() *MaterialIconProvider {
|
||||
materialIconProvider.once.Do(materialIconProvider.loadData)
|
||||
return &materialIconProvider
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) loadData() {
|
||||
buf, err := options.AssetFS().ReadFile("fileicon/material-icon-rules.json")
|
||||
if err != nil {
|
||||
log.Error("Failed to read material icon rules: %v", err)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(buf, &m.rules)
|
||||
if err != nil {
|
||||
log.Error("Failed to unmarshal material icon rules: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
buf, err = options.AssetFS().ReadFile("fileicon/material-icon-svgs.json")
|
||||
if err != nil {
|
||||
log.Error("Failed to read material icon rules: %v", err)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(buf, &m.svgs)
|
||||
if err != nil {
|
||||
log.Error("Failed to unmarshal material icon rules: %v", err)
|
||||
return
|
||||
}
|
||||
log.Debug("Loaded material icon rules and SVG images")
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name, svg string) template.HTML {
|
||||
data := ctx.GetData()
|
||||
renderedSVGs, _ := data["_RenderedSVGs"].(map[string]bool)
|
||||
if renderedSVGs == nil {
|
||||
renderedSVGs = make(map[string]bool)
|
||||
data["_RenderedSVGs"] = renderedSVGs
|
||||
}
|
||||
// This part is a bit hacky, but it works really well. It should be safe to do so because all SVG icons are generated by us.
|
||||
// Will try to refactor this in the future.
|
||||
if !strings.HasPrefix(svg, "<svg") {
|
||||
panic("Invalid SVG icon")
|
||||
}
|
||||
svgID := "svg-mfi-" + name
|
||||
svgCommonAttrs := `class="svg fileicon" width="16" height="16" aria-hidden="true"`
|
||||
posOuterBefore := strings.IndexByte(svg, '>')
|
||||
if renderedSVGs[svgID] && posOuterBefore != -1 {
|
||||
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
|
||||
}
|
||||
svg = `<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:]
|
||||
renderedSVGs[svgID] = true
|
||||
return template.HTML(svg)
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.TreeEntry) template.HTML {
|
||||
if m.rules == nil {
|
||||
return BasicThemeIcon(entry)
|
||||
}
|
||||
|
||||
if entry.IsLink() {
|
||||
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
|
||||
return svg.RenderHTML("material-folder-symlink")
|
||||
}
|
||||
return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
|
||||
}
|
||||
|
||||
name := m.findIconNameByGit(entry)
|
||||
if name == "folder" {
|
||||
// the material icon pack's "folder" icon doesn't look good, so use our built-in one
|
||||
return svg.RenderHTML("material-folder-generic")
|
||||
}
|
||||
if iconSVG, ok := m.svgs[name]; ok && iconSVG != "" {
|
||||
return m.renderFileIconSVG(ctx, name, iconSVG)
|
||||
}
|
||||
return svg.RenderHTML("octicon-file")
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
|
||||
iconsData := m.rules
|
||||
fileNameLower := strings.ToLower(path.Base(name))
|
||||
if isDir {
|
||||
if s, ok := iconsData.FolderNames[fileNameLower]; ok {
|
||||
return s
|
||||
}
|
||||
return "folder"
|
||||
}
|
||||
|
||||
if s, ok := iconsData.FileNames[fileNameLower]; ok {
|
||||
return s
|
||||
}
|
||||
|
||||
for i := len(fileNameLower) - 1; i >= 0; i-- {
|
||||
if fileNameLower[i] == '.' {
|
||||
ext := fileNameLower[i+1:]
|
||||
if s, ok := iconsData.FileExtensions[ext]; ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "file"
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) findIconNameByGit(entry *git.TreeEntry) string {
|
||||
if entry.IsSubModule() {
|
||||
return "folder-git"
|
||||
}
|
||||
return m.FindIconName(entry.Name(), entry.IsDir())
|
||||
}
|
24
modules/fileicon/material_test.go
Normal file
24
modules/fileicon/material_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package fileicon_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/fileicon"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{FixtureFiles: []string{}})
|
||||
}
|
||||
|
||||
func TestFindIconName(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
p := fileicon.DefaultMaterialIconProvider()
|
||||
assert.Equal(t, "php", p.FindIconName("foo.php", false))
|
||||
assert.Equal(t, "php", p.FindIconName("foo.PHP", false))
|
||||
}
|
@ -65,7 +65,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
||||
log.Debug("missing commit for %s", entry.Name())
|
||||
}
|
||||
|
||||
// If the entry if a submodule add a submodule file for this
|
||||
// If the entry is a submodule add a submodule file for this
|
||||
if entry.IsSubModule() {
|
||||
subModuleURL := ""
|
||||
var fullPath string
|
||||
@ -85,8 +85,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
||||
}
|
||||
|
||||
// Retrieve the commit for the treePath itself (see above). We basically
|
||||
// get it for free during the tree traversal and it's used for listing
|
||||
// pages to display information about newest commit for a given path.
|
||||
// get it for free during the tree traversal, and it's used for listing
|
||||
// pages to display information about the newest commit for a given path.
|
||||
var treeCommit *Commit
|
||||
var ok bool
|
||||
if treePath == "" {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@ -101,6 +102,9 @@ func (r *Request) Param(key, value string) *Request {
|
||||
|
||||
// Body adds request raw body. It supports string, []byte and io.Reader as body.
|
||||
func (r *Request) Body(data any) *Request {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
switch t := data.(type) {
|
||||
case nil: // do nothing
|
||||
case string:
|
||||
@ -193,6 +197,9 @@ func (r *Request) getResponse() (*http.Response, error) {
|
||||
// Response executes request client gets response manually.
|
||||
// Caller MUST close the response body if no error occurs
|
||||
func (r *Request) Response() (*http.Response, error) {
|
||||
if r == nil {
|
||||
return nil, errors.New("invalid request")
|
||||
}
|
||||
return r.getResponse()
|
||||
}
|
||||
|
||||
|
@ -70,14 +70,13 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans
|
||||
g.logger.Log("json marshal error", err)
|
||||
return nil, err
|
||||
}
|
||||
url := g.server.JoinPath("objects/batch").String()
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeGitLFS,
|
||||
headerContentType: mimeGitLFS,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||
req := newInternalRequestLFS(g.ctx, g.server.JoinPath("objects/batch").String(), http.MethodPost, headers, bodyBytes)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
g.logger.Log("http request error", err)
|
||||
@ -179,13 +178,12 @@ func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser,
|
||||
g.logger.Log("argument id incorrect")
|
||||
return nil, 0, transfer.ErrCorruptData
|
||||
}
|
||||
url := action.Href
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeOctetStream,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
|
||||
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodGet, headers, nil)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to get response: %w", err)
|
||||
@ -225,7 +223,6 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
|
||||
g.logger.Log("argument id incorrect")
|
||||
return transfer.ErrCorruptData
|
||||
}
|
||||
url := action.Href
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
@ -233,7 +230,7 @@ func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer
|
||||
headerContentLength: strconv.FormatInt(size, 10),
|
||||
}
|
||||
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodPut, headers, nil)
|
||||
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPut, headers, nil)
|
||||
req.Body(r)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
@ -274,14 +271,13 @@ func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (trans
|
||||
// the server sent no verify action
|
||||
return transfer.SuccessStatus(), nil
|
||||
}
|
||||
url := action.Href
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeGitLFS,
|
||||
headerContentType: mimeGitLFS,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||
req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPost, headers, bodyBytes)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
return transfer.NewStatus(transfer.StatusInternalServerError), err
|
||||
|
@ -43,14 +43,13 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) {
|
||||
g.logger.Log("json marshal error", err)
|
||||
return nil, err
|
||||
}
|
||||
url := g.server.String()
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeGitLFS,
|
||||
headerContentType: mimeGitLFS,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||
req := newInternalRequestLFS(g.ctx, g.server.String(), http.MethodPost, headers, bodyBytes)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
g.logger.Log("http request error", err)
|
||||
@ -95,14 +94,13 @@ func (g *giteaLockBackend) Unlock(lock transfer.Lock) error {
|
||||
g.logger.Log("json marshal error", err)
|
||||
return err
|
||||
}
|
||||
url := g.server.JoinPath(lock.ID(), "unlock").String()
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeGitLFS,
|
||||
headerContentType: mimeGitLFS,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodPost, headers, bodyBytes)
|
||||
req := newInternalRequestLFS(g.ctx, g.server.JoinPath(lock.ID(), "unlock").String(), http.MethodPost, headers, bodyBytes)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
g.logger.Log("http request error", err)
|
||||
@ -176,16 +174,15 @@ func (g *giteaLockBackend) Range(cursor string, limit int, iter func(transfer.Lo
|
||||
}
|
||||
|
||||
func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, error) {
|
||||
urlq := g.server.JoinPath() // get a copy
|
||||
urlq.RawQuery = v.Encode()
|
||||
url := urlq.String()
|
||||
serverURLWithQuery := g.server.JoinPath() // get a copy
|
||||
serverURLWithQuery.RawQuery = v.Encode()
|
||||
headers := map[string]string{
|
||||
headerAuthorization: g.authToken,
|
||||
headerGiteaInternalAuth: g.internalAuth,
|
||||
headerAccept: mimeGitLFS,
|
||||
headerContentType: mimeGitLFS,
|
||||
}
|
||||
req := newInternalRequestLFS(g.ctx, url, http.MethodGet, headers, nil)
|
||||
req := newInternalRequestLFS(g.ctx, serverURLWithQuery.String(), http.MethodGet, headers, nil)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
g.logger.Log("http request error", err)
|
||||
|
@ -8,9 +8,13 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/charmbracelet/git-lfs-transfer/transfer"
|
||||
)
|
||||
@ -57,8 +61,7 @@ const (
|
||||
|
||||
// Operations enum
|
||||
const (
|
||||
opNone = iota
|
||||
opDownload
|
||||
opDownload = iota + 1
|
||||
opUpload
|
||||
)
|
||||
|
||||
@ -86,8 +89,49 @@ func statusCodeToErr(code int) error {
|
||||
}
|
||||
}
|
||||
|
||||
func newInternalRequestLFS(ctx context.Context, url, method string, headers map[string]string, body any) *httplib.Request {
|
||||
req := private.NewInternalRequest(ctx, url, method)
|
||||
func toInternalLFSURL(s string) string {
|
||||
pos1 := strings.Index(s, "://")
|
||||
if pos1 == -1 {
|
||||
return ""
|
||||
}
|
||||
appSubURLWithSlash := setting.AppSubURL + "/"
|
||||
pos2 := strings.Index(s[pos1+3:], appSubURLWithSlash)
|
||||
if pos2 == -1 {
|
||||
return ""
|
||||
}
|
||||
routePath := s[pos1+3+pos2+len(appSubURLWithSlash):]
|
||||
fields := strings.SplitN(routePath, "/", 3)
|
||||
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
|
||||
return ""
|
||||
}
|
||||
return setting.LocalURL + "api/internal/repo/" + routePath
|
||||
}
|
||||
|
||||
func isInternalLFSURL(s string) bool {
|
||||
if !strings.HasPrefix(s, setting.LocalURL) {
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
routePath := util.PathJoinRelX(u.Path)
|
||||
subRoutePath, cut := strings.CutPrefix(routePath, "api/internal/repo/")
|
||||
if !cut {
|
||||
return false
|
||||
}
|
||||
fields := strings.SplitN(subRoutePath, "/", 3)
|
||||
if len(fields) < 3 || !strings.HasPrefix(fields[2], "info/lfs") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func newInternalRequestLFS(ctx context.Context, internalURL, method string, headers map[string]string, body any) *httplib.Request {
|
||||
if !isInternalLFSURL(internalURL) {
|
||||
return nil
|
||||
}
|
||||
req := private.NewInternalRequest(ctx, internalURL, method)
|
||||
for k, v := range headers {
|
||||
req.Header(k, v)
|
||||
}
|
||||
|
53
modules/lfstransfer/backend/util_test.go
Normal file
53
modules/lfstransfer/backend/util_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package backend
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToInternalLFSURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.LocalURL, "http://localurl/")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||
cases := []struct {
|
||||
url string
|
||||
expected string
|
||||
}{
|
||||
{"http://appurl/any", ""},
|
||||
{"http://appurl/sub/any", ""},
|
||||
{"http://appurl/sub/owner/repo/any", ""},
|
||||
{"http://appurl/sub/owner/repo/info/any", ""},
|
||||
{"http://appurl/sub/owner/repo/info/lfs/any", "http://localurl/api/internal/repo/owner/repo/info/lfs/any"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
assert.Equal(t, c.expected, toInternalLFSURL(c.url), c.url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInternalLFSURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.LocalURL, "http://localurl/")()
|
||||
defer test.MockVariableValue(&setting.InternalToken, "mock-token")()
|
||||
cases := []struct {
|
||||
url string
|
||||
expected bool
|
||||
}{
|
||||
{"", false},
|
||||
{"http://otherurl/api/internal/repo/owner/repo/info/lfs/any", false},
|
||||
{"http://localurl/api/internal/repo/owner/repo/info/lfs/any", true},
|
||||
{"http://localurl/api/internal/repo/owner/repo/info", false},
|
||||
{"http://localurl/api/internal/misc/owner/repo/info/lfs/any", false},
|
||||
{"http://localurl/api/internal/owner/repo/info/lfs/any", false},
|
||||
{"http://localurl/api/internal/foo/bar", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
req := newInternalRequestLFS(t.Context(), c.url, "GET", nil, nil)
|
||||
assert.Equal(t, c.expected, req != nil, c.url)
|
||||
assert.Equal(t, c.expected, isInternalLFSURL(c.url), c.url)
|
||||
}
|
||||
}
|
@ -40,6 +40,10 @@ func NewInternalRequest(ctx context.Context, url, method string) *httplib.Reques
|
||||
Ensure you are running in the correct environment or set the correct configuration file with -c.`, setting.CustomConf)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(url, setting.LocalURL) {
|
||||
log.Fatal("Invalid internal request URL: %q", url)
|
||||
}
|
||||
|
||||
req := httplib.NewRequest(url, method).
|
||||
SetContext(ctx).
|
||||
Header("X-Real-IP", getClientIP()).
|
||||
|
@ -94,6 +94,9 @@ type RequestContext interface {
|
||||
}
|
||||
|
||||
func FromContext(ctx context.Context) RequestContext {
|
||||
if rc, ok := ctx.(RequestContext); ok {
|
||||
return rc
|
||||
}
|
||||
// here we must use the current ctx and the underlying store
|
||||
// the current ctx guarantees that the ctx deadline/cancellation/values are respected
|
||||
// the underlying store guarantees that the request-specific data is available
|
||||
@ -134,6 +137,6 @@ func NewRequestContext(parentCtx context.Context, profDesc string) (_ context.Co
|
||||
|
||||
// NewRequestContextForTest creates a new RequestContext for testing purposes
|
||||
// It doesn't add the context to the process manager, nor do cleanup
|
||||
func NewRequestContextForTest(parentCtx context.Context) context.Context {
|
||||
func NewRequestContextForTest(parentCtx context.Context) RequestContext {
|
||||
return &requestContext{Context: parentCtx, RequestDataStore: &requestDataStore{values: make(map[any]any)}}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ var UI = struct {
|
||||
DefaultShowFullName bool
|
||||
DefaultTheme string
|
||||
Themes []string
|
||||
FileIconTheme string
|
||||
Reactions []string
|
||||
ReactionsLookup container.Set[string] `ini:"-"`
|
||||
CustomEmojis []string
|
||||
@ -84,6 +85,7 @@ var UI = struct {
|
||||
ReactionMaxUserNum: 10,
|
||||
MaxDisplayFileSize: 8388608,
|
||||
DefaultTheme: `gitea-auto`,
|
||||
FileIconTheme: `material`,
|
||||
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
|
||||
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`},
|
||||
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"},
|
||||
|
@ -59,7 +59,6 @@ func NewFuncMap() template.FuncMap {
|
||||
// -----------------------------------------------------------------
|
||||
// svg / avatar / icon / color
|
||||
"svg": svg.RenderHTML,
|
||||
"EntryIcon": base.EntryIcon,
|
||||
"MigrationIcon": migrationIcon,
|
||||
"ActionIcon": actionIcon,
|
||||
"SortArrow": sortArrow,
|
||||
|
@ -4,7 +4,6 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"html/template"
|
||||
@ -16,20 +15,23 @@ import (
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/modules/emoji"
|
||||
"code.gitea.io/gitea/modules/fileicon"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/htmlutil"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
type RenderUtils struct {
|
||||
ctx context.Context
|
||||
ctx reqctx.RequestContext
|
||||
}
|
||||
|
||||
func NewRenderUtils(ctx context.Context) *RenderUtils {
|
||||
func NewRenderUtils(ctx reqctx.RequestContext) *RenderUtils {
|
||||
return &RenderUtils{ctx: ctx}
|
||||
}
|
||||
|
||||
@ -179,6 +181,13 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
|
||||
textColor, itemColor, itemHTML)
|
||||
}
|
||||
|
||||
func (ut *RenderUtils) RenderFileIcon(entry *git.TreeEntry) template.HTML {
|
||||
if setting.UI.FileIconTheme == "material" {
|
||||
return fileicon.DefaultMaterialIconProvider().FileIcon(ut.ctx, entry)
|
||||
}
|
||||
return fileicon.BasicThemeIcon(entry)
|
||||
}
|
||||
|
||||
// RenderEmoji renders html text with emoji post processors
|
||||
func (ut *RenderUtils) RenderEmoji(text string) template.HTML {
|
||||
renderedText, err := markup.PostProcessEmoji(markup.NewRenderContext(ut.ctx), template.HTMLEscapeString(text))
|
||||
|
@ -8,45 +8,46 @@ import (
|
||||
"html/template"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
)
|
||||
|
||||
func renderEmojiLegacy(ctx context.Context, text string) template.HTML {
|
||||
panicIfDevOrTesting()
|
||||
return NewRenderUtils(ctx).RenderEmoji(text)
|
||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderEmoji(text)
|
||||
}
|
||||
|
||||
func renderLabelLegacy(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
|
||||
panicIfDevOrTesting()
|
||||
return NewRenderUtils(ctx).RenderLabel(label)
|
||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderLabel(label)
|
||||
}
|
||||
|
||||
func renderLabelsLegacy(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
|
||||
panicIfDevOrTesting()
|
||||
return NewRenderUtils(ctx).RenderLabels(labels, repoLink, issue)
|
||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderLabels(labels, repoLink, issue)
|
||||
}
|
||||
|
||||
func renderMarkdownToHtmlLegacy(ctx context.Context, input string) template.HTML { //nolint:revive
|
||||
panicIfDevOrTesting()
|
||||
return NewRenderUtils(ctx).MarkdownToHtml(input)
|
||||
return NewRenderUtils(reqctx.FromContext(ctx)).MarkdownToHtml(input)
|
||||
}
|
||||
|
||||
func renderCommitMessageLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
||||
panicIfDevOrTesting()
|
||||
return NewRenderUtils(ctx).RenderCommitMessage(msg, metas)
|
||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitMessage(msg, metas)
|
||||
}
|
||||
|
||||
func renderCommitMessageLinkSubjectLegacy(ctx context.Context, msg, urlDefault string, metas map[string]string) template.HTML {
|
||||
panicIfDevOrTesting()
|
||||
return NewRenderUtils(ctx).RenderCommitMessageLinkSubject(msg, urlDefault, metas)
|
||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitMessageLinkSubject(msg, urlDefault, metas)
|
||||
}
|
||||
|
||||
func renderIssueTitleLegacy(ctx context.Context, text string, metas map[string]string) template.HTML {
|
||||
panicIfDevOrTesting()
|
||||
return NewRenderUtils(ctx).RenderIssueTitle(text, metas)
|
||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderIssueTitle(text, metas)
|
||||
}
|
||||
|
||||
func renderCommitBodyLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
||||
panicIfDevOrTesting()
|
||||
return NewRenderUtils(ctx).RenderCommitBody(msg, metas)
|
||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitBody(msg, metas)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
|
||||
@ -67,9 +68,9 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func newTestRenderUtils() *RenderUtils {
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, translation.ContextKey, &translation.MockLocale{})
|
||||
func newTestRenderUtils(t *testing.T) *RenderUtils {
|
||||
ctx := reqctx.NewRequestContextForTest(t.Context())
|
||||
ctx.SetContextValue(translation.ContextKey, &translation.MockLocale{})
|
||||
return NewRenderUtils(ctx)
|
||||
}
|
||||
|
||||
@ -105,7 +106,7 @@ func TestRenderCommitBody(t *testing.T) {
|
||||
want: "second line",
|
||||
},
|
||||
}
|
||||
ut := newTestRenderUtils()
|
||||
ut := newTestRenderUtils(t)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, nil), "RenderCommitBody(%v, %v)", tt.args.msg, nil)
|
||||
@ -131,17 +132,17 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<a href="/mention-user">@mention-user</a> test
|
||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||
space`
|
||||
assert.EqualValues(t, expected, string(newTestRenderUtils().RenderCommitBody(testInput(), testMetas)))
|
||||
assert.EqualValues(t, expected, string(newTestRenderUtils(t).RenderCommitBody(testInput(), testMetas)))
|
||||
}
|
||||
|
||||
func TestRenderCommitMessage(t *testing.T) {
|
||||
expected := `space <a href="/mention-user" data-markdown-generated-content="">@mention-user</a> `
|
||||
assert.EqualValues(t, expected, newTestRenderUtils().RenderCommitMessage(testInput(), testMetas))
|
||||
assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessage(testInput(), testMetas))
|
||||
}
|
||||
|
||||
func TestRenderCommitMessageLinkSubject(t *testing.T) {
|
||||
expected := `<a href="https://example.com/link" class="muted">space </a><a href="/mention-user" data-markdown-generated-content="">@mention-user</a>`
|
||||
assert.EqualValues(t, expected, newTestRenderUtils().RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", testMetas))
|
||||
assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", testMetas))
|
||||
}
|
||||
|
||||
func TestRenderIssueTitle(t *testing.T) {
|
||||
@ -168,7 +169,7 @@ mail@domain.com
|
||||
space<SPACE><SPACE>
|
||||
`
|
||||
expected = strings.ReplaceAll(expected, "<SPACE>", " ")
|
||||
assert.EqualValues(t, expected, string(newTestRenderUtils().RenderIssueTitle(testInput(), testMetas)))
|
||||
assert.EqualValues(t, expected, string(newTestRenderUtils(t).RenderIssueTitle(testInput(), testMetas)))
|
||||
}
|
||||
|
||||
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||
@ -194,11 +195,11 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
#123
|
||||
space</p>
|
||||
`
|
||||
assert.Equal(t, expected, string(newTestRenderUtils().MarkdownToHtml(testInput())))
|
||||
assert.Equal(t, expected, string(newTestRenderUtils(t).MarkdownToHtml(testInput())))
|
||||
}
|
||||
|
||||
func TestRenderLabels(t *testing.T) {
|
||||
ut := newTestRenderUtils()
|
||||
ut := newTestRenderUtils(t)
|
||||
label := &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
|
||||
issue := &issues.Issue{}
|
||||
expected := `/owner/repo/issues?labels=123`
|
||||
@ -212,6 +213,6 @@ func TestRenderLabels(t *testing.T) {
|
||||
|
||||
func TestUserMention(t *testing.T) {
|
||||
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||
rendered := newTestRenderUtils().MarkdownToHtml("@no-such-user @mention-user @mention-user")
|
||||
rendered := newTestRenderUtils(t).MarkdownToHtml("@no-such-user @mention-user @mention-user")
|
||||
assert.EqualValues(t, `<p>@no-such-user <a href="/mention-user" rel="nofollow">@mention-user</a> <a href="/mention-user" rel="nofollow">@mention-user</a></p>`, strings.TrimSpace(string(rendered)))
|
||||
}
|
||||
|
9450
options/fileicon/material-icon-rules.json
Normal file
9450
options/fileicon/material-icon-rules.json
Normal file
File diff suppressed because it is too large
Load Diff
1074
options/fileicon/material-icon-svgs.json
Normal file
1074
options/fileicon/material-icon-svgs.json
Normal file
File diff suppressed because one or more lines are too long
110
package-lock.json
generated
110
package-lock.json
generated
@ -100,6 +100,7 @@
|
||||
"eslint-plugin-wc": "2.2.0",
|
||||
"happy-dom": "17.1.0",
|
||||
"markdownlint-cli": "0.44.0",
|
||||
"material-icon-theme": "5.20.0",
|
||||
"nolyfill": "1.0.43",
|
||||
"postcss-html": "1.8.0",
|
||||
"stylelint": "16.14.1",
|
||||
@ -4675,6 +4676,13 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/chroma-js": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-3.1.2.tgz",
|
||||
"integrity": "sha512-IJnETTalXbsLx1eKEgx19d5L6SRM7cH4vINw/99p/M11HCuXGRWL+6YmCm7FWFGIo6dtWuQoQi1dc5yQ7ESIHg==",
|
||||
"dev": true,
|
||||
"license": "(BSD-3-Clause AND Apache-2.0)"
|
||||
},
|
||||
"node_modules/chrome-trace-event": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
|
||||
@ -5702,6 +5710,33 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deep-rename-keys": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz",
|
||||
"integrity": "sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"kind-of": "^3.0.2",
|
||||
"rename-keys": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-rename-keys/node_modules/kind-of": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||
"integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-buffer": "^1.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delaunator": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
|
||||
@ -7004,6 +7039,13 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
|
||||
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
@ -7856,6 +7898,13 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-builtin-module": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
|
||||
@ -8840,6 +8889,25 @@
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/material-icon-theme": {
|
||||
"version": "5.20.0",
|
||||
"resolved": "https://registry.npmjs.org/material-icon-theme/-/material-icon-theme-5.20.0.tgz",
|
||||
"integrity": "sha512-EAz5I2O7Hq6G8Rv0JdO6NXL+jK/mvDppcVUVbsUMpSqSmFczNdaR5WJ3lOiRz4HNBlEN2i2sVSfuqI5iNQfGLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chroma-js": "^3.0.0",
|
||||
"events": "^3.3.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"svgson": "^5.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.55.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/material-extensions"
|
||||
}
|
||||
},
|
||||
"node_modules/mathml-tag-names": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
|
||||
@ -11020,6 +11088,16 @@
|
||||
"jsesc": "bin/jsesc"
|
||||
}
|
||||
},
|
||||
"node_modules/rename-keys": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/rename-keys/-/rename-keys-1.2.0.tgz",
|
||||
"integrity": "sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@ -12258,6 +12336,17 @@
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/svgson": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/svgson/-/svgson-5.3.1.tgz",
|
||||
"integrity": "sha512-qdPgvUNWb40gWktBJnbJRelWcPzkLed/ShhnRsjbayXz8OtdPOzbil9jtiZdrYvSDumAz/VNQr6JaNfPx/gvPA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deep-rename-keys": "^0.2.1",
|
||||
"xml-reader": "2.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-ui-dist": {
|
||||
"version": "5.18.3",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.3.tgz",
|
||||
@ -14107,6 +14196,16 @@
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-lexer": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
|
||||
"integrity": "sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
@ -14117,6 +14216,17 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-reader": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
|
||||
"integrity": "sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^2.0.0",
|
||||
"xml-lexer": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
@ -99,6 +99,7 @@
|
||||
"eslint-plugin-wc": "2.2.0",
|
||||
"happy-dom": "17.1.0",
|
||||
"markdownlint-cli": "0.44.0",
|
||||
"material-icon-theme": "5.20.0",
|
||||
"nolyfill": "1.0.43",
|
||||
"postcss-html": "1.8.0",
|
||||
"stylelint": "16.14.1",
|
||||
|
1
public/assets/img/svg/material-folder-generic.svg
generated
Normal file
1
public/assets/img/svg/material-folder-generic.svg
generated
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="svg material-folder-generic" width="16" height="16" aria-hidden="true"><path fill="#42a5f5" d="M10 4H4c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-8z"/></svg>
|
After Width: | Height: | Size: 250 B |
1
public/assets/img/svg/material-folder-symlink.svg
generated
Normal file
1
public/assets/img/svg/material-folder-symlink.svg
generated
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="svg material-folder-symlink" width="16" height="16" aria-hidden="true"><path fill="#42a5f5" d="M10 4H4c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-8z" opacity=".745"/><path fill="#c5e5fd" d="M16.972 10.757v2.641h-6.561v5.281h6.561v2.641l6.562-5.281z" opacity=".81"/></svg>
|
After Width: | Height: | Size: 364 B |
@ -900,7 +900,6 @@ func ExcerptBlob(ctx *context.Context) {
|
||||
}
|
||||
section := &gitdiff.DiffSection{
|
||||
FileName: filePath,
|
||||
Name: filePath,
|
||||
}
|
||||
if direction == "up" && (idxLeft-lastLeft) > chunkSize {
|
||||
idxLeft -= chunkSize
|
||||
|
@ -78,7 +78,7 @@ const (
|
||||
type DiffLine struct {
|
||||
LeftIdx int // line number, 1-based
|
||||
RightIdx int // line number, 1-based
|
||||
Match int // line number, 1-based
|
||||
Match int // the diff matched index. -1: no match. 0: plain and no need to match. >0: for add/del, "Lines" slice index of the other side
|
||||
Type DiffLineType
|
||||
Content string
|
||||
Comments issues_model.CommentList // related PR code comments
|
||||
@ -203,12 +203,20 @@ func getLineContent(content string, locale translation.Locale) DiffInline {
|
||||
type DiffSection struct {
|
||||
file *DiffFile
|
||||
FileName string
|
||||
Name string
|
||||
Lines []*DiffLine
|
||||
}
|
||||
|
||||
func (diffSection *DiffSection) GetLine(idx int) *DiffLine {
|
||||
if idx <= 0 {
|
||||
return nil
|
||||
}
|
||||
return diffSection.Lines[idx]
|
||||
}
|
||||
|
||||
// GetLine gets a specific line by type (add or del) and file line number
|
||||
func (diffSection *DiffSection) GetLine(lineType DiffLineType, idx int) *DiffLine {
|
||||
// This algorithm is not quite right.
|
||||
// Actually now we have "Match" field, it is always right, so use it instead in new GetLine
|
||||
func (diffSection *DiffSection) getLineLegacy(lineType DiffLineType, idx int) *DiffLine { //nolint:unused
|
||||
var (
|
||||
difference = 0
|
||||
addCount = 0
|
||||
@ -279,7 +287,7 @@ func (diffSection *DiffSection) getLineContentForRender(lineIdx int, diffLine *D
|
||||
if setting.Git.DisableDiffHighlight {
|
||||
return template.HTML(html.EscapeString(diffLine.Content[1:]))
|
||||
}
|
||||
h, _ = highlight.Code(diffSection.Name, fileLanguage, diffLine.Content[1:])
|
||||
h, _ = highlight.Code(diffSection.FileName, fileLanguage, diffLine.Content[1:])
|
||||
return h
|
||||
}
|
||||
|
||||
@ -292,20 +300,31 @@ func (diffSection *DiffSection) getDiffLineForRender(diffLineType DiffLineType,
|
||||
highlightedLeftLines, highlightedRightLines = diffSection.file.highlightedLeftLines, diffSection.file.highlightedRightLines
|
||||
}
|
||||
|
||||
var lineHTML template.HTML
|
||||
hcd := newHighlightCodeDiff()
|
||||
var diff1, diff2, lineHTML template.HTML
|
||||
if leftLine != nil {
|
||||
diff1 = diffSection.getLineContentForRender(leftLine.LeftIdx, leftLine, fileLanguage, highlightedLeftLines)
|
||||
lineHTML = util.Iif(diffLineType == DiffLinePlain, diff1, "")
|
||||
}
|
||||
if rightLine != nil {
|
||||
diff2 = diffSection.getLineContentForRender(rightLine.RightIdx, rightLine, fileLanguage, highlightedRightLines)
|
||||
lineHTML = util.Iif(diffLineType == DiffLinePlain, diff2, "")
|
||||
}
|
||||
if diffLineType != DiffLinePlain {
|
||||
// it seems that Gitea doesn't need the line wrapper of Chroma, so do not add them back
|
||||
// if the line wrappers are still needed in the future, it can be added back by "diffLineWithHighlightWrapper(hcd.lineWrapperTags. ...)"
|
||||
lineHTML = hcd.diffLineWithHighlight(diffLineType, diff1, diff2)
|
||||
if diffLineType == DiffLinePlain {
|
||||
// left and right are the same, no need to do line-level diff
|
||||
if leftLine != nil {
|
||||
lineHTML = diffSection.getLineContentForRender(leftLine.LeftIdx, leftLine, fileLanguage, highlightedLeftLines)
|
||||
} else if rightLine != nil {
|
||||
lineHTML = diffSection.getLineContentForRender(rightLine.RightIdx, rightLine, fileLanguage, highlightedRightLines)
|
||||
}
|
||||
} else {
|
||||
var diff1, diff2 template.HTML
|
||||
if leftLine != nil {
|
||||
diff1 = diffSection.getLineContentForRender(leftLine.LeftIdx, leftLine, fileLanguage, highlightedLeftLines)
|
||||
}
|
||||
if rightLine != nil {
|
||||
diff2 = diffSection.getLineContentForRender(rightLine.RightIdx, rightLine, fileLanguage, highlightedRightLines)
|
||||
}
|
||||
if diff1 != "" && diff2 != "" {
|
||||
// if only some parts of a line are changed, highlight these changed parts as "deleted/added".
|
||||
lineHTML = hcd.diffLineWithHighlight(diffLineType, diff1, diff2)
|
||||
} else {
|
||||
// if left is empty or right is empty (a line is fully deleted or added), then we do not need to diff anymore.
|
||||
// the tmpl code already adds background colors for these cases.
|
||||
lineHTML = util.Iif(diffLineType == DiffLineDel, diff1, diff2)
|
||||
}
|
||||
}
|
||||
return DiffInlineWithUnicodeEscape(lineHTML, locale)
|
||||
}
|
||||
@ -317,10 +336,10 @@ func (diffSection *DiffSection) GetComputedInlineDiffFor(diffLine *DiffLine, loc
|
||||
case DiffLineSection:
|
||||
return getLineContent(diffLine.Content[1:], locale)
|
||||
case DiffLineAdd:
|
||||
compareDiffLine := diffSection.GetLine(DiffLineDel, diffLine.RightIdx)
|
||||
compareDiffLine := diffSection.GetLine(diffLine.Match)
|
||||
return diffSection.getDiffLineForRender(DiffLineAdd, compareDiffLine, diffLine, locale)
|
||||
case DiffLineDel:
|
||||
compareDiffLine := diffSection.GetLine(DiffLineAdd, diffLine.LeftIdx)
|
||||
compareDiffLine := diffSection.GetLine(diffLine.Match)
|
||||
return diffSection.getDiffLineForRender(DiffLineDel, diffLine, compareDiffLine, locale)
|
||||
default: // Plain
|
||||
// TODO: there was an "if" check: `if diffLine.Content >strings.IndexByte(" +-", diffLine.Content[0]) > -1 { ... } else { ... }`
|
||||
@ -383,15 +402,22 @@ type DiffLimitedContent struct {
|
||||
|
||||
// GetTailSectionAndLimitedContent creates a fake DiffLineSection if the last section is not the end of the file
|
||||
func (diffFile *DiffFile) GetTailSectionAndLimitedContent(leftCommit, rightCommit *git.Commit) (_ *DiffSection, diffLimitedContent DiffLimitedContent) {
|
||||
if len(diffFile.Sections) == 0 || leftCommit == nil || diffFile.Type != DiffFileChange || diffFile.IsBin || diffFile.IsLFSFile {
|
||||
var leftLineCount, rightLineCount int
|
||||
diffLimitedContent = DiffLimitedContent{}
|
||||
if diffFile.IsBin || diffFile.IsLFSFile {
|
||||
return nil, diffLimitedContent
|
||||
}
|
||||
if (diffFile.Type == DiffFileDel || diffFile.Type == DiffFileChange) && leftCommit != nil {
|
||||
leftLineCount, diffLimitedContent.LeftContent = getCommitFileLineCountAndLimitedContent(leftCommit, diffFile.OldName)
|
||||
}
|
||||
if (diffFile.Type == DiffFileAdd || diffFile.Type == DiffFileChange) && rightCommit != nil {
|
||||
rightLineCount, diffLimitedContent.RightContent = getCommitFileLineCountAndLimitedContent(rightCommit, diffFile.OldName)
|
||||
}
|
||||
if len(diffFile.Sections) == 0 || diffFile.Type != DiffFileChange {
|
||||
return nil, diffLimitedContent
|
||||
}
|
||||
|
||||
lastSection := diffFile.Sections[len(diffFile.Sections)-1]
|
||||
lastLine := lastSection.Lines[len(lastSection.Lines)-1]
|
||||
leftLineCount, leftContent := getCommitFileLineCountAndLimitedContent(leftCommit, diffFile.Name)
|
||||
rightLineCount, rightContent := getCommitFileLineCountAndLimitedContent(rightCommit, diffFile.Name)
|
||||
diffLimitedContent = DiffLimitedContent{LeftContent: leftContent, RightContent: rightContent}
|
||||
if leftLineCount <= lastLine.LeftIdx || rightLineCount <= lastLine.RightIdx {
|
||||
return nil, diffLimitedContent
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ func (hcd *highlightCodeDiff) diffLineWithHighlightWrapper(lineWrapperTags []str
|
||||
|
||||
dmp := defaultDiffMatchPatch()
|
||||
diffs := dmp.DiffMain(convertedCodeA, convertedCodeB, true)
|
||||
diffs = dmp.DiffCleanupEfficiency(diffs)
|
||||
diffs = dmp.DiffCleanupSemantic(diffs)
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
|
@ -23,6 +23,16 @@ func TestDiffWithHighlight(t *testing.T) {
|
||||
assert.Equal(t, `x <span class="k"><span class="added-code">bar</span></span> y`, string(outAdd))
|
||||
})
|
||||
|
||||
t.Run("CleanUp", func(t *testing.T) {
|
||||
hcd := newHighlightCodeDiff()
|
||||
codeA := template.HTML(`<span class="cm>this is a comment</span>`)
|
||||
codeB := template.HTML(`<span class="cm>this is updated comment</span>`)
|
||||
outDel := hcd.diffLineWithHighlight(DiffLineDel, codeA, codeB)
|
||||
assert.Equal(t, `<span class="cm>this is <span class="removed-code">a</span> comment</span>`, string(outDel))
|
||||
outAdd := hcd.diffLineWithHighlight(DiffLineAdd, codeA, codeB)
|
||||
assert.Equal(t, `<span class="cm>this is <span class="added-code">updated</span> comment</span>`, string(outAdd))
|
||||
})
|
||||
|
||||
t.Run("OpenCloseTags", func(t *testing.T) {
|
||||
hcd := newHighlightCodeDiff()
|
||||
hcd.placeholderTokenMap['O'], hcd.placeholderTokenMap['C'] = "<span>", "</span>"
|
||||
|
@ -47,7 +47,6 @@ func TestGetDiffPreview(t *testing.T) {
|
||||
Sections: []*gitdiff.DiffSection{
|
||||
{
|
||||
FileName: "README.md",
|
||||
Name: "",
|
||||
Lines: []*gitdiff.DiffLine{
|
||||
{
|
||||
LeftIdx: 0,
|
||||
|
@ -19,12 +19,6 @@
|
||||
<link rel="icon" href="{{AssetUrlPrefix}}/img/favicon.svg" type="image/svg+xml">
|
||||
<link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png">
|
||||
{{template "base/head_script" .}}
|
||||
<noscript>
|
||||
<style>
|
||||
.dropdown:hover > .menu { display: block; }
|
||||
.ui.secondary.menu .dropdown.item > .menu { margin-top: 0; }
|
||||
</style>
|
||||
</noscript>
|
||||
{{template "base/head_opengraph" .}}
|
||||
{{template "base/head_style" .}}
|
||||
{{template "custom/header" .}}
|
||||
|
@ -15,8 +15,8 @@
|
||||
{{$commit := $item.Commit}}
|
||||
{{$submoduleFile := $item.SubmoduleFile}}
|
||||
<div class="repo-file-cell name {{if not $commit}}notready{{end}}">
|
||||
{{ctx.RenderUtils.RenderFileIcon $entry}}
|
||||
{{if $entry.IsSubModule}}
|
||||
{{svg "octicon-file-submodule"}}
|
||||
{{$submoduleLink := $submoduleFile.SubmoduleWebLink ctx}}
|
||||
{{if $submoduleLink}}
|
||||
<a class="muted" href="{{$submoduleLink.RepoWebLink}}">{{$entry.Name}}</a> <span class="at">@</span> <a href="{{$submoduleLink.CommitWebLink}}">{{ShortSha $submoduleFile.RefID}}</a>
|
||||
@ -26,7 +26,6 @@
|
||||
{{else}}
|
||||
{{if $entry.IsDir}}
|
||||
{{$subJumpablePathName := $entry.GetSubJumpablePathName}}
|
||||
{{svg "octicon-file-directory-fill"}}
|
||||
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $subJumpablePathName}}" title="{{$subJumpablePathName}}">
|
||||
{{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}}
|
||||
{{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}}
|
||||
@ -38,7 +37,6 @@
|
||||
{{end}}
|
||||
</a>
|
||||
{{else}}
|
||||
{{svg (printf "octicon-%s" (EntryIcon $entry))}}
|
||||
<a class="muted" href="{{$.TreeLink}}/{{PathEscapeSegments $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -54,9 +54,14 @@ func TestGitLFSSSH(t *testing.T) {
|
||||
return strings.Contains(s, "POST /api/internal/repo/user2/repo1.git/info/lfs/objects/batch")
|
||||
})
|
||||
countUpload := slices.ContainsFunc(routerCalls, func(s string) bool {
|
||||
return strings.Contains(s, "PUT /user2/repo1.git/info/lfs/objects/")
|
||||
return strings.Contains(s, "PUT /api/internal/repo/user2/repo1.git/info/lfs/objects/")
|
||||
})
|
||||
nonAPIRequests := slices.ContainsFunc(routerCalls, func(s string) bool {
|
||||
fields := strings.Fields(s)
|
||||
return !strings.HasPrefix(fields[1], "/api/")
|
||||
})
|
||||
assert.NotZero(t, countBatch)
|
||||
assert.NotZero(t, countUpload)
|
||||
assert.Zero(t, nonAPIRequests)
|
||||
})
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
|
||||
|
||||
func TestViewRepoWithSymlinks(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
defer test.MockVariableValue(&setting.UI.FileIconTheme, "basic")()
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
req := NewRequest(t, "GET", "/user2/repo20.git")
|
||||
|
@ -45,6 +45,7 @@ SIGNING_KEY = none
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3003
|
||||
ROOT_URL = http://localhost:3003/
|
||||
LOCAL_ROOT_URL = http://127.0.0.1:3003/
|
||||
DISABLE_SSH = false
|
||||
SSH_LISTEN_HOST = localhost
|
||||
SSH_PORT = 2201
|
||||
|
@ -47,6 +47,7 @@ SIGNING_KEY = none
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3001
|
||||
ROOT_URL = http://localhost:3001/
|
||||
LOCAL_ROOT_URL = http://127.0.0.1:3001/
|
||||
DISABLE_SSH = false
|
||||
SSH_LISTEN_HOST = localhost
|
||||
SSH_PORT = 2201
|
||||
|
@ -46,6 +46,7 @@ SIGNING_KEY = none
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3002
|
||||
ROOT_URL = http://localhost:3002/
|
||||
LOCAL_ROOT_URL = http://127.0.0.1:3002/
|
||||
DISABLE_SSH = false
|
||||
SSH_LISTEN_HOST = localhost
|
||||
SSH_PORT = 2202
|
||||
|
@ -41,6 +41,7 @@ SIGNING_KEY = none
|
||||
SSH_DOMAIN = localhost
|
||||
HTTP_PORT = 3003
|
||||
ROOT_URL = http://localhost:3003/
|
||||
LOCAL_ROOT_URL = http://127.0.0.1:3003/
|
||||
DISABLE_SSH = false
|
||||
SSH_LISTEN_HOST = localhost
|
||||
SSH_PORT = 2203
|
||||
|
@ -5,27 +5,20 @@ import {parse} from 'node:path';
|
||||
import {readFile, writeFile, mkdir} from 'node:fs/promises';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {exit} from 'node:process';
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
const glob = (pattern) => fastGlob.sync(pattern, {
|
||||
cwd: fileURLToPath(new URL('..', import.meta.url)),
|
||||
absolute: true,
|
||||
});
|
||||
|
||||
function doExit(err) {
|
||||
if (err) console.error(err);
|
||||
exit(err ? 1 : 0);
|
||||
}
|
||||
|
||||
async function processFile(file, {prefix, fullName} = {}) {
|
||||
let name;
|
||||
if (fullName) {
|
||||
name = fullName;
|
||||
} else {
|
||||
async function processAssetsSvgFile(file, {prefix, fullName} = {}) {
|
||||
let name = fullName;
|
||||
if (!name) {
|
||||
name = parse(file).name;
|
||||
if (prefix) name = `${prefix}-${name}`;
|
||||
if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons
|
||||
}
|
||||
|
||||
// Set the `xmlns` attribute so that the files are displayable in standalone documents
|
||||
// The svg backend module will strip the attribute during startup for inline display
|
||||
const {data} = optimize(await readFile(file, 'utf8'), {
|
||||
@ -44,28 +37,60 @@ async function processFile(file, {prefix, fullName} = {}) {
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await writeFile(fileURLToPath(new URL(`../public/assets/img/svg/${name}.svg`, import.meta.url)), data);
|
||||
}
|
||||
|
||||
function processFiles(pattern, opts) {
|
||||
return glob(pattern).map((file) => processFile(file, opts));
|
||||
function processAssetsSvgFiles(pattern, opts) {
|
||||
return glob(pattern).map((file) => processAssetsSvgFile(file, opts));
|
||||
}
|
||||
|
||||
async function processMaterialFileIcons() {
|
||||
const files = glob('node_modules/material-icon-theme/icons/*.svg');
|
||||
const svgSymbols = {};
|
||||
for (const file of files) {
|
||||
// remove all unnecessary attributes, only keep "viewBox"
|
||||
const {data} = optimize(await readFile(file, 'utf8'), {
|
||||
plugins: [
|
||||
{name: 'preset-default'},
|
||||
{name: 'removeDimensions'},
|
||||
{name: 'removeXMLNS'},
|
||||
{name: 'removeAttrs', params: {attrs: 'xml:space', elemSeparator: ','}},
|
||||
],
|
||||
});
|
||||
const svgName = parse(file).name;
|
||||
// intentionally use single quote here to avoid escaping
|
||||
svgSymbols[svgName] = data.replace(/"/g, `'`);
|
||||
}
|
||||
fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-svgs.json`, import.meta.url)), JSON.stringify(svgSymbols, null, 2));
|
||||
|
||||
const iconRulesJson = await readFile(fileURLToPath(new URL(`../node_modules/material-icon-theme/dist/material-icons.json`, import.meta.url)));
|
||||
const iconRules = JSON.parse(iconRulesJson);
|
||||
// The rules are from VSCode material-icon-theme, we need to adjust them to our needs
|
||||
// 1. We only use lowercase filenames to match (it should be good enough for most cases and more efficient)
|
||||
// 2. We do not have a "Language ID" system: https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
|
||||
// * So we just treat the "Language ID" as file extension, it is not always true, but it is good enough for most cases.
|
||||
delete iconRules.iconDefinitions;
|
||||
for (const [k, v] of Object.entries(iconRules.fileNames)) iconRules.fileNames[k.toLowerCase()] = v;
|
||||
for (const [k, v] of Object.entries(iconRules.folderNames)) iconRules.folderNames[k.toLowerCase()] = v;
|
||||
for (const [k, v] of Object.entries(iconRules.fileExtensions)) iconRules.fileExtensions[k.toLowerCase()] = v;
|
||||
for (const [k, v] of Object.entries(iconRules.languageIds)) iconRules.fileExtensions[k.toLowerCase()] = v;
|
||||
const iconRulesPretty = JSON.stringify(iconRules, null, 2);
|
||||
fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-rules.json`, import.meta.url)), iconRulesPretty);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await mkdir(fileURLToPath(new URL('../public/assets/img/svg', import.meta.url)), {recursive: true});
|
||||
} catch {}
|
||||
|
||||
await mkdir(fileURLToPath(new URL('../public/assets/img/svg', import.meta.url)), {recursive: true});
|
||||
await Promise.all([
|
||||
...processFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}),
|
||||
...processFiles('web_src/svg/*.svg'),
|
||||
...processFiles('public/assets/img/gitea.svg', {fullName: 'gitea-gitea'}),
|
||||
...processAssetsSvgFiles('node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}),
|
||||
...processAssetsSvgFiles('web_src/svg/*.svg'),
|
||||
...processAssetsSvgFiles('public/assets/img/gitea.svg', {fullName: 'gitea-gitea'}),
|
||||
processMaterialFileIcons(),
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
doExit(await main());
|
||||
await main();
|
||||
} catch (err) {
|
||||
doExit(err);
|
||||
console.error(err);
|
||||
exit(1);
|
||||
}
|
||||
|
@ -4,6 +4,10 @@
|
||||
fill: currentcolor;
|
||||
}
|
||||
|
||||
.svg.fileicon {
|
||||
fill: transparent; /* some material icons have dark background fill, so need to reset */
|
||||
}
|
||||
|
||||
.middle .svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
1
web_src/svg/material-folder-generic.svg
Normal file
1
web_src/svg/material-folder-generic.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 4H4c-1.11 0-2 .89-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" fill="#42a5f5"/></svg>
|
After Width: | Height: | Size: 195 B |
1
web_src/svg/material-folder-symlink.svg
Normal file
1
web_src/svg/material-folder-symlink.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 4H4c-1.11 0-2 .89-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" fill="#42a5f5" opacity=".745"/><path d="M16.972 10.757v2.641h-6.561v5.281h6.561v2.641l6.562-5.281-6.562-5.282z" opacity=".81" fill="#c5e5fd"/></svg>
|
After Width: | Height: | Size: 321 B |
Loading…
x
Reference in New Issue
Block a user