Compare commits

..

No commits in common. "28625fba5bec01ef8278d6bf4ae40a3319ba301a" and "ddf61373f691a584844f141bc4728948fe142dc3" have entirely different histories.

36 changed files with 116 additions and 194 deletions

View File

@ -153,7 +153,7 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
return DefaultAvatarLink() return DefaultAvatarLink()
} }
enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar) enableFederatedAvatar := system_model.GetSettingBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
var err error var err error
if enableFederatedAvatar && system_model.LibravatarService != nil { if enableFederatedAvatar && system_model.LibravatarService != nil {
@ -174,7 +174,7 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
return urlStr return urlStr
} }
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar) disableGravatar := system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
if !disableGravatar { if !disableGravatar {
// copy GravatarSourceURL, because we will modify its Path. // copy GravatarSourceURL, because we will modify its Path.
avatarURLCopy := *system_model.GravatarSourceURL avatarURLCopy := *system_model.GravatarSourceURL

View File

@ -28,7 +28,7 @@ func enableGravatar(t *testing.T) {
err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false") err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false")
assert.NoError(t, err) assert.NoError(t, err)
setting.GravatarSource = gravatarSource setting.GravatarSource = gravatarSource
err = system_model.Init(db.DefaultContext) err = system_model.Init()
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -134,7 +134,7 @@ func Find[T any](ctx context.Context, opts FindOptions, objects *[]T) error {
if !opts.IsListAll() { if !opts.IsListAll() {
sess.Limit(opts.GetSkipTake()) sess.Limit(opts.GetSkipTake())
} }
return sess.Find(objects) return sess.Find(&objects)
} }
// Count represents a common count function which accept an options interface // Count represents a common count function which accept an options interface
@ -148,5 +148,5 @@ func FindAndCount[T any](ctx context.Context, opts FindOptions, objects *[]T) (i
if !opts.IsListAll() { if !opts.IsListAll() {
sess.Limit(opts.GetSkipTake()) sess.Limit(opts.GetSkipTake())
} }
return sess.FindAndCount(objects) return sess.FindAndCount(&objects)
} }

View File

@ -1,48 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package db_test
import (
"testing"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
"xorm.io/builder"
)
type mockListOptions struct {
db.ListOptions
}
func (opts *mockListOptions) IsListAll() bool {
return true
}
func (opts *mockListOptions) ToConds() builder.Cond {
return builder.NewCond()
}
func TestFind(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
xe := unittest.GetXORMEngine()
assert.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
opts := mockListOptions{}
var repoUnits []repo_model.RepoUnit
err := db.Find(db.DefaultContext, &opts, &repoUnits)
assert.NoError(t, err)
assert.EqualValues(t, 83, len(repoUnits))
cnt, err := db.Count(db.DefaultContext, &opts, new(repo_model.RepoUnit))
assert.NoError(t, err)
assert.EqualValues(t, 83, cnt)
repoUnits = make([]repo_model.RepoUnit, 0, 10)
newCnt, err := db.FindAndCount(db.DefaultContext, &opts, &repoUnits)
assert.NoError(t, err)
assert.EqualValues(t, cnt, newCnt)
}

View File

@ -39,9 +39,9 @@ import (
var ItemsPerPage = 40 var ItemsPerPage = 40
// Init initialize model // Init initialize model
func Init(ctx context.Context) error { func Init() error {
unit.LoadUnitConfig() unit.LoadUnitConfig()
return system_model.Init(ctx) return system_model.Init()
} }
// DeleteRepository deletes a repository for a user or organization. // DeleteRepository deletes a repository for a user or organization.

View File

@ -79,8 +79,8 @@ func IsErrDataExpired(err error) bool {
return ok return ok
} }
// GetSetting returns specific setting without using the cache // GetSettingNoCache returns specific setting without using the cache
func GetSetting(ctx context.Context, key string) (*Setting, error) { func GetSettingNoCache(ctx context.Context, key string) (*Setting, error) {
v, err := GetSettings(ctx, []string{key}) v, err := GetSettings(ctx, []string{key})
if err != nil { if err != nil {
return nil, err return nil, err
@ -93,11 +93,11 @@ func GetSetting(ctx context.Context, key string) (*Setting, error) {
const contextCacheKey = "system_setting" const contextCacheKey = "system_setting"
// GetSettingWithCache returns the setting value via the key // GetSetting returns the setting value via the key
func GetSettingWithCache(ctx context.Context, key string) (string, error) { func GetSetting(ctx context.Context, key string) (string, error) {
return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
return cache.GetString(genSettingCacheKey(key), func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) {
res, err := GetSetting(ctx, key) res, err := GetSettingNoCache(ctx, key)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -110,15 +110,6 @@ func GetSettingWithCache(ctx context.Context, key string) (string, error) {
// none existing keys and errors are ignored and result in false // none existing keys and errors are ignored and result in false
func GetSettingBool(ctx context.Context, key string) bool { func GetSettingBool(ctx context.Context, key string) bool {
s, _ := GetSetting(ctx, key) s, _ := GetSetting(ctx, key)
if s == nil {
return false
}
v, _ := strconv.ParseBool(s.SettingValue)
return v
}
func GetSettingWithCacheBool(ctx context.Context, key string) bool {
s, _ := GetSettingWithCache(ctx, key)
v, _ := strconv.ParseBool(s) v, _ := strconv.ParseBool(s)
return v return v
} }
@ -129,7 +120,7 @@ func GetSettings(ctx context.Context, keys []string) (map[string]*Setting, error
keys[i] = strings.ToLower(keys[i]) keys[i] = strings.ToLower(keys[i])
} }
settings := make([]*Setting, 0, len(keys)) settings := make([]*Setting, 0, len(keys))
if err := db.GetEngine(ctx). if err := db.GetEngine(db.DefaultContext).
Where(builder.In("setting_key", keys)). Where(builder.In("setting_key", keys)).
Find(&settings); err != nil { Find(&settings); err != nil {
return nil, err return nil, err
@ -160,9 +151,9 @@ func (settings AllSettings) GetVersion(key string) int {
} }
// GetAllSettings returns all settings from user // GetAllSettings returns all settings from user
func GetAllSettings(ctx context.Context) (AllSettings, error) { func GetAllSettings() (AllSettings, error) {
settings := make([]*Setting, 0, 5) settings := make([]*Setting, 0, 5)
if err := db.GetEngine(ctx). if err := db.GetEngine(db.DefaultContext).
Find(&settings); err != nil { Find(&settings); err != nil {
return nil, err return nil, err
} }
@ -177,12 +168,12 @@ func GetAllSettings(ctx context.Context) (AllSettings, error) {
func DeleteSetting(ctx context.Context, setting *Setting) error { func DeleteSetting(ctx context.Context, setting *Setting) error {
cache.RemoveContextData(ctx, contextCacheKey, setting.SettingKey) cache.RemoveContextData(ctx, contextCacheKey, setting.SettingKey)
cache.Remove(genSettingCacheKey(setting.SettingKey)) cache.Remove(genSettingCacheKey(setting.SettingKey))
_, err := db.GetEngine(ctx).Delete(setting) _, err := db.GetEngine(db.DefaultContext).Delete(setting)
return err return err
} }
func SetSettingNoVersion(ctx context.Context, key, value string) error { func SetSettingNoVersion(ctx context.Context, key, value string) error {
s, err := GetSetting(ctx, key) s, err := GetSettingNoCache(ctx, key)
if IsErrSettingIsNotExist(err) { if IsErrSettingIsNotExist(err) {
return SetSetting(ctx, &Setting{ return SetSetting(ctx, &Setting{
SettingKey: key, SettingKey: key,
@ -198,7 +189,7 @@ func SetSettingNoVersion(ctx context.Context, key, value string) error {
// SetSetting updates a users' setting for a specific key // SetSetting updates a users' setting for a specific key
func SetSetting(ctx context.Context, setting *Setting) error { func SetSetting(ctx context.Context, setting *Setting) error {
if err := upsertSettingValue(ctx, strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil { if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
return err return err
} }
@ -214,8 +205,8 @@ func SetSetting(ctx context.Context, setting *Setting) error {
return nil return nil
} }
func upsertSettingValue(parentCtx context.Context, key, value string, version int) error { func upsertSettingValue(key, value string, version int) error {
return db.WithTx(parentCtx, func(ctx context.Context) error { return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
// here we use a general method to do a safe upsert for different databases (and most transaction levels) // here we use a general method to do a safe upsert for different databases (and most transaction levels)
@ -258,9 +249,9 @@ var (
LibravatarService *libravatar.Libravatar LibravatarService *libravatar.Libravatar
) )
func Init(ctx context.Context) error { func Init() error {
var disableGravatar bool var disableGravatar bool
disableGravatarSetting, err := GetSetting(ctx, KeyPictureDisableGravatar) disableGravatarSetting, err := GetSettingNoCache(db.DefaultContext, KeyPictureDisableGravatar)
if IsErrSettingIsNotExist(err) { if IsErrSettingIsNotExist(err) {
disableGravatar = setting_module.GetDefaultDisableGravatar() disableGravatar = setting_module.GetDefaultDisableGravatar()
disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)} disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
@ -271,7 +262,7 @@ func Init(ctx context.Context) error {
} }
var enableFederatedAvatar bool var enableFederatedAvatar bool
enableFederatedAvatarSetting, err := GetSetting(ctx, KeyPictureEnableFederatedAvatar) enableFederatedAvatarSetting, err := GetSettingNoCache(db.DefaultContext, KeyPictureEnableFederatedAvatar)
if IsErrSettingIsNotExist(err) { if IsErrSettingIsNotExist(err) {
enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar) enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)} enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
@ -284,13 +275,13 @@ func Init(ctx context.Context) error {
if setting_module.OfflineMode { if setting_module.OfflineMode {
disableGravatar = true disableGravatar = true
enableFederatedAvatar = false enableFederatedAvatar = false
if !GetSettingBool(ctx, KeyPictureDisableGravatar) { if !GetSettingBool(db.DefaultContext, KeyPictureDisableGravatar) {
if err := SetSettingNoVersion(ctx, KeyPictureDisableGravatar, "true"); err != nil { if err := SetSettingNoVersion(db.DefaultContext, KeyPictureDisableGravatar, "true"); err != nil {
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err) return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
} }
} }
if GetSettingBool(ctx, KeyPictureEnableFederatedAvatar) { if GetSettingBool(db.DefaultContext, KeyPictureEnableFederatedAvatar) {
if err := SetSettingNoVersion(ctx, KeyPictureEnableFederatedAvatar, "false"); err != nil { if err := SetSettingNoVersion(db.DefaultContext, KeyPictureEnableFederatedAvatar, "false"); err != nil {
return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err) return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
} }
} }

View File

@ -40,10 +40,10 @@ func TestSettings(t *testing.T) {
value, err := system.GetSetting(db.DefaultContext, keyName) value, err := system.GetSetting(db.DefaultContext, keyName)
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, updatedSetting.SettingValue, value.SettingValue) assert.EqualValues(t, updatedSetting.SettingValue, value)
// get all settings // get all settings
settings, err = system.GetAllSettings(db.DefaultContext) settings, err = system.GetAllSettings()
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, settings, 3) assert.Len(t, settings, 3)
assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue) assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue)
@ -51,7 +51,7 @@ func TestSettings(t *testing.T) {
// delete setting // delete setting
err = system.DeleteSetting(db.DefaultContext, &system.Setting{SettingKey: strings.ToLower(keyName)}) err = system.DeleteSetting(db.DefaultContext, &system.Setting{SettingKey: strings.ToLower(keyName)})
assert.NoError(t, err) assert.NoError(t, err)
settings, err = system.GetAllSettings(db.DefaultContext) settings, err = system.GetAllSettings()
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, settings, 2) assert.Len(t, settings, 2)
} }

View File

@ -113,7 +113,7 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
if err = storage.Init(); err != nil { if err = storage.Init(); err != nil {
fatalTestError("storage.Init: %v\n", err) fatalTestError("storage.Init: %v\n", err)
} }
if err = system_model.Init(db.DefaultContext); err != nil { if err = system_model.Init(); err != nil {
fatalTestError("models.Init: %v\n", err) fatalTestError("models.Init: %v\n", err)
} }

View File

@ -67,7 +67,7 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
useLocalAvatar := false useLocalAvatar := false
autoGenerateAvatar := false autoGenerateAvatar := false
disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar) disableGravatar := system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
switch { switch {
case u.UseCustomAvatar: case u.UseCustomAvatar:

View File

@ -106,7 +106,7 @@ func enableGravatar(t *testing.T) {
err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false") err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false")
assert.NoError(t, err) assert.NoError(t, err)
setting.GravatarSource = "https://secure.gravatar.com/avatar" setting.GravatarSource = "https://secure.gravatar.com/avatar"
err = system_model.Init(db.DefaultContext) err = system_model.Init()
assert.NoError(t, err) assert.NoError(t, err)
} }

View File

@ -92,7 +92,7 @@ func NewFuncMap() []template.FuncMap {
return setting.AssetVersion return setting.AssetVersion
}, },
"DisableGravatar": func(ctx context.Context) bool { "DisableGravatar": func(ctx context.Context) bool {
return system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar) return system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
}, },
"DefaultShowFullName": func() bool { "DefaultShowFullName": func() bool {
return setting.UI.DefaultShowFullName return setting.UI.DefaultShowFullName
@ -174,9 +174,8 @@ func NewFuncMap() []template.FuncMap {
"RenderEmojiPlain": emoji.ReplaceAliases, "RenderEmojiPlain": emoji.ReplaceAliases,
"ReactionToEmoji": ReactionToEmoji, "ReactionToEmoji": ReactionToEmoji,
"RenderNote": RenderNote, "RenderNote": RenderNote,
"RenderMarkdownToHtml": func(ctx context.Context, input string) template.HTML { "RenderMarkdownToHtml": func(input string) template.HTML {
output, err := markdown.RenderString(&markup.RenderContext{ output, err := markdown.RenderString(&markup.RenderContext{
Ctx: ctx,
URLPrefix: setting.AppSubURL, URLPrefix: setting.AppSubURL,
}, input) }, input)
if err != nil { if err != nil {

View File

@ -150,7 +150,7 @@ func GlobalInitInstalled(ctx context.Context) {
mustInit(system.Init) mustInit(system.Init)
mustInit(oauth2.Init) mustInit(oauth2.Init)
mustInitCtx(ctx, models.Init) mustInit(models.Init)
mustInit(repo_service.Init) mustInit(repo_service.Init)
// Booting long running goroutines. // Booting long running goroutines.

View File

@ -103,7 +103,7 @@ func Config(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminConfig"] = true ctx.Data["PageIsAdminConfig"] = true
systemSettings, err := system_model.GetAllSettings(ctx) systemSettings, err := system_model.GetAllSettings()
if err != nil { if err != nil {
ctx.ServerError("system_model.GetAllSettings", err) ctx.ServerError("system_model.GetAllSettings", err)
return return

View File

@ -33,10 +33,9 @@ func Repos(ctx *context.Context) {
ctx.Data["PageIsAdminRepositories"] = true ctx.Data["PageIsAdminRepositories"] = true
explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{ explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{
Private: true, Private: true,
PageSize: setting.UI.Admin.RepoPagingNum, PageSize: setting.UI.Admin.RepoPagingNum,
TplName: tplRepos, TplName: tplRepos,
OnlyShowRelevant: false,
}) })
} }

View File

@ -23,17 +23,14 @@ const (
// RepoSearchOptions when calling search repositories // RepoSearchOptions when calling search repositories
type RepoSearchOptions struct { type RepoSearchOptions struct {
OwnerID int64 OwnerID int64
Private bool Private bool
Restricted bool Restricted bool
PageSize int PageSize int
OnlyShowRelevant bool TplName base.TplName
TplName base.TplName
} }
// RenderRepoSearch render repositories search page // RenderRepoSearch render repositories search page
// This function is also used to render the Admin Repository Management page.
// The isAdmin param should be set to true when rendering the Admin page.
func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
// Sitemap index for sitemap paths // Sitemap index for sitemap paths
page := int(ctx.ParamsInt64("idx")) page := int(ctx.ParamsInt64("idx"))
@ -51,10 +48,11 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
} }
var ( var (
repos []*repo_model.Repository repos []*repo_model.Repository
count int64 count int64
err error err error
orderBy db.SearchOrderBy orderBy db.SearchOrderBy
onlyShowRelevant bool
) )
ctx.Data["SortType"] = ctx.FormString("sort") ctx.Data["SortType"] = ctx.FormString("sort")
@ -86,9 +84,11 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
orderBy = db.SearchOrderByRecentUpdated orderBy = db.SearchOrderByRecentUpdated
} }
onlyShowRelevant = !ctx.FormBool(relevantReposOnlyParam)
keyword := ctx.FormTrim("q") keyword := ctx.FormTrim("q")
ctx.Data["OnlyShowRelevant"] = opts.OnlyShowRelevant ctx.Data["OnlyShowRelevant"] = onlyShowRelevant
topicOnly := ctx.FormBool("topic") topicOnly := ctx.FormBool("topic")
ctx.Data["TopicOnly"] = topicOnly ctx.Data["TopicOnly"] = topicOnly
@ -111,7 +111,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
TopicOnly: topicOnly, TopicOnly: topicOnly,
Language: language, Language: language,
IncludeDescription: setting.UI.SearchRepoDescription, IncludeDescription: setting.UI.SearchRepoDescription,
OnlyShowRelevant: opts.OnlyShowRelevant, OnlyShowRelevant: onlyShowRelevant,
}) })
if err != nil { if err != nil {
ctx.ServerError("SearchRepository", err) ctx.ServerError("SearchRepository", err)
@ -158,10 +158,9 @@ func Repos(ctx *context.Context) {
} }
RenderRepoSearch(ctx, &RepoSearchOptions{ RenderRepoSearch(ctx, &RepoSearchOptions{
PageSize: setting.UI.ExplorePagingNum, PageSize: setting.UI.ExplorePagingNum,
OwnerID: ownerID, OwnerID: ownerID,
Private: ctx.Doer != nil, Private: ctx.Doer != nil,
TplName: tplExploreRepos, TplName: tplExploreRepos,
OnlyShowRelevant: !ctx.FormBool(relevantReposOnlyParam),
}) })
} }

View File

@ -27,6 +27,8 @@ func NewDiffPatch(ctx *context.Context) {
ctx.Data["PageIsPatch"] = true ctx.Data["PageIsPatch"] = true
ctx.Data["TreePath"] = ""
ctx.Data["commit_summary"] = "" ctx.Data["commit_summary"] = ""
ctx.Data["commit_message"] = "" ctx.Data["commit_message"] = ""
if canCommit { if canCommit {
@ -52,6 +54,7 @@ func NewDiffPatchPost(ctx *context.Context) {
branchName = form.NewBranchName branchName = form.NewBranchName
} }
ctx.Data["PageIsPatch"] = true ctx.Data["PageIsPatch"] = true
ctx.Data["TreePath"] = ""
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["FileContent"] = form.Content ctx.Data["FileContent"] = form.Content
ctx.Data["commit_summary"] = form.CommitSummary ctx.Data["commit_summary"] = form.CommitSummary
@ -86,14 +89,13 @@ func NewDiffPatchPost(ctx *context.Context) {
message += "\n\n" + form.CommitMessage message += "\n\n" + form.CommitMessage
} }
fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, &files.ApplyDiffPatchOptions{ if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, &files.ApplyDiffPatchOptions{
LastCommitID: form.LastCommit, LastCommitID: form.LastCommit,
OldBranch: ctx.Repo.BranchName, OldBranch: ctx.Repo.BranchName,
NewBranch: branchName, NewBranch: branchName,
Message: message, Message: message,
Content: strings.ReplaceAll(form.Content, "\r", ""), Content: strings.ReplaceAll(form.Content, "\r", ""),
}) }); err != nil {
if err != nil {
if models.IsErrBranchAlreadyExists(err) { if models.IsErrBranchAlreadyExists(err) {
// User has specified a branch that already exists // User has specified a branch that already exists
branchErr := err.(models.ErrBranchAlreadyExists) branchErr := err.(models.ErrBranchAlreadyExists)
@ -112,6 +114,6 @@ func NewDiffPatchPost(ctx *context.Context) {
if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) { if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) {
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName)) ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
} else { } else {
ctx.Redirect(ctx.Repo.RepoLink + "/commit/" + fileResponse.Commit.SHA) ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
} }
} }

View File

@ -24,7 +24,6 @@ func ToPushMirror(pm *repo_model.PushMirror) (*api.PushMirror, error) {
LastUpdateUnix: pm.LastUpdateUnix.FormatLong(), LastUpdateUnix: pm.LastUpdateUnix.FormatLong(),
LastError: pm.LastError, LastError: pm.LastError,
Interval: pm.Interval.String(), Interval: pm.Interval.String(),
SyncOnCommit: pm.SyncOnCommit,
}, nil }, nil
} }

View File

@ -26,7 +26,7 @@ git-fetch-with-cli = true</code></pre></div>
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Readme}} {{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Readme}}
<h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4> <h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4>
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}} {{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}}
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}</div>{{end}} {{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment">{{RenderMarkdownToHtml .PackageDescriptor.Metadata.Readme}}</div>{{end}}
{{end}} {{end}}
{{if .PackageDescriptor.Metadata.Dependencies}} {{if .PackageDescriptor.Metadata.Dependencies}}

View File

@ -20,7 +20,7 @@
<h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4> <h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4>
<div class="ui attached segment"> <div class="ui attached segment">
{{if .PackageDescriptor.Metadata.Description}}<p>{{.PackageDescriptor.Metadata.Description}}</p>{{end}} {{if .PackageDescriptor.Metadata.Description}}<p>{{.PackageDescriptor.Metadata.Description}}</p>{{end}}
{{if .PackageDescriptor.Metadata.LongDescription}}{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.LongDescription}}{{end}} {{if .PackageDescriptor.Metadata.LongDescription}}{{RenderMarkdownToHtml .PackageDescriptor.Metadata.LongDescription}}{{end}}
</div> </div>
{{end}} {{end}}

View File

@ -25,7 +25,7 @@
<div class="ui attached segment"> <div class="ui attached segment">
{{if .PackageDescriptor.Metadata.Readme}} {{if .PackageDescriptor.Metadata.Readme}}
<div class="markup markdown"> <div class="markup markdown">
{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}} {{RenderMarkdownToHtml .PackageDescriptor.Metadata.Readme}}
</div> </div>
{{else if .PackageDescriptor.Metadata.Description}} {{else if .PackageDescriptor.Metadata.Description}}
{{.PackageDescriptor.Metadata.Description}} {{.PackageDescriptor.Metadata.Description}}

View File

@ -14,6 +14,6 @@
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Readme}} {{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Readme}}
<h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4> <h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4>
{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}} {{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}}
{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}</div>{{end}} {{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment">{{RenderMarkdownToHtml .PackageDescriptor.Metadata.Readme}}</div>{{end}}
{{end}} {{end}}
{{end}} {{end}}

View File

@ -16,9 +16,9 @@
<div class="ui attached segment"> <div class="ui attached segment">
<p>{{if .PackageDescriptor.Metadata.Summary}}{{.PackageDescriptor.Metadata.Summary}}{{end}}</p> <p>{{if .PackageDescriptor.Metadata.Summary}}{{.PackageDescriptor.Metadata.Summary}}{{end}}</p>
{{if .PackageDescriptor.Metadata.LongDescription}} {{if .PackageDescriptor.Metadata.LongDescription}}
{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.LongDescription}} {{RenderMarkdownToHtml .PackageDescriptor.Metadata.LongDescription}}
{{else if .PackageDescriptor.Metadata.Description}} {{else if .PackageDescriptor.Metadata.Description}}
{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Description}} {{RenderMarkdownToHtml .PackageDescriptor.Metadata.Description}}
{{end}} {{end}}
</div> </div>
{{end}} {{end}}

View File

@ -2,15 +2,15 @@
<input type="hidden" name="template-file" value="{{.TemplateFile}}"> <input type="hidden" name="template-file" value="{{.TemplateFile}}">
{{range .Fields}} {{range .Fields}}
{{if eq .Type "input"}} {{if eq .Type "input"}}
{{template "repo/issue/fields/input" Dict "Context" $.Context "item" .}} {{template "repo/issue/fields/input" .}}
{{else if eq .Type "markdown"}} {{else if eq .Type "markdown"}}
{{template "repo/issue/fields/markdown" Dict "Context" $.Context "item" .}} {{template "repo/issue/fields/markdown" .}}
{{else if eq .Type "textarea"}} {{else if eq .Type "textarea"}}
{{template "repo/issue/fields/textarea" Dict "Context" $.Context "item" .}} {{template "repo/issue/fields/textarea" .}}
{{else if eq .Type "dropdown"}} {{else if eq .Type "dropdown"}}
{{template "repo/issue/fields/dropdown" Dict "Context" $.Context "item" .}} {{template "repo/issue/fields/dropdown" .}}
{{else if eq .Type "checkboxes"}} {{else if eq .Type "checkboxes"}}
{{template "repo/issue/fields/checkboxes" Dict "Context" $.Context "item" .}} {{template "repo/issue/fields/checkboxes" .}}
{{end}} {{end}}
{{end}} {{end}}
{{else}} {{else}}

View File

@ -1,7 +1,7 @@
<div class="field"> <div class="field">
{{template "repo/issue/fields/header" .}} {{template "repo/issue/fields/header" .}}
{{$field := .}} {{$field := .}}
{{range $i, $opt := .item.Attributes.options}} {{range $i, $opt := .Attributes.options}}
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" name="form-field-{{$field.ID}}-{{$i}}" {{if $opt.required}}readonly checked{{end}}> <input type="checkbox" name="form-field-{{$field.ID}}-{{$i}}" {{if $opt.required}}readonly checked{{end}}>

View File

@ -1,12 +1,12 @@
<div class="field"> <div class="field">
{{template "repo/issue/fields/header" .}} {{template "repo/issue/fields/header" .}}
{{/* FIXME: required validation */}} {{/* FIXME: required validation */}}
<div class="ui fluid selection dropdown {{if .item.Attributes.multiple}}multiple clearable{{end}}"> <div class="ui fluid selection dropdown {{if .Attributes.multiple}}multiple clearable{{end}}">
<input type="hidden" name="form-field-{{.item.ID}}" value="0"> <input type="hidden" name="form-field-{{.ID}}" value="0">
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
<div class="default text"></div> <div class="default text"></div>
<div class="menu"> <div class="menu">
{{range $i, $opt := .item.Attributes.options}} {{range $i, $opt := .Attributes.options}}
<div class="item" data-value="{{$i}}">{{$opt}}</div> <div class="item" data-value="{{$i}}">{{$opt}}</div>
{{end}} {{end}}
</div> </div>

View File

@ -1,6 +1,6 @@
{{if .item.Attributes.label}} {{if .Attributes.label}}
<h3>{{.item.Attributes.label}}{{if .item.Validations.required}}<label class="required"></label>{{end}}</h3> <h3>{{.Attributes.label}}{{if .Validations.required}}<label class="required"></label>{{end}}</h3>
{{end}} {{end}}
{{if .item.Attributes.description}} {{if .Attributes.description}}
<span class="help">{{RenderMarkdownToHtml .Context .item.Attributes.description}}</span> <span class="help">{{RenderMarkdownToHtml .Attributes.description}}</span>
{{end}} {{end}}

View File

@ -1,4 +1,4 @@
<div class="field"> <div class="field">
{{template "repo/issue/fields/header" .}} {{template "repo/issue/fields/header" .}}
<input type="{{if .item.Validations.is_number}}number{{else}}text{{end}}" name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" value="{{.item.Attributes.value}}" {{if .item.Validations.required}}required{{end}} {{if .item.Validations.regex}}pattern="{{.item.Validations.regex}}" title="{{.item.Validations.regex}}"{{end}}> <input type="{{if .Validations.is_number}}number{{else}}text{{end}}" name="form-field-{{.ID}}" placeholder="{{.Attributes.placeholder}}" value="{{.Attributes.value}}" {{if .Validations.required}}required{{end}} {{if .Validations.regex}}pattern="{{.Validations.regex}}" title="{{.Validations.regex}}"{{end}}>
</div> </div>

View File

@ -1,3 +1,3 @@
<div class="field"> <div class="field">
<div>{{RenderMarkdownToHtml .Context .item.Attributes.value}}</div> <div>{{RenderMarkdownToHtml .Attributes.value}}</div>
</div> </div>

View File

@ -2,5 +2,5 @@
{{template "repo/issue/fields/header" .}} {{template "repo/issue/fields/header" .}}
{{/* FIXME: preview markdown result */}} {{/* FIXME: preview markdown result */}}
{{/* FIXME: required validation for markdown editor */}} {{/* FIXME: required validation for markdown editor */}}
<textarea name="form-field-{{.item.ID}}" placeholder="{{.item.Attributes.placeholder}}" class="edit_area {{if .item.Attributes.render}}no-easymde{{end}}" {{if and .item.Validations.required .item.Attributes.render}}required{{end}}>{{.item.Attributes.value}}</textarea> <textarea name="form-field-{{.ID}}" placeholder="{{.Attributes.placeholder}}" class="edit_area {{if .Attributes.render}}no-easymde{{end}}" {{if and .Validations.required .Attributes.render}}required{{end}}>{{.Attributes.value}}</textarea>
</div> </div>

View File

@ -7,7 +7,7 @@
<div class="header">{{.ctx.locale.Tr "repo.pick_reaction"}}</div> <div class="header">{{.ctx.locale.Tr "repo.pick_reaction"}}</div>
<div class="divider"></div> <div class="divider"></div>
{{range $value := AllowedReactions}} {{range $value := AllowedReactions}}
<a class="item reaction tooltip" data-content="{{$value}}">{{ReactionToEmoji $value}}</a> <div class="item reaction tooltip" data-content="{{$value}}">{{ReactionToEmoji $value}}</div>
{{end}} {{end}}
</div> </div>
</div> </div>

View File

@ -10,16 +10,16 @@
{{else}} {{else}}
{{$referenceUrl = Printf "%s/files#%s" .ctx.Issue.Link .item.HashTag}} {{$referenceUrl = Printf "%s/files#%s" .ctx.Issue.Link .item.HashTag}}
{{end}} {{end}}
<a class="item context" data-clipboard-text-type="url" data-clipboard-text="{{AppSubUrl}}{{$referenceUrl}}">{{.ctx.locale.Tr "repo.issues.context.copy_link"}}</a> <div class="item context" data-clipboard-text-type="url" data-clipboard-text="{{AppSubUrl}}{{$referenceUrl}}">{{.ctx.locale.Tr "repo.issues.context.copy_link"}}</div>
<a class="item context quote-reply {{if .diff}}quote-reply-diff{{end}}" data-target="{{.item.HashTag}}-raw">{{.ctx.locale.Tr "repo.issues.context.quote_reply"}}</a> <div class="item context quote-reply {{if .diff}}quote-reply-diff{{end}}" data-target="{{.item.HashTag}}-raw">{{.ctx.locale.Tr "repo.issues.context.quote_reply"}}</div>
{{if not .ctx.UnitIssuesGlobalDisabled}} {{if not .ctx.UnitIssuesGlobalDisabled}}
<a class="item context reference-issue" data-target="{{.item.HashTag}}-raw" data-modal="#reference-issue-modal" data-poster="{{.item.Poster.GetDisplayName}}" data-poster-username="{{.item.Poster.Name}}" data-reference="{{$referenceUrl}}">{{.ctx.locale.Tr "repo.issues.context.reference_issue"}}</a> <div class="item context reference-issue" data-target="{{.item.HashTag}}-raw" data-modal="#reference-issue-modal" data-poster="{{.item.Poster.GetDisplayName}}" data-poster-username="{{.item.Poster.Name}}" data-reference="{{$referenceUrl}}">{{.ctx.locale.Tr "repo.issues.context.reference_issue"}}</div>
{{end}} {{end}}
{{if or .ctx.Permission.IsAdmin .IsCommentPoster .ctx.HasIssuesOrPullsWritePermission}} {{if or .ctx.Permission.IsAdmin .IsCommentPoster .ctx.HasIssuesOrPullsWritePermission}}
<div class="divider"></div> <div class="divider"></div>
<a class="item context edit-content">{{.ctx.locale.Tr "repo.issues.context.edit"}}</a> <div class="item context edit-content">{{.ctx.locale.Tr "repo.issues.context.edit"}}</div>
{{if .delete}} {{if .delete}}
<a class="item context delete-comment" data-comment-id={{.item.HashTag}} data-url="{{.ctx.RepoLink}}/comments/{{.item.ID}}/delete" data-locale="{{.ctx.locale.Tr "repo.issues.delete_comment_confirm"}}">{{.ctx.locale.Tr "repo.issues.context.delete"}}</a> <div class="item context delete-comment" data-comment-id={{.item.HashTag}} data-url="{{.ctx.RepoLink}}/comments/{{.item.ID}}/delete" data-locale="{{.ctx.locale.Tr "repo.issues.delete_comment_confirm"}}">{{.ctx.locale.Tr "repo.issues.context.delete"}}</div>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>

View File

@ -121,7 +121,7 @@
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_labels"}}"> <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_labels"}}">
</div> </div>
{{end}} {{end}}
<a class="no-select item" href="#">{{.locale.Tr "repo.issues.new.clear_labels"}}</a> <div class="no-select item">{{.locale.Tr "repo.issues.new.clear_labels"}}</div>
{{if or .Labels .OrgLabels}} {{if or .Labels .OrgLabels}}
{{$previousExclusiveScope := "_no_scope"}} {{$previousExclusiveScope := "_no_scope"}}
{{range .Labels}} {{range .Labels}}

View File

@ -81,8 +81,7 @@ function attachOneDropdownAria($dropdown) {
$dropdown.on('keydown', (e) => { $dropdown.on('keydown', (e) => {
// here it must use keydown event before dropdown's keyup handler, otherwise there is no Enter event in our keyup handler // here it must use keydown event before dropdown's keyup handler, otherwise there is no Enter event in our keyup handler
if (e.key === 'Enter') { if (e.key === 'Enter') {
let $item = $dropdown.dropdown('get item', $dropdown.dropdown('get value')); const $item = $dropdown.dropdown('get item', $dropdown.dropdown('get value'));
if (!$item) $item = $menu.find('> .item.selected'); // when dropdown filters items by input, there is no "value", so query the "selected" item
// if the selected item is clickable, then trigger the click event. in the future there could be a special CSS class for it. // if the selected item is clickable, then trigger the click event. in the future there could be a special CSS class for it.
if ($item && $item.is('a')) $item[0].click(); if ($item && $item.is('a')) $item[0].click();
} }

View File

@ -29,26 +29,6 @@ import {hideElem, showElem} from '../utils/dom.js';
const {csrfToken} = window.config; const {csrfToken} = window.config;
// if there are draft comments (more than 20 chars), confirm before reloading, to avoid losing comments
function reloadConfirmDraftComment() {
const commentTextareas = [
document.querySelector('.edit-content-zone:not(.gt-hidden) textarea'),
document.querySelector('.edit_area'),
];
for (const textarea of commentTextareas) {
// Most users won't feel too sad if they lose a comment with 10 or 20 chars, they can re-type these in seconds.
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
if (textarea && textarea.value.trim().length > 20) {
textarea.parentElement.scrollIntoView();
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
return;
}
break;
}
}
window.location.reload();
}
export function initRepoCommentForm() { export function initRepoCommentForm() {
const $commentForm = $('.comment.form'); const $commentForm = $('.comment.form');
if ($commentForm.length === 0) { if ($commentForm.length === 0) {
@ -106,15 +86,12 @@ export function initRepoCommentForm() {
let hasUpdateAction = $listMenu.data('action') === 'update'; let hasUpdateAction = $listMenu.data('action') === 'update';
const items = {}; const items = {};
$(`.${selector}`).dropdown({ $(`.${selector}`).dropdown('setting', 'onHide', () => {
'action': 'nothing', // do not hide the menu if user presses Enter hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
fullTextSearch: 'exact', if (hasUpdateAction) {
async onHide() { // TODO: Add batch functionality and make this 1 network request.
hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var (async function() {
if (hasUpdateAction) { for (const [elementId, item] of Object.entries(items)) {
// TODO: Add batch functionality and make this 1 network request.
const itemEntries = Object.entries(items);
for (const [elementId, item] of itemEntries) {
await updateIssuesMeta( await updateIssuesMeta(
item['update-url'], item['update-url'],
item.action, item.action,
@ -122,11 +99,9 @@ export function initRepoCommentForm() {
elementId, elementId,
); );
} }
if (itemEntries.length) { window.location.reload();
reloadConfirmDraftComment(); })();
} }
}
},
}); });
$listMenu.find('.item:not(.no-select)').on('click', function (e) { $listMenu.find('.item:not(.no-select)').on('click', function (e) {
@ -221,7 +196,7 @@ export function initRepoCommentForm() {
'clear', 'clear',
$listMenu.data('issue-id'), $listMenu.data('issue-id'),
'', '',
).then(reloadConfirmDraftComment); ).then(() => window.location.reload());
} }
$(this).parent().find('.item').each(function () { $(this).parent().find('.item').each(function () {
@ -264,7 +239,7 @@ export function initRepoCommentForm() {
'', '',
$menu.data('issue-id'), $menu.data('issue-id'),
$(this).data('id'), $(this).data('id'),
).then(reloadConfirmDraftComment); ).then(() => window.location.reload());
} }
let icon = ''; let icon = '';
@ -297,7 +272,7 @@ export function initRepoCommentForm() {
'', '',
$menu.data('issue-id'), $menu.data('issue-id'),
$(this).data('id'), $(this).data('id'),
).then(reloadConfirmDraftComment); ).then(() => window.location.reload());
} }
$list.find('.selected').html(''); $list.find('.selected').html('');

View File

@ -3269,9 +3269,17 @@ td.blob-excerpt {
.ui.attached.header.diff-file-header { .ui.attached.header.diff-file-header {
&.sticky-2nd-row { &.sticky-2nd-row {
position: sticky; position: sticky;
top: 77px; top: 46px;
z-index: 7; z-index: 7;
@media @mediaMd {
top: 77px;
}
@media @mediaSm {
top: 77px;
}
@media (max-width: 480px) { @media (max-width: 480px) {
position: static; position: static;
} }

View File

@ -415,7 +415,6 @@
padding: .2em .4em; padding: .2em .4em;
margin: 0; margin: 0;
font-size: 85%; font-size: 85%;
white-space: break-spaces;
background-color: var(--color-markup-code-block); background-color: var(--color-markup-code-block);
border-radius: 4px; border-radius: 4px;
} }