mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-21 00:24:57 +02:00
Merge remote-tracking branch 'origin/main' into playwright-on-unsupported
This commit is contained in:
commit
2991c5057a
3
Makefile
3
Makefile
@ -519,7 +519,8 @@ test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test
|
||||
|
||||
.PHONY: playwright
|
||||
playwright: deps-frontend
|
||||
@pnpm exec playwright install --with-deps chromium firefox webkit $(PLAYWRIGHT_FLAGS)
|
||||
@# on GitHub Actions VMs, playwright's system deps are pre-installed
|
||||
@pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium firefox $(PLAYWRIGHT_FLAGS)
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e: $(EXECUTABLE_E2E)
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
@ -25,12 +24,6 @@ func (runs RunList) GetUserIDs() []int64 {
|
||||
})
|
||||
}
|
||||
|
||||
func (runs RunList) GetRepoIDs() []int64 {
|
||||
return container.FilterSlice(runs, func(run *ActionRun) (int64, bool) {
|
||||
return run.RepoID, true
|
||||
})
|
||||
}
|
||||
|
||||
func (runs RunList) LoadTriggerUser(ctx context.Context) error {
|
||||
userIDs := runs.GetUserIDs()
|
||||
users := make(map[int64]*user_model.User, len(userIDs))
|
||||
@ -50,18 +43,6 @@ func (runs RunList) LoadTriggerUser(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runs RunList) LoadRepos(ctx context.Context) error {
|
||||
repoIDs := runs.GetRepoIDs()
|
||||
repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, run := range runs {
|
||||
run.Repo = repos[run.RepoID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FindRunOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
|
||||
@ -4,62 +4,13 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type ScheduleList []*ActionSchedule
|
||||
|
||||
// GetUserIDs returns a slice of user's id
|
||||
func (schedules ScheduleList) GetUserIDs() []int64 {
|
||||
return container.FilterSlice(schedules, func(schedule *ActionSchedule) (int64, bool) {
|
||||
return schedule.TriggerUserID, true
|
||||
})
|
||||
}
|
||||
|
||||
func (schedules ScheduleList) GetRepoIDs() []int64 {
|
||||
return container.FilterSlice(schedules, func(schedule *ActionSchedule) (int64, bool) {
|
||||
return schedule.RepoID, true
|
||||
})
|
||||
}
|
||||
|
||||
func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error {
|
||||
userIDs := schedules.GetUserIDs()
|
||||
users := make(map[int64]*user_model.User, len(userIDs))
|
||||
if err := db.GetEngine(ctx).In("id", userIDs).Find(&users); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, schedule := range schedules {
|
||||
if schedule.TriggerUserID == user_model.ActionsUserID {
|
||||
schedule.TriggerUser = user_model.NewActionsUser()
|
||||
} else {
|
||||
schedule.TriggerUser = users[schedule.TriggerUserID]
|
||||
if schedule.TriggerUser == nil {
|
||||
schedule.TriggerUser = user_model.NewGhostUser()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (schedules ScheduleList) LoadRepos(ctx context.Context) error {
|
||||
repoIDs := schedules.GetRepoIDs()
|
||||
repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, schedule := range schedules {
|
||||
schedule.Repo = repos[schedule.RepoID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FindScheduleOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
|
||||
@ -282,9 +282,3 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err
|
||||
|
||||
return actions, count, nil
|
||||
}
|
||||
|
||||
func CountUserFeeds(ctx context.Context, userID int64) (int64, error) {
|
||||
return db.GetEngine(ctx).Where("user_id = ?", userID).
|
||||
And("is_deleted = ?", false).
|
||||
Count(&Action{})
|
||||
}
|
||||
|
||||
@ -192,28 +192,6 @@ func (err ErrGPGKeyIDAlreadyUsed) Unwrap() error {
|
||||
return util.ErrAlreadyExist
|
||||
}
|
||||
|
||||
// ErrGPGKeyAccessDenied represents a "GPGKeyAccessDenied" kind of Error.
|
||||
type ErrGPGKeyAccessDenied struct {
|
||||
UserID int64
|
||||
KeyID int64
|
||||
}
|
||||
|
||||
// IsErrGPGKeyAccessDenied checks if an error is a ErrGPGKeyAccessDenied.
|
||||
func IsErrGPGKeyAccessDenied(err error) bool {
|
||||
_, ok := err.(ErrGPGKeyAccessDenied)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Error pretty-prints an error of type ErrGPGKeyAccessDenied.
|
||||
func (err ErrGPGKeyAccessDenied) Error() string {
|
||||
return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d]",
|
||||
err.UserID, err.KeyID)
|
||||
}
|
||||
|
||||
func (err ErrGPGKeyAccessDenied) Unwrap() error {
|
||||
return util.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// ErrKeyAccessDenied represents a "KeyAccessDenied" kind of error.
|
||||
type ErrKeyAccessDenied struct {
|
||||
UserID int64
|
||||
|
||||
@ -105,14 +105,6 @@ func addDeployKey(ctx context.Context, keyID, repoID int64, name, fingerprint st
|
||||
return key, db.Insert(ctx, key)
|
||||
}
|
||||
|
||||
// HasDeployKey returns true if public key is a deploy key of given repository.
|
||||
func HasDeployKey(ctx context.Context, keyID, repoID int64) bool {
|
||||
has, _ := db.GetEngine(ctx).
|
||||
Where("key_id = ? AND repo_id = ?", keyID, repoID).
|
||||
Get(new(DeployKey))
|
||||
return has
|
||||
}
|
||||
|
||||
// AddDeployKey add new deploy key to database and authorized_keys file.
|
||||
func AddDeployKey(ctx context.Context, repoID int64, name, content string, readOnly bool) (*DeployKey, error) {
|
||||
fingerprint, err := CalcFingerprint(content)
|
||||
|
||||
@ -200,13 +200,3 @@ func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) {
|
||||
had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{})
|
||||
return had > 0, err
|
||||
}
|
||||
|
||||
// WebAuthnCredentials implements the webauthn.User interface
|
||||
func WebAuthnCredentials(ctx context.Context, userID int64) ([]webauthn.Credential, error) {
|
||||
dbCreds, err := GetWebAuthnCredentialsByUID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dbCreds.ToCredentials(), nil
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
@ -60,24 +59,6 @@ func (branches BranchList) LoadPusher(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (branches BranchList) LoadRepo(ctx context.Context) error {
|
||||
ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) {
|
||||
return branch.RepoID, branch.RepoID > 0 && branch.Repo == nil
|
||||
})
|
||||
|
||||
reposMap := make(map[int64]*repo_model.Repository, len(ids))
|
||||
if err := db.GetEngine(ctx).In("id", ids).Find(&reposMap); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, branch := range branches {
|
||||
if branch.RepoID <= 0 || branch.Repo != nil {
|
||||
continue
|
||||
}
|
||||
branch.Repo = reposMap[branch.RepoID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FindBranchOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
|
||||
@ -89,12 +89,6 @@ type ErrUnknownDependencyType struct {
|
||||
Type DependencyType
|
||||
}
|
||||
|
||||
// IsErrUnknownDependencyType checks if an error is ErrUnknownDependencyType
|
||||
func IsErrUnknownDependencyType(err error) bool {
|
||||
_, ok := err.(ErrUnknownDependencyType)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUnknownDependencyType) Error() string {
|
||||
return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
|
||||
}
|
||||
|
||||
@ -48,21 +48,6 @@ func (err ErrIssueNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// ErrNewIssueInsert is used when the INSERT statement in newIssue fails
|
||||
type ErrNewIssueInsert struct {
|
||||
OriginalError error
|
||||
}
|
||||
|
||||
// IsErrNewIssueInsert checks if an error is a ErrNewIssueInsert.
|
||||
func IsErrNewIssueInsert(err error) bool {
|
||||
_, ok := err.(ErrNewIssueInsert)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNewIssueInsert) Error() string {
|
||||
return err.OriginalError.Error()
|
||||
}
|
||||
|
||||
var ErrIssueAlreadyChanged = util.NewInvalidArgumentErrorf("the issue is already changed")
|
||||
|
||||
// Issue represents an issue or pull request of repository.
|
||||
|
||||
@ -165,27 +165,6 @@ func MovePin(ctx context.Context, issue *Issue, newPosition int) error {
|
||||
})
|
||||
}
|
||||
|
||||
func GetPinnedIssueIDs(ctx context.Context, repoID int64, isPull bool) ([]int64, error) {
|
||||
var issuePins []IssuePin
|
||||
if err := db.GetEngine(ctx).
|
||||
Table("issue_pin").
|
||||
Where("repo_id = ?", repoID).
|
||||
And("is_pull = ?", isPull).
|
||||
Find(&issuePins); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Slice(issuePins, func(i, j int) bool {
|
||||
return issuePins[i].PinOrder < issuePins[j].PinOrder
|
||||
})
|
||||
|
||||
var ids []int64
|
||||
for _, pin := range issuePins {
|
||||
ids = append(ids, pin.IssueID)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func GetIssuePinsByRepoID(ctx context.Context, repoID int64, isPull bool) ([]*IssuePin, error) {
|
||||
var pins []*IssuePin
|
||||
if err := db.GetEngine(ctx).Where("repo_id = ? AND is_pull = ?", repoID, isPull).Find(&pins); err != nil {
|
||||
|
||||
@ -93,12 +93,6 @@ type ErrIssueIsOpen struct {
|
||||
Index int64
|
||||
}
|
||||
|
||||
// IsErrIssueIsOpen checks if an error is a ErrIssueIsOpen.
|
||||
func IsErrIssueIsOpen(err error) bool {
|
||||
_, ok := err.(ErrIssueIsOpen)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrIssueIsOpen) Error() string {
|
||||
return fmt.Sprintf("%s [id: %d, repo_id: %d, index: %d] is already open", util.Iif(err.IsPull, "Pull Request", "Issue"), err.ID, err.RepoID, err.Index)
|
||||
}
|
||||
@ -441,7 +435,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la
|
||||
LabelIDs: labelIDs,
|
||||
Attachments: uuids,
|
||||
}); err != nil {
|
||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
|
||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("newIssue: %w", err)
|
||||
|
||||
@ -475,7 +475,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss
|
||||
LabelIDs: labelIDs,
|
||||
Attachments: uuids,
|
||||
}); err != nil {
|
||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) {
|
||||
if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("newIssue: %w", err)
|
||||
|
||||
@ -28,19 +28,3 @@ func (t *TeamUnit) Unit() unit.Unit {
|
||||
func getUnitsByTeamID(ctx context.Context, teamID int64) (units []*TeamUnit, err error) {
|
||||
return units, db.GetEngine(ctx).Where("team_id = ?", teamID).Find(&units)
|
||||
}
|
||||
|
||||
// UpdateTeamUnits updates a teams's units
|
||||
func UpdateTeamUnits(ctx context.Context, team *Team, units []TeamUnit) (err error) {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if _, err = db.GetEngine(ctx).Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(units) > 0 {
|
||||
if err = db.Insert(ctx, units); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@ -36,14 +36,6 @@ type SearchMembersOptions struct {
|
||||
TeamID int64
|
||||
}
|
||||
|
||||
func (opts SearchMembersOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.TeamID > 0 {
|
||||
cond = cond.And(builder.Eq{"": opts.TeamID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
// GetTeamMembers returns all members in given team of organization.
|
||||
func GetTeamMembers(ctx context.Context, opts *SearchMembersOptions) ([]*user_model.User, error) {
|
||||
var members []*user_model.User
|
||||
|
||||
@ -337,20 +337,6 @@ func SetDefaultColumn(ctx context.Context, projectID, columnID int64) error {
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateColumnSorting update project column sorting
|
||||
func UpdateColumnSorting(ctx context.Context, cl ColumnList) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
for i := range cl {
|
||||
if _, err := db.GetEngine(ctx).ID(cl[i].ID).Cols(
|
||||
"sorting",
|
||||
).Update(cl[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (ColumnList, error) {
|
||||
columns := make([]*Column, 0, 5)
|
||||
if len(columnsIDs) == 0 {
|
||||
|
||||
@ -44,12 +44,6 @@ type ErrTopicNotExist struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// IsErrTopicNotExist checks if an error is an ErrTopicNotExist.
|
||||
func IsErrTopicNotExist(err error) bool {
|
||||
_, ok := err.(ErrTopicNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Error implements error interface
|
||||
func (err ErrTopicNotExist) Error() string {
|
||||
return fmt.Sprintf("topic is not exist [name: %s]", err.Name)
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// NoticeType describes the notice type
|
||||
@ -60,18 +59,6 @@ func CreateRepositoryNotice(desc string, args ...any) error {
|
||||
return CreateNotice(graceful.GetManager().ShutdownContext(), NoticeRepository, desc, args...)
|
||||
}
|
||||
|
||||
// RemoveAllWithNotice removes all directories in given path and
|
||||
// creates a system notice when error occurs.
|
||||
func RemoveAllWithNotice(ctx context.Context, title, path string) {
|
||||
if err := util.RemoveAll(path); err != nil {
|
||||
desc := fmt.Sprintf("%s [%s]: %v", title, path, err)
|
||||
log.Warn(title+" [%s]: %v", path, err)
|
||||
if err = CreateNotice(graceful.GetManager().ShutdownContext(), NoticeRepository, desc); err != nil {
|
||||
log.Error("CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveStorageWithNotice removes a file from the storage and
|
||||
// creates a system notice when error occurs.
|
||||
func RemoveStorageWithNotice(ctx context.Context, bucket storage.ObjectStorage, title, path string) {
|
||||
|
||||
@ -212,12 +212,6 @@ func RemoveUserBadges(ctx context.Context, u *User, badges []*Badge) error {
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAllUserBadges removes all badges from a user.
|
||||
func RemoveAllUserBadges(ctx context.Context, u *User) error {
|
||||
_, err := db.GetEngine(ctx).Where("user_id=?", u.ID).Delete(&UserBadge{})
|
||||
return err
|
||||
}
|
||||
|
||||
// SearchBadgeOptions represents the options when finding badges
|
||||
type SearchBadgeOptions struct {
|
||||
db.ListOptions
|
||||
@ -258,16 +252,3 @@ func (opts *SearchBadgeOptions) ToOrders() string {
|
||||
func SearchBadges(ctx context.Context, opts *SearchBadgeOptions) ([]*Badge, int64, error) {
|
||||
return db.FindAndCount[Badge](ctx, opts)
|
||||
}
|
||||
|
||||
// GetBadgeByID returns a specific badge by ID
|
||||
func GetBadgeByID(ctx context.Context, id int64) (*Badge, error) {
|
||||
badge := new(Badge)
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(badge)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, util.NewNotExistErrorf("badge does not exist [id: %d]", id)
|
||||
}
|
||||
return badge, nil
|
||||
}
|
||||
|
||||
@ -147,11 +147,6 @@ func InsertEmailAddress(ctx context.Context, email *EmailAddress) (*EmailAddress
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func UpdateEmailAddress(ctx context.Context, email *EmailAddress) error {
|
||||
_, err := db.GetEngine(ctx).ID(email.ID).AllCols().Update(email)
|
||||
return err
|
||||
}
|
||||
|
||||
// ValidateEmail check if email is a valid & allowed address
|
||||
func ValidateEmail(email string) error {
|
||||
if err := validateEmailBasic(email); err != nil {
|
||||
|
||||
@ -71,27 +71,6 @@ func (err ErrUserProhibitLogin) Unwrap() error {
|
||||
return util.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// ErrUserInactive represents a "ErrUserInactive" kind of error.
|
||||
type ErrUserInactive struct {
|
||||
UID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
// IsErrUserInactive checks if an error is a ErrUserInactive
|
||||
func IsErrUserInactive(err error) bool {
|
||||
_, ok := err.(ErrUserInactive)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserInactive) Error() string {
|
||||
return fmt.Sprintf("user is inactive [uid: %d, name: %s]", err.UID, err.Name)
|
||||
}
|
||||
|
||||
// Unwrap unwraps this error as a ErrPermission error
|
||||
func (err ErrUserInactive) Unwrap() error {
|
||||
return util.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// ErrUserIsNotLocal represents a "ErrUserIsNotLocal" kind of error.
|
||||
type ErrUserIsNotLocal struct {
|
||||
UID int64
|
||||
|
||||
@ -21,12 +21,6 @@ type ErrExternalLoginUserAlreadyExist struct {
|
||||
LoginSourceID int64
|
||||
}
|
||||
|
||||
// IsErrExternalLoginUserAlreadyExist checks if an error is a ExternalLoginUserAlreadyExist.
|
||||
func IsErrExternalLoginUserAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrExternalLoginUserAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrExternalLoginUserAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("external login user already exists [externalID: %s, userID: %d, loginSourceID: %d]", err.ExternalID, err.UserID, err.LoginSourceID)
|
||||
}
|
||||
@ -41,12 +35,6 @@ type ErrExternalLoginUserNotExist struct {
|
||||
LoginSourceID int64
|
||||
}
|
||||
|
||||
// IsErrExternalLoginUserNotExist checks if an error is a ExternalLoginUserNotExist.
|
||||
func IsErrExternalLoginUserNotExist(err error) bool {
|
||||
_, ok := err.(ErrExternalLoginUserNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrExternalLoginUserNotExist) Error() string {
|
||||
return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
|
||||
}
|
||||
|
||||
@ -50,12 +50,6 @@ func (err ErrUserSettingIsNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// IsErrUserSettingIsNotExist return true if err is ErrSettingIsNotExist
|
||||
func IsErrUserSettingIsNotExist(err error) bool {
|
||||
_, ok := err.(ErrUserSettingIsNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
// genSettingCacheKey returns the cache key for some configuration
|
||||
func genSettingCacheKey(userID int64, key string) string {
|
||||
return fmt.Sprintf("user_%d.setting.%s", userID, key)
|
||||
|
||||
@ -83,12 +83,6 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func WithJobResults(results map[string]string) ParseOption {
|
||||
return func(c *parseContext) {
|
||||
c.jobResults = results
|
||||
}
|
||||
}
|
||||
|
||||
func WithGitContext(context *model.GithubContext) ParseOption {
|
||||
return func(c *parseContext) {
|
||||
c.gitContext = context
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
@ -110,9 +109,3 @@ func (e *Event) WriteTo(w io.Writer) (int64, error) {
|
||||
|
||||
return sum, err
|
||||
}
|
||||
|
||||
func (e *Event) String() string {
|
||||
buf := new(strings.Builder)
|
||||
_, _ = e.WriteTo(buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
@ -44,23 +44,6 @@ func CommitsCount(ctx context.Context, repo Repository, opts CommitsCountOptions
|
||||
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
|
||||
}
|
||||
|
||||
// CommitsCountBetween return numbers of commits between two commits
|
||||
func CommitsCountBetween(ctx context.Context, repo Repository, start, end string) (int64, error) {
|
||||
count, err := CommitsCount(ctx, repo, CommitsCountOptions{
|
||||
Revision: []string{start + ".." + end},
|
||||
})
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "no merge base") {
|
||||
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
|
||||
// previously it would return the results of git rev-list before last so let's try that...
|
||||
return CommitsCount(ctx, repo, CommitsCountOptions{
|
||||
Revision: []string{start, end},
|
||||
})
|
||||
}
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
// FileCommitsCount return the number of files at a revision
|
||||
func FileCommitsCount(ctx context.Context, repo Repository, revision, file string) (int64, error) {
|
||||
return CommitsCount(ctx, repo,
|
||||
|
||||
@ -5,21 +5,11 @@ package gitrepo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/globallock"
|
||||
)
|
||||
|
||||
func GitConfigGet(ctx context.Context, repo Repository, key string) (string, error) {
|
||||
result, _, err := RunCmdString(ctx, repo, gitcmd.NewCommand("config", "--get").
|
||||
AddDynamicArguments(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(result), nil
|
||||
}
|
||||
|
||||
func getRepoConfigLockKey(repoStoragePath string) string {
|
||||
return "repo-config:" + repoStoragePath
|
||||
}
|
||||
|
||||
@ -78,11 +78,6 @@ func (hl *HostMatchList) AppendBuiltin(builtin string) {
|
||||
hl.builtins = append(hl.builtins, builtin)
|
||||
}
|
||||
|
||||
// AppendPattern appends more pattern to match
|
||||
func (hl *HostMatchList) AppendPattern(pattern string) {
|
||||
hl.patterns = append(hl.patterns, pattern)
|
||||
}
|
||||
|
||||
// IsEmpty checks if the checklist is empty
|
||||
func (hl *HostMatchList) IsEmpty() bool {
|
||||
return hl == nil || (len(hl.builtins) == 0 && len(hl.patterns) == 0 && len(hl.ipNets) == 0)
|
||||
|
||||
@ -74,10 +74,6 @@ func (r *GlodmarkRender) Convert(source []byte, writer io.Writer, opts ...parser
|
||||
return r.goldmarkMarkdown.Convert(source, writer, opts...)
|
||||
}
|
||||
|
||||
func (r *GlodmarkRender) Renderer() renderer.Renderer {
|
||||
return r.goldmarkMarkdown.Renderer()
|
||||
}
|
||||
|
||||
func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.CodeBlockContext, entering bool) {
|
||||
if entering {
|
||||
languageBytes, _ := c.Language()
|
||||
|
||||
@ -21,25 +21,6 @@ import (
|
||||
// ErrURLNotSupported represents url is not supported
|
||||
var ErrURLNotSupported = errors.New("url method not supported")
|
||||
|
||||
// ErrInvalidConfiguration is called when there is invalid configuration for a storage
|
||||
type ErrInvalidConfiguration struct {
|
||||
cfg any
|
||||
err error
|
||||
}
|
||||
|
||||
func (err ErrInvalidConfiguration) Error() string {
|
||||
if err.err != nil {
|
||||
return fmt.Sprintf("Invalid Configuration Argument: %v: Error: %v", err.cfg, err.err)
|
||||
}
|
||||
return fmt.Sprintf("Invalid Configuration Argument: %v", err.cfg)
|
||||
}
|
||||
|
||||
// IsErrInvalidConfiguration checks if an error is an ErrInvalidConfiguration
|
||||
func IsErrInvalidConfiguration(err error) bool {
|
||||
_, ok := err.(ErrInvalidConfiguration)
|
||||
return ok
|
||||
}
|
||||
|
||||
type Type = setting.StorageType
|
||||
|
||||
// NewStorageFunc is a function that creates a storage
|
||||
|
||||
@ -47,7 +47,7 @@ func ParseJSONError(buf []byte) (ret struct {
|
||||
}
|
||||
|
||||
func ParseJSONRedirect(buf []byte) (ret struct {
|
||||
Redirect string `json:"redirect"`
|
||||
Redirect *string `json:"redirect"`
|
||||
},
|
||||
) {
|
||||
_ = json.Unmarshal(buf, &ret)
|
||||
|
||||
@ -51,9 +51,3 @@ func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale {
|
||||
func SetLocaleCookie(resp http.ResponseWriter, lang string, maxAge int) {
|
||||
SetSiteCookie(resp, "lang", lang, maxAge)
|
||||
}
|
||||
|
||||
// DeleteLocaleCookie convenience function to delete the locale cookie consistently
|
||||
// Setting the lang cookie will trigger the middleware to reset the language to previous state.
|
||||
func DeleteLocaleCookie(resp http.ResponseWriter) {
|
||||
SetSiteCookie(resp, "lang", "", -1)
|
||||
}
|
||||
|
||||
@ -34,7 +34,6 @@
|
||||
"@replit/codemirror-lang-svelte": "6.0.0",
|
||||
"@replit/codemirror-vscode-keymap": "6.0.2",
|
||||
"@resvg/resvg-wasm": "2.6.2",
|
||||
"@silverwind/vue3-calendar-heatmap": "2.1.1",
|
||||
"@vitejs/plugin-vue": "6.0.6",
|
||||
"ansi_up": "6.0.6",
|
||||
"asciinema-player": "3.15.1",
|
||||
|
||||
@ -36,11 +36,5 @@ export default defineConfig({
|
||||
...devices['Desktop Firefox'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'webkit',
|
||||
use: {
|
||||
...devices['Desktop Safari'],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@ -112,9 +112,6 @@ importers:
|
||||
'@resvg/resvg-wasm':
|
||||
specifier: 2.6.2
|
||||
version: 2.6.2
|
||||
'@silverwind/vue3-calendar-heatmap':
|
||||
specifier: 2.1.1
|
||||
version: 2.1.1(tippy.js@6.3.7)(vue@3.5.32(typescript@6.0.2))
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: 6.0.6
|
||||
version: 6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1))(vue@3.5.32(typescript@6.0.2))
|
||||
@ -1249,13 +1246,6 @@ packages:
|
||||
'@scarf/scarf@1.4.0':
|
||||
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
|
||||
|
||||
'@silverwind/vue3-calendar-heatmap@2.1.1':
|
||||
resolution: {integrity: sha512-RQtLOpkysm0LR3PbUoc+aDcYxzy7xboygb1SQEwrUm2/XB2nmt0BEra2ADXpu4kwFxtk0+IyNwzFvbBai/wvTg==}
|
||||
engines: {node: '>=16'}
|
||||
peerDependencies:
|
||||
tippy.js: ^6.3.7
|
||||
vue: ^3.2.29
|
||||
|
||||
'@simonwep/pickr@1.9.0':
|
||||
resolution: {integrity: sha512-oEYvv15PyfZzjoAzvXYt3UyNGwzsrpFxLaZKzkOSd0WYBVwLd19iJerePDONxC1iF6+DpcswPdLIM2KzCJuYFg==}
|
||||
|
||||
@ -5052,11 +5042,6 @@ snapshots:
|
||||
|
||||
'@scarf/scarf@1.4.0': {}
|
||||
|
||||
'@silverwind/vue3-calendar-heatmap@2.1.1(tippy.js@6.3.7)(vue@3.5.32(typescript@6.0.2))':
|
||||
dependencies:
|
||||
tippy.js: 6.3.7
|
||||
vue: 3.5.32(typescript@6.0.2)
|
||||
|
||||
'@simonwep/pickr@1.9.0':
|
||||
dependencies:
|
||||
core-js: 3.32.2
|
||||
|
||||
@ -281,11 +281,7 @@ func DeleteGPGKey(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.PathParamInt64("id")); err != nil {
|
||||
if asymkey_model.IsErrGPGKeyAccessDenied(err) {
|
||||
ctx.APIError(http.StatusForbidden, "You do not have access to this key")
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -295,8 +291,6 @@ func DeleteGPGKey(ctx *context.APIContext) {
|
||||
// HandleAddGPGKeyError handle add GPGKey error
|
||||
func HandleAddGPGKeyError(ctx *context.APIContext, err error, token string) {
|
||||
switch {
|
||||
case asymkey_model.IsErrGPGKeyAccessDenied(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "You do not have access to this GPG key")
|
||||
case asymkey_model.IsErrGPGKeyIDAlreadyUsed(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "A key with the same id already exists")
|
||||
case asymkey_model.IsErrGPGKeyParsing(err):
|
||||
|
||||
@ -314,15 +314,6 @@ func SignInPost(ctx *context.Context) {
|
||||
log.Warn("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
|
||||
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
|
||||
ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
|
||||
} else if user_model.IsErrUserInactive(err) {
|
||||
if setting.Service.RegisterEmailConfirm {
|
||||
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
|
||||
ctx.HTML(http.StatusOK, TplActivate)
|
||||
} else {
|
||||
log.Warn("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err)
|
||||
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
|
||||
ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
|
||||
}
|
||||
} else {
|
||||
ctx.ServerError("UserSignIn", err)
|
||||
}
|
||||
|
||||
@ -105,16 +105,6 @@ func handleSignInError(ctx *context.Context, userName string, ptrForm any, tmpl
|
||||
log.Info("Failed authentication attempt for %s from %s: %v", userName, ctx.RemoteAddr(), err)
|
||||
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
|
||||
ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
|
||||
} else if user_model.IsErrUserInactive(err) {
|
||||
ctx.Data["user_exists"] = true
|
||||
if setting.Service.RegisterEmailConfirm {
|
||||
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
|
||||
ctx.HTML(http.StatusOK, TplActivate)
|
||||
} else {
|
||||
log.Info("Failed authentication attempt for %s from %s: %v", userName, ctx.RemoteAddr(), err)
|
||||
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
|
||||
ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
|
||||
}
|
||||
} else {
|
||||
ctx.ServerError(invoker, err)
|
||||
}
|
||||
|
||||
@ -109,9 +109,3 @@ func HomeSitemap(ctx *context.Context) {
|
||||
log.Error("Failed writing sitemap: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// NotFound render 404 page
|
||||
func NotFound(ctx *context.Context) {
|
||||
ctx.Data["Title"] = "Page Not Found"
|
||||
ctx.NotFound(nil)
|
||||
}
|
||||
|
||||
@ -35,14 +35,6 @@ const (
|
||||
tplProjectsView templates.TplName = "org/projects/view"
|
||||
)
|
||||
|
||||
// MustEnableProjects check if projects are enabled in settings
|
||||
func MustEnableProjects(ctx *context.Context) {
|
||||
if unit.TypeProjects.UnitGlobalDisabled() {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Projects renders the home page of projects
|
||||
func Projects(ctx *context.Context) {
|
||||
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
||||
|
||||
@ -725,16 +725,16 @@ func handleSettingsPostConvert(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.RepoSettingForm)
|
||||
repo := ctx.Repo.Repository
|
||||
if !ctx.Repo.IsOwner() {
|
||||
ctx.HTTPError(http.StatusNotFound)
|
||||
ctx.JSONErrorNotFound()
|
||||
return
|
||||
}
|
||||
if repo.Name != form.RepoName {
|
||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
|
||||
return
|
||||
}
|
||||
|
||||
if !repo.IsMirror {
|
||||
ctx.HTTPError(http.StatusNotFound)
|
||||
ctx.JSONErrorNotFound()
|
||||
return
|
||||
}
|
||||
repo.IsMirror = false
|
||||
@ -748,14 +748,14 @@ func handleSettingsPostConvert(ctx *context.Context) {
|
||||
}
|
||||
log.Trace("Repository converted from mirror to regular: %s", repo.FullName())
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed"))
|
||||
ctx.Redirect(repo.Link())
|
||||
ctx.JSONRedirect(repo.Link())
|
||||
}
|
||||
|
||||
func handleSettingsPostConvertFork(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.RepoSettingForm)
|
||||
repo := ctx.Repo.Repository
|
||||
if !ctx.Repo.IsOwner() {
|
||||
ctx.HTTPError(http.StatusNotFound)
|
||||
ctx.JSONErrorNotFound()
|
||||
return
|
||||
}
|
||||
if err := repo.LoadOwner(ctx); err != nil {
|
||||
@ -763,12 +763,12 @@ func handleSettingsPostConvertFork(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
if repo.Name != form.RepoName {
|
||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
|
||||
return
|
||||
}
|
||||
|
||||
if !repo.IsFork {
|
||||
ctx.HTTPError(http.StatusNotFound)
|
||||
ctx.JSONErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
@ -776,7 +776,7 @@ func handleSettingsPostConvertFork(ctx *context.Context) {
|
||||
maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit()
|
||||
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
|
||||
ctx.Flash.Error(msg)
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
ctx.JSONRedirect(repo.Link() + "/settings")
|
||||
return
|
||||
}
|
||||
|
||||
@ -788,25 +788,25 @@ func handleSettingsPostConvertFork(ctx *context.Context) {
|
||||
|
||||
log.Trace("Repository converted from fork to regular: %s", repo.FullName())
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.convert_fork_succeed"))
|
||||
ctx.Redirect(repo.Link())
|
||||
ctx.JSONRedirect(repo.Link())
|
||||
}
|
||||
|
||||
func handleSettingsPostTransfer(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.RepoSettingForm)
|
||||
repo := ctx.Repo.Repository
|
||||
if !ctx.Repo.IsOwner() {
|
||||
ctx.HTTPError(http.StatusNotFound)
|
||||
ctx.JSONErrorNotFound()
|
||||
return
|
||||
}
|
||||
if repo.Name != form.RepoName {
|
||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
|
||||
return
|
||||
}
|
||||
|
||||
newOwner, err := user_model.GetUserByName(ctx, ctx.FormString("new_owner_name"))
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.Tr("form.enterred_invalid_owner_name"))
|
||||
return
|
||||
}
|
||||
ctx.ServerError("IsUserExist", err)
|
||||
@ -816,7 +816,7 @@ func handleSettingsPostTransfer(ctx *context.Context) {
|
||||
if newOwner.Type == user_model.UserTypeOrganization {
|
||||
if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx, ctx.Doer.ID) {
|
||||
// The user shouldn't know about this organization
|
||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.Tr("form.enterred_invalid_owner_name"))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -830,14 +830,14 @@ func handleSettingsPostTransfer(ctx *context.Context) {
|
||||
oldFullname := repo.FullName()
|
||||
if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil {
|
||||
if repo_model.IsErrRepoAlreadyExist(err) {
|
||||
ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.Tr("repo.settings.new_owner_has_same_repo"))
|
||||
} else if repo_model.IsErrRepoTransferInProgress(err) {
|
||||
ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.Tr("repo.settings.transfer_in_progress"))
|
||||
} else if repo_service.IsRepositoryLimitReached(err) {
|
||||
limit := err.(repo_service.LimitReachedError).Limit
|
||||
ctx.RenderWithErrDeprecated(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit))
|
||||
} else if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.Tr("repo.settings.transfer.blocked_user"))
|
||||
} else {
|
||||
ctx.ServerError("TransferOwnership", err)
|
||||
}
|
||||
@ -852,7 +852,7 @@ func handleSettingsPostTransfer(ctx *context.Context) {
|
||||
log.Trace("Repository transferred: %s -> %s", oldFullname, ctx.Repo.Repository.FullName())
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.transfer_succeed"))
|
||||
}
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
ctx.JSONRedirect(repo.Link() + "/settings")
|
||||
}
|
||||
|
||||
func handleSettingsPostCancelTransfer(ctx *context.Context) {
|
||||
@ -887,11 +887,11 @@ func handleSettingsPostDelete(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.RepoSettingForm)
|
||||
repo := ctx.Repo.Repository
|
||||
if !ctx.Repo.IsOwner() {
|
||||
ctx.HTTPError(http.StatusNotFound)
|
||||
ctx.JSONErrorNotFound()
|
||||
return
|
||||
}
|
||||
if repo.Name != form.RepoName {
|
||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
|
||||
return
|
||||
}
|
||||
|
||||
@ -907,18 +907,18 @@ func handleSettingsPostDelete(ctx *context.Context) {
|
||||
log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
|
||||
ctx.Redirect(ctx.Repo.Owner.DashboardLink())
|
||||
ctx.JSONRedirect(ctx.Repo.Owner.DashboardLink())
|
||||
}
|
||||
|
||||
func handleSettingsPostDeleteWiki(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.RepoSettingForm)
|
||||
repo := ctx.Repo.Repository
|
||||
if !ctx.Repo.IsOwner() {
|
||||
ctx.HTTPError(http.StatusNotFound)
|
||||
ctx.JSONErrorNotFound()
|
||||
return
|
||||
}
|
||||
if repo.Name != form.RepoName {
|
||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
|
||||
ctx.JSONError(ctx.Tr("form.enterred_invalid_repo_name"))
|
||||
return
|
||||
}
|
||||
|
||||
@ -929,7 +929,7 @@ func handleSettingsPostDeleteWiki(ctx *context.Context) {
|
||||
log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
|
||||
ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings")
|
||||
}
|
||||
|
||||
func handleSettingsPostArchive(ctx *context.Context) {
|
||||
|
||||
@ -362,10 +362,6 @@ func RunnerUpdatePost(ctx *context.Context) {
|
||||
ctx.JSONRedirect("")
|
||||
}
|
||||
|
||||
func RedirectToDefaultSetting(ctx *context.Context) {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/actions/runners")
|
||||
}
|
||||
|
||||
func findActionsRunner(ctx *context.Context, rCtx *runnersCtx) *actions_model.ActionRunner {
|
||||
runnerID := ctx.PathParamInt64("runnerid")
|
||||
opts := &actions_model.FindRunnerOptions{
|
||||
|
||||
@ -30,27 +30,6 @@ func UserAssignmentWeb() func(ctx *Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// UserIDAssignmentAPI returns a middleware to handle context-user assignment for api routes
|
||||
func UserIDAssignmentAPI() func(ctx *APIContext) {
|
||||
return func(ctx *APIContext) {
|
||||
userID := ctx.PathParamInt64("user-id")
|
||||
|
||||
if ctx.IsSigned && ctx.Doer.ID == userID {
|
||||
ctx.ContextUser = ctx.Doer
|
||||
} else {
|
||||
var err error
|
||||
ctx.ContextUser, err = user_model.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UserAssignmentAPI returns a middleware to handle context-user assignment for api routes
|
||||
func UserAssignmentAPI() func(ctx *APIContext) {
|
||||
return func(ctx *APIContext) {
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
secret_model "code.gitea.io/gitea/models/secret"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
// ToSecret converts Secret to API format
|
||||
func ToSecret(secret *secret_model.Secret) *api.Secret {
|
||||
result := &api.Secret{
|
||||
Name: secret.Name,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@ -740,14 +740,3 @@ func (f *AddTimeManuallyForm) Validate(req *http.Request, errs binding.Errors) b
|
||||
type SaveTopicForm struct {
|
||||
Topics []string `binding:"topics;Required;"`
|
||||
}
|
||||
|
||||
// DeadlineForm hold the validation rules for deadlines
|
||||
type DeadlineForm struct {
|
||||
DateString string `form:"date" binding:"Required;Size(10)"`
|
||||
}
|
||||
|
||||
// Validate validates the fields
|
||||
func (f *DeadlineForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
|
||||
ctx := context.GetValidateContext(req)
|
||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
@ -32,11 +32,6 @@ const (
|
||||
ContentTypeSubmodule ContentType = "submodule" // submodule content type (submodule)
|
||||
)
|
||||
|
||||
// String gets the string of ContentType
|
||||
func (ct *ContentType) String() string {
|
||||
return string(*ct)
|
||||
}
|
||||
|
||||
type GetContentsOrListOptions struct {
|
||||
TreePath string
|
||||
IncludeSingleFileContent bool // include the file's content when the tree path is a file
|
||||
|
||||
@ -26,12 +26,6 @@ type ErrSHANotFound struct {
|
||||
SHA string
|
||||
}
|
||||
|
||||
// IsErrSHANotFound checks if an error is a ErrSHANotFound.
|
||||
func IsErrSHANotFound(err error) bool {
|
||||
_, ok := err.(ErrSHANotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrSHANotFound) Error() string {
|
||||
return fmt.Sprintf("sha not found [%s]", err.SHA)
|
||||
}
|
||||
|
||||
@ -8,13 +8,19 @@ description: |
|
||||
|
||||
icon: public/assets/img/logo.png
|
||||
confinement: strict
|
||||
base: core22
|
||||
base: core24
|
||||
adopt-info: gitea
|
||||
|
||||
architectures:
|
||||
- build-on: armhf
|
||||
- build-on: amd64
|
||||
- build-on: arm64
|
||||
platforms:
|
||||
armhf:
|
||||
build-on: [armhf]
|
||||
build-for: [armhf]
|
||||
amd64:
|
||||
build-on: [amd64]
|
||||
build-for: [amd64]
|
||||
arm64:
|
||||
build-on: [arm64]
|
||||
build-for: [arm64]
|
||||
|
||||
environment:
|
||||
GITEA_CUSTOM: "$SNAP_COMMON"
|
||||
|
||||
@ -885,7 +885,7 @@
|
||||
<div class="ui warning message">
|
||||
{{ctx.Locale.Tr "repo.settings.convert_notices_1"}}
|
||||
</div>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
|
||||
<input type="hidden" name="action" value="convert">
|
||||
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.convert_confirm"))}}
|
||||
@ -902,7 +902,7 @@
|
||||
<div class="ui warning message">
|
||||
{{ctx.Locale.Tr "repo.settings.convert_fork_notices_1"}}
|
||||
</div>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
|
||||
<input type="hidden" name="action" value="convert_fork">
|
||||
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.convert_fork_confirm"))}}
|
||||
@ -921,7 +921,7 @@
|
||||
{{ctx.Locale.Tr "repo.settings.transfer_notices_3"}} <br>
|
||||
{{ctx.Locale.Tr "repo.settings.transfer_notices_4"}}
|
||||
</div>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
|
||||
<input type="hidden" name="action" value="transfer">
|
||||
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
|
||||
<div class="required field">
|
||||
@ -946,7 +946,7 @@
|
||||
{{ctx.Locale.Tr "repo.settings.delete_notices_fork_1"}}
|
||||
{{end}}
|
||||
</div>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.confirm_delete"))}}
|
||||
@ -1012,7 +1012,7 @@
|
||||
{{ctx.Locale.Tr "repo.settings.delete_notices_1"}}<br>
|
||||
{{ctx.Locale.Tr "repo.settings.wiki_delete_notices_1" .Repository.Name}}
|
||||
</div>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
|
||||
<input type="hidden" name="action" value="delete-wiki">
|
||||
{{template "repo/settings/repo_name_confirm_fields" (dict "RepoName" .Repository.Name)}}
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonDangerText" (ctx.Locale.Tr "repo.settings.confirm_wiki_delete"))}}
|
||||
|
||||
9
tests/e2e/heatmap.test.ts
Normal file
9
tests/e2e/heatmap.test.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {test, expect} from '@playwright/test';
|
||||
import {login} from './utils.ts';
|
||||
|
||||
test('heatmap tooltip shows on hover', async ({page}) => {
|
||||
await login(page);
|
||||
await page.goto('/');
|
||||
await page.locator('.heatmap-day').first().hover();
|
||||
await expect(page.locator('.tippy-box[data-state="visible"]')).toBeVisible();
|
||||
});
|
||||
@ -372,7 +372,8 @@ func testForkToEditFile(t *testing.T, session *TestSession, user, owner, repo, b
|
||||
"action": "convert_fork",
|
||||
},
|
||||
)
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.NotNil(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
|
||||
})
|
||||
|
||||
// Fork repository again, and check the existence of the forked repo with unique name
|
||||
|
||||
@ -189,7 +189,8 @@ func testDeleteRepository(t *testing.T, session *TestSession, ownerName, repoNam
|
||||
req := NewRequestWithValues(t, "POST", relURL+"?action=delete", map[string]string{
|
||||
"repo_name": repoName,
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.NotNil(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
|
||||
}
|
||||
|
||||
func TestPullBranchDelete(t *testing.T) {
|
||||
|
||||
@ -39,7 +39,7 @@ func TestRepositoryVisibilityChange(t *testing.T) {
|
||||
"confirm_repo_name": "user2/repo1",
|
||||
})
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
|
||||
assert.NotNil(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
|
||||
|
||||
repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
assert.True(t, repo1.IsPrivate)
|
||||
@ -51,7 +51,7 @@ func TestRepositoryVisibilityChange(t *testing.T) {
|
||||
"private": "false",
|
||||
})
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
|
||||
assert.NotNil(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect)
|
||||
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
assert.False(t, repo2.IsPrivate)
|
||||
|
||||
@ -35,8 +35,6 @@
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"stripInternal": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"types": [
|
||||
|
||||
@ -86,7 +86,7 @@ function iifeBuildOpts({sourceFileName, write}: {sourceFileName: string, write?:
|
||||
}
|
||||
|
||||
// Build iife.js as a blocking IIFE bundle. In dev mode, serves it from memory
|
||||
// and rebuilds on file changes. In prod mode, writes to disk during closeBundle.
|
||||
// and rebuilds on file changes. In prod mode, writes to disk and updates "manifest.json".
|
||||
function iifePlugin(sourceFileName: string): Plugin {
|
||||
let iifeCode = '', iifeMap = '';
|
||||
const iifeModules = new Set<string>();
|
||||
@ -143,7 +143,7 @@ function iifePlugin(sourceFileName: string): Plugin {
|
||||
}
|
||||
});
|
||||
},
|
||||
async closeBundle() {
|
||||
async writeBundle() {
|
||||
for (const file of globSync(`js/${sourceBaseName}.*.js*`, {cwd: outDir})) unlinkSync(join(outDir, file));
|
||||
|
||||
const result = await build(iifeBuildOpts({sourceFileName}));
|
||||
@ -152,15 +152,9 @@ function iifePlugin(sourceFileName: string): Plugin {
|
||||
if (!entry) throw new Error('IIFE build produced no output');
|
||||
|
||||
const manifestPath = join(outDir, '.vite', 'manifest.json');
|
||||
try {
|
||||
const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
||||
manifestData[`web_src/js/${sourceFileName}`] = {file: entry.fileName, name: sourceBaseName, isEntry: true};
|
||||
writeFileSync(manifestPath, JSON.stringify(manifestData, null, 2));
|
||||
} catch {
|
||||
// FIXME: if it throws error here, the real Vite compilation error will be hidden, and makes the debug very difficult
|
||||
// Need to find a correct way to handle errors.
|
||||
console.error(`Failed to update manifest for ${sourceFileName}`);
|
||||
}
|
||||
const manifestData = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
||||
manifestData[`web_src/js/${sourceFileName}`] = {file: entry.fileName, name: sourceBaseName, isEntry: true};
|
||||
writeFileSync(manifestPath, JSON.stringify(manifestData, null, 2));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -32,26 +32,29 @@
|
||||
fill: currentcolor !important;
|
||||
}
|
||||
|
||||
/* root legend */
|
||||
#user-heatmap .vch__container > .vch__legend {
|
||||
#user-heatmap .heatmap-footer {
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* for the "Less" and "More" legend */
|
||||
#user-heatmap .vch__legend .vch__legend {
|
||||
/* "Less [colors] More" scale */
|
||||
#user-heatmap .heatmap-legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
#user-heatmap .vch__legend .vch__legend div:first-child,
|
||||
#user-heatmap .vch__legend .vch__legend div:last-child {
|
||||
#user-heatmap .heatmap-legend-svg {
|
||||
margin-right: -12px;
|
||||
}
|
||||
|
||||
#user-heatmap .heatmap-legend > div:first-child,
|
||||
#user-heatmap .heatmap-legend > div:last-child {
|
||||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
#user-heatmap .vch__day__square:hover {
|
||||
#user-heatmap .heatmap-day:hover {
|
||||
outline: 1.5px solid var(--color-text);
|
||||
}
|
||||
|
||||
@ -1,21 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
// TODO: Switch to upstream after https://github.com/razorness/vue3-calendar-heatmap/pull/34 is merged
|
||||
import {CalendarHeatmap} from '@silverwind/vue3-calendar-heatmap';
|
||||
import {onMounted, shallowRef} from 'vue';
|
||||
import type {Value as HeatmapValue, Locale as HeatmapLocale} from '@silverwind/vue3-calendar-heatmap';
|
||||
import {computed, onBeforeUnmount, onMounted} from 'vue';
|
||||
import tippy, {createSingleton} from 'tippy.js';
|
||||
import type {CreateSingletonInstance, Instance} from 'tippy.js';
|
||||
|
||||
defineProps<{
|
||||
type HeatmapValue = {date: Date; count: number};
|
||||
type HeatmapCell = {date: Date; colorIndex: number; ariaLabel: string; tooltip: string};
|
||||
type MonthLabel = {monthIdx: number; weekIdx: number};
|
||||
|
||||
const props = defineProps<{
|
||||
values: HeatmapValue[];
|
||||
locale: {
|
||||
textTotalContributions: string;
|
||||
heatMapLocale: Partial<HeatmapLocale>;
|
||||
heatMapLocale: {months: string[]; days: string[]; on: string; more: string; less: string};
|
||||
noDataText: string;
|
||||
tooltipUnit: string;
|
||||
};
|
||||
}>();
|
||||
|
||||
const colorRange = [
|
||||
'var(--color-secondary-alpha-60)',
|
||||
'var(--color-secondary-alpha-60)',
|
||||
'var(--color-primary-light-4)',
|
||||
'var(--color-primary-light-2)',
|
||||
@ -24,21 +26,112 @@ const colorRange = [
|
||||
'var(--color-primary-dark-4)',
|
||||
];
|
||||
|
||||
const endDate = shallowRef(new Date());
|
||||
const squareSize = 10;
|
||||
const squareBorder = 2;
|
||||
const cellSize = squareSize + squareBorder;
|
||||
const daysInWeek = 7;
|
||||
const trailingDays = 365;
|
||||
const gridLeft = Math.ceil(squareSize * 2.5);
|
||||
const gridTop = squareSize + squareSize / 2;
|
||||
|
||||
onMounted(() => {
|
||||
// work around issue with first legend color being rendered twice and legend cut off
|
||||
const legend = document.querySelector<HTMLElement>('.vch__external-legend-wrapper')!;
|
||||
legend.setAttribute('viewBox', '12 0 80 10');
|
||||
legend.style.marginRight = '-12px';
|
||||
const now = new Date();
|
||||
|
||||
function dateKey(d: Date): string {
|
||||
return `${d.getFullYear()}${String(d.getMonth()).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function shiftDate(d: Date, days: number): Date {
|
||||
const out = new Date(d);
|
||||
out.setDate(out.getDate() + days);
|
||||
return out;
|
||||
}
|
||||
|
||||
const grid = computed(() => {
|
||||
const start = shiftDate(now, -trailingDays);
|
||||
const padStart = start.getDay();
|
||||
const padEnd = daysInWeek - 1 - now.getDay();
|
||||
const weekCount = (trailingDays + 1 + padStart + padEnd) / daysInWeek;
|
||||
|
||||
const maxCount = props.values.length ? Math.max(...props.values.map((v) => v.count)) : 0;
|
||||
const max = maxCount > 0 ? Math.ceil(maxCount / 5 * 4) : 1;
|
||||
|
||||
const activities = new Map<string, {count: number; colorIndex: number}>();
|
||||
for (const {date, count} of props.values) {
|
||||
const colorIndex = count >= max ? 4 : Math.max(1, Math.ceil((count / max) * 3));
|
||||
activities.set(dateKey(date), {count, colorIndex});
|
||||
}
|
||||
|
||||
const {months, on} = props.locale.heatMapLocale;
|
||||
const {noDataText, tooltipUnit} = props.locale;
|
||||
|
||||
const cursorStart = shiftDate(start, -padStart);
|
||||
const cursor = new Date(cursorStart.getFullYear(), cursorStart.getMonth(), cursorStart.getDate());
|
||||
const calendar: HeatmapCell[][] = [];
|
||||
for (let w = 0; w < weekCount; w++) {
|
||||
const week: HeatmapCell[] = [];
|
||||
for (let d = 0; d < daysInWeek; d++) {
|
||||
const hit = activities.get(dateKey(cursor));
|
||||
const dateStr = `${months[cursor.getMonth()]} ${cursor.getDate()}, ${cursor.getFullYear()}`;
|
||||
const head = hit ? `${hit.count} ${tooltipUnit}` : noDataText;
|
||||
week.push({
|
||||
date: new Date(cursor),
|
||||
colorIndex: hit ? hit.colorIndex : 0,
|
||||
ariaLabel: `${head} ${on} ${dateStr}`,
|
||||
tooltip: `<b>${head}</b> ${on} ${dateStr}`,
|
||||
});
|
||||
cursor.setDate(cursor.getDate() + 1);
|
||||
}
|
||||
calendar.push(week);
|
||||
}
|
||||
|
||||
const monthLabels: MonthLabel[] = [];
|
||||
for (let w = 1; w < calendar.length; w++) {
|
||||
const prev = calendar[w - 1][0].date;
|
||||
const curr = calendar[w][0].date;
|
||||
if (prev.getMonth() !== curr.getMonth()) {
|
||||
monthLabels.push({monthIdx: curr.getMonth(), weekIdx: w});
|
||||
}
|
||||
}
|
||||
|
||||
const width = gridLeft + (cellSize * weekCount) + squareBorder;
|
||||
const height = gridTop + (cellSize * daysInWeek);
|
||||
return {calendar, monthLabels, width, height};
|
||||
});
|
||||
|
||||
function handleDayClick(e: Event & {date: Date}) {
|
||||
// Reset filter if same date is clicked
|
||||
const legendViewBox = `${cellSize} 0 ${squareSize * (colorRange.length + 2)} ${squareSize}`;
|
||||
|
||||
const cellInstances = new Map<Element, Instance>();
|
||||
let singleton: CreateSingletonInstance | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
singleton = createSingleton([], {
|
||||
overrides: [],
|
||||
moveTransition: 'transform 0.1s ease-out',
|
||||
allowHTML: true,
|
||||
theme: 'tooltip',
|
||||
role: 'tooltip',
|
||||
placement: 'top',
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
singleton?.destroy();
|
||||
for (const instance of cellInstances.values()) instance.destroy();
|
||||
cellInstances.clear();
|
||||
});
|
||||
|
||||
function lazyInitTooltip(e: MouseEvent) {
|
||||
const el = e.target as Element;
|
||||
if (!singleton || cellInstances.has(el) || !el.classList.contains('heatmap-day')) return;
|
||||
cellInstances.set(el, tippy(el, {content: el.getAttribute('data-tooltip')!}));
|
||||
singleton.setInstances([...cellInstances.values()]);
|
||||
}
|
||||
|
||||
function handleDayClick(date: Date) {
|
||||
const params = new URLSearchParams(document.location.search);
|
||||
const queryDate = params.get('date');
|
||||
// Timezone has to be stripped because toISOString() converts to UTC
|
||||
const clickedDate = new Date(e.date.getTime() - (e.date.getTimezoneOffset() * 60000)).toISOString().substring(0, 10);
|
||||
const clickedDate = new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().substring(0, 10);
|
||||
|
||||
if (queryDate && queryDate === clickedDate) {
|
||||
params.delete('date');
|
||||
@ -53,16 +146,63 @@ function handleDayClick(e: Event & {date: Date}) {
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<calendar-heatmap
|
||||
:locale="locale.heatMapLocale"
|
||||
:no-data-text="locale.noDataText"
|
||||
:tooltip-unit="locale.tooltipUnit"
|
||||
:end-date="endDate"
|
||||
:values="values"
|
||||
:range-color="colorRange"
|
||||
@day-click="handleDayClick($event)"
|
||||
:tippy-props="{theme: 'tooltip'}"
|
||||
>
|
||||
<template #vch__legend-left>{{ locale.textTotalContributions }}</template>
|
||||
</calendar-heatmap>
|
||||
<div>
|
||||
<svg class="heatmap-svg" :viewBox="`0 0 ${grid.width} ${grid.height}`">
|
||||
<g class="heatmap-month-labels" :transform="`translate(${gridLeft}, 0)`">
|
||||
<text
|
||||
v-for="m in grid.monthLabels"
|
||||
:key="m.weekIdx"
|
||||
class="heatmap-month-label"
|
||||
:x="cellSize * m.weekIdx"
|
||||
:y="cellSize - squareBorder"
|
||||
>
|
||||
{{ locale.heatMapLocale.months[m.monthIdx] }}
|
||||
</text>
|
||||
</g>
|
||||
<g class="heatmap-day-labels" :transform="`translate(0, ${gridTop})`">
|
||||
<text class="heatmap-day-label" :x="0" :y="20">{{ locale.heatMapLocale.days[1] }}</text>
|
||||
<text class="heatmap-day-label" :x="0" :y="44">{{ locale.heatMapLocale.days[3] }}</text>
|
||||
<text class="heatmap-day-label" :x="0" :y="69">{{ locale.heatMapLocale.days[5] }}</text>
|
||||
</g>
|
||||
<g class="heatmap-grid" :transform="`translate(${gridLeft}, ${gridTop})`" @mouseover="lazyInitTooltip">
|
||||
<g
|
||||
v-for="(week, w) in grid.calendar"
|
||||
:key="w"
|
||||
class="heatmap-week"
|
||||
:transform="`translate(${w * cellSize}, 0)`"
|
||||
>
|
||||
<template v-for="(day, d) in week" :key="d">
|
||||
<rect
|
||||
v-if="day.date < now"
|
||||
class="heatmap-day"
|
||||
:transform="`translate(0, ${d * cellSize})`"
|
||||
:width="squareSize"
|
||||
:height="squareSize"
|
||||
:style="{fill: colorRange[day.colorIndex]}"
|
||||
:aria-label="day.ariaLabel"
|
||||
:data-tooltip="day.tooltip"
|
||||
@click="handleDayClick(day.date)"
|
||||
/>
|
||||
</template>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="heatmap-footer">
|
||||
<div>{{ locale.textTotalContributions }}</div>
|
||||
<div class="heatmap-legend">
|
||||
<div>{{ locale.heatMapLocale.less }}</div>
|
||||
<svg class="heatmap-legend-svg" :viewBox="legendViewBox" :height="squareSize">
|
||||
<rect
|
||||
v-for="(color, i) in colorRange"
|
||||
:key="i"
|
||||
:width="squareSize"
|
||||
:height="squareSize"
|
||||
:x="(i + 1) * cellSize"
|
||||
:style="{fill: color}"
|
||||
/>
|
||||
</svg>
|
||||
<div>{{ locale.heatMapLocale.more }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
type DayDataObject,
|
||||
} from '../utils/time.ts';
|
||||
import {chartJsColors} from '../utils/color.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {sleep} from '../utils.ts';
|
||||
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
|
||||
import {onMounted, shallowRef} from 'vue';
|
||||
@ -78,7 +79,7 @@ async function fetchGraphData() {
|
||||
errorText.value = response.statusText;
|
||||
}
|
||||
} catch (err) {
|
||||
errorText.value = err.message;
|
||||
errorText.value = errorMessage(err);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
fillEmptyStartDaysWithZeroes,
|
||||
} from '../utils/time.ts';
|
||||
import {chartJsColors} from '../utils/color.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {sleep} from '../utils.ts';
|
||||
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
@ -166,7 +167,7 @@ export default defineComponent({
|
||||
this.errorText = response.statusText;
|
||||
}
|
||||
} catch (err) {
|
||||
this.errorText = err.message;
|
||||
this.errorText = errorMessage(err);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
type DayDataObject,
|
||||
} from '../utils/time.ts';
|
||||
import {chartJsColors} from '../utils/color.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {sleep} from '../utils.ts';
|
||||
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
|
||||
import {onMounted, ref, shallowRef} from 'vue';
|
||||
@ -74,7 +75,7 @@ async function fetchGraphData() {
|
||||
errorText.value = response.statusText;
|
||||
}
|
||||
} catch (err) {
|
||||
errorText.value = err.message;
|
||||
errorText.value = errorMessage(err);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import {showTemporaryTooltip} from '../../modules/tippy.ts';
|
||||
import {POST} from '../../modules/fetch.ts';
|
||||
import {registerGlobalInitFunc} from '../../modules/observer.ts';
|
||||
import {queryElems} from '../../utils/dom.ts';
|
||||
import {errorMessage} from '../../modules/errors.ts';
|
||||
import {submitFormFetchAction} from '../common-fetch-action.ts';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
@ -26,7 +27,7 @@ function initSystemConfigAutoCheckbox(el: HTMLInputElement) {
|
||||
const json: Record<string, any> = await resp.json();
|
||||
if (json.errorMessage) throw new Error(json.errorMessage);
|
||||
} catch (ex) {
|
||||
showTemporaryTooltip(el, ex.toString());
|
||||
showTemporaryTooltip(el, errorMessage(ex));
|
||||
el.checked = !el.checked;
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {getCurrentLocale} from '../utils.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import {localUserSettings} from '../modules/user-settings.ts';
|
||||
|
||||
@ -46,7 +47,7 @@ export async function initCitationFileCopyContent() {
|
||||
try {
|
||||
await initInputCitationValue(citationCopyApa, citationCopyBibtex);
|
||||
} catch (e) {
|
||||
console.error(`initCitationFileCopyContent error: ${e}`, e);
|
||||
console.error(`initCitationFileCopyContent error: ${errorMessage(e)}`, e);
|
||||
return;
|
||||
}
|
||||
updateUi();
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {GET, request} from '../modules/fetch.ts';
|
||||
import {hideToastsAll, showErrorToast} from '../modules/toast.ts';
|
||||
import {addDelegatedEventListener, createElementFromHTML} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {confirmModal, createConfirmModal} from './comp/ConfirmModal.ts';
|
||||
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
import {registerGlobalSelectorFunc} from '../modules/observer.ts';
|
||||
@ -90,7 +91,7 @@ async function handleFetchActionSuccess(el: HTMLElement, opt: FetchActionOpts, r
|
||||
async function handleFetchActionError(resp: Response) {
|
||||
const isRespJson = resp.headers.get('content-type')?.includes('application/json');
|
||||
const respText = await resp.text();
|
||||
const respJson = isRespJson ? JSON.parse(await resp.text()) : null;
|
||||
const respJson = isRespJson ? JSON.parse(respText) : null;
|
||||
if (respJson?.errorMessage) {
|
||||
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
|
||||
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
|
||||
@ -134,10 +135,10 @@ async function performActionRequest(el: HTMLElement, opt: FetchActionOpts) {
|
||||
return;
|
||||
}
|
||||
await handleFetchActionError(resp);
|
||||
} catch (e) {
|
||||
if (e.name !== 'AbortError') {
|
||||
console.error(`Fetch action request error:`, e);
|
||||
showErrorToast(`Error: ${e.message ?? e}`);
|
||||
} catch (err) {
|
||||
if ((err as Error).name !== 'AbortError') {
|
||||
console.error(`Fetch action request error:`, err);
|
||||
showErrorToast(`Error: ${errorMessage(err)}`);
|
||||
}
|
||||
} finally {
|
||||
toggleLoadingIndicator(el, opt, false);
|
||||
|
||||
@ -71,26 +71,26 @@ export class ComboMarkdownEditor {
|
||||
|
||||
options: ComboMarkdownEditorOptions;
|
||||
|
||||
tabEditor: HTMLElement;
|
||||
tabPreviewer: HTMLElement;
|
||||
tabEditor?: HTMLElement;
|
||||
tabPreviewer?: HTMLElement;
|
||||
|
||||
supportEasyMDE: boolean;
|
||||
supportEasyMDE!: boolean;
|
||||
easyMDE: any;
|
||||
easyMDEToolbarActions: any;
|
||||
easyMDEToolbarDefault: any;
|
||||
|
||||
textarea: ComboMarkdownEditorTextarea;
|
||||
textareaMarkdownToolbar: HTMLElement;
|
||||
textarea!: ComboMarkdownEditorTextarea;
|
||||
textareaMarkdownToolbar!: HTMLElement;
|
||||
textareaAutosize: any;
|
||||
|
||||
buttonMonospace: HTMLButtonElement;
|
||||
buttonMonospace!: HTMLButtonElement;
|
||||
|
||||
dropzone: HTMLElement | null;
|
||||
dropzone: HTMLElement | null = null;
|
||||
attachedDropzoneInst: any;
|
||||
|
||||
previewMode: string;
|
||||
previewUrl: string;
|
||||
previewContext: string;
|
||||
previewMode!: string;
|
||||
previewUrl!: string;
|
||||
previewContext!: string;
|
||||
|
||||
constructor(container: ComboMarkdownEditorContainer, options:ComboMarkdownEditorOptions = {}) {
|
||||
if (container._giteaComboMarkdownEditor) throw new Error('ComboMarkdownEditor already initialized');
|
||||
@ -291,7 +291,7 @@ export class ComboMarkdownEditor {
|
||||
}
|
||||
|
||||
switchTabToEditor() {
|
||||
this.tabEditor.click();
|
||||
this.tabEditor!.click(); // when this function is called, the tab must exist
|
||||
}
|
||||
|
||||
prepareEasyMDEToolbarActions() {
|
||||
|
||||
@ -5,6 +5,7 @@ import {showTemporaryTooltip} from '../modules/tippy.ts';
|
||||
import {GET, POST} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {createElementFromHTML, createElementFromAttrs} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {isImageFile, isVideoFile} from '../utils.ts';
|
||||
import type Dropzone from '@deltablot/dropzone';
|
||||
|
||||
@ -149,7 +150,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
|
||||
} catch (error) {
|
||||
// TODO: if listing the existing attachments failed, it should stop from operating the content or attachments,
|
||||
// otherwise the attachments might be lost.
|
||||
showErrorToast(`Failed to load attachments: ${error}`);
|
||||
showErrorToast(`Failed to load attachments: ${errorMessage(error)}`);
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@ import type {InplaceRenderPlugin} from '../render/plugin.ts';
|
||||
import {newInplacePluginPdfViewer} from '../render/plugins/inplace-pdf-viewer.ts';
|
||||
import {registerGlobalInitFunc} from '../modules/observer.ts';
|
||||
import {createElementFromHTML} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {html} from '../utils/html.ts';
|
||||
import {basename} from '../utils.ts';
|
||||
|
||||
@ -30,7 +31,7 @@ async function renderRawFileToContainer(container: HTMLElement, rawFileLink: str
|
||||
rendered = true;
|
||||
}
|
||||
} catch (e) {
|
||||
errorMsg = `${e}`;
|
||||
errorMsg = errorMessage(e);
|
||||
} finally {
|
||||
container.classList.remove('is-loading');
|
||||
}
|
||||
|
||||
@ -94,8 +94,8 @@ function createContext(imageAfter: HTMLImageElement, imageBefore: HTMLImageEleme
|
||||
}
|
||||
|
||||
class ImageDiff {
|
||||
containerEl: HTMLElement;
|
||||
diffContainerWidth: number;
|
||||
containerEl!: HTMLElement;
|
||||
diffContainerWidth!: number;
|
||||
|
||||
async init(containerEl: HTMLElement) {
|
||||
this.containerEl = containerEl;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {queryElems} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {sleep} from '../utils.ts';
|
||||
@ -26,7 +27,7 @@ async function onDownloadArchive(e: Event) {
|
||||
window.location.href = el.href; // the archive is ready, start real downloading
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showErrorToast(`Failed to download the archive: ${e}`, {duration: 2500});
|
||||
showErrorToast(`Failed to download the archive: ${errorMessage(e)}`, {duration: 2500});
|
||||
} finally {
|
||||
targetLoading.classList.remove('is-loading', 'loading-icon-2px');
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import {initViewedCheckboxListenerFor, initExpandAndCollapseFilesButton} from '.
|
||||
import {initImageDiff} from './imagediff.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {queryElemSiblings, hideElem, showElem, animateOnce, addDelegatedEventListener, createElementFromHTML, queryElems} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {POST, GET} from '../modules/fetch.ts';
|
||||
import {createTippy} from '../modules/tippy.ts';
|
||||
import {invertFileFolding} from './file-fold.ts';
|
||||
@ -85,7 +86,7 @@ function initRepoDiffConversationForm() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
showErrorToast(`Submit form failed: ${error}`);
|
||||
showErrorToast(`Submit form failed: ${errorMessage(error)}`);
|
||||
} finally {
|
||||
form?.classList.remove('is-loading');
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} fr
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {hideElem, querySingleVisibleElem, showElem} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
|
||||
import {convertHtmlToMarkdown} from '../markup/html2markdown.ts';
|
||||
import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
@ -73,7 +74,7 @@ async function tryOnEditContent(e: Event) {
|
||||
}
|
||||
comboMarkdownEditor.dropzoneSubmitReload();
|
||||
} catch (error) {
|
||||
showErrorToast(`Failed to save the content: ${error}`);
|
||||
showErrorToast(`Failed to save the content: ${errorMessage(error)}`);
|
||||
console.error(error);
|
||||
} finally {
|
||||
renderContent.classList.remove('is-loading');
|
||||
|
||||
@ -2,6 +2,7 @@ import {updateIssuesMeta} from './repo-common.ts';
|
||||
import {toggleElem, queryElems, isElemVisible} from '../utils/dom.ts';
|
||||
import {html, htmlRaw} from '../utils/html.ts';
|
||||
import {confirmModal} from './comp/ConfirmModal.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {createSortable} from '../modules/sortable.ts';
|
||||
import {DELETE, POST} from '../modules/fetch.ts';
|
||||
@ -87,7 +88,9 @@ function initRepoIssueListCheckboxes() {
|
||||
await updateIssuesMeta(url, action, issueIDs, elementId);
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showErrorToast(err.responseJSON?.error ?? err.message);
|
||||
// FIXME: this logic (including updateIssuesMeta) is not right, should refactor to our JSONError framework
|
||||
const e = err as {responseJSON?: {error: string}};
|
||||
showErrorToast(e.responseJSON?.error ?? errorMessage(err));
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
@ -2,6 +2,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import {GET, POST} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {addDelegatedEventListener, queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {parseDom} from '../utils.ts';
|
||||
|
||||
export function syncIssueMainContentTimelineItems(oldMainContent: Element, newMainContent: Element) {
|
||||
@ -42,7 +43,7 @@ export class IssueSidebarComboList {
|
||||
elDropdown: HTMLElement;
|
||||
elList: HTMLElement | null;
|
||||
elComboValue: HTMLInputElement;
|
||||
initialValues: string[];
|
||||
initialValues: string[] = [];
|
||||
container: HTMLElement;
|
||||
|
||||
elIssueMainContent: HTMLElement;
|
||||
@ -129,7 +130,7 @@ export class IssueSidebarComboList {
|
||||
await this.reloadPagePartially();
|
||||
} catch (e) {
|
||||
console.error('Failed to update to backend', e);
|
||||
showErrorToast(`Failed to update to backend: ${e}`);
|
||||
showErrorToast(`Failed to update to backend: ${errorMessage(e)}`);
|
||||
} finally {
|
||||
this.elIssueSidebar.classList.remove('is-loading');
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {htmlEscape} from '../utils/html.ts';
|
||||
import {createTippy} from '../modules/tippy.ts';
|
||||
import {
|
||||
@ -425,7 +426,7 @@ export function initRepoIssueTitleEdit() {
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showErrorToast(error.message);
|
||||
showErrorToast(errorMessage(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import {createSortable} from '../modules/sortable.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {queryElemChildren} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
|
||||
export function initRepoSettingsBranchesDrag() {
|
||||
const protectedBranchesList = document.querySelector<HTMLElement>('#protected-branches-list');
|
||||
@ -23,8 +24,7 @@ export function initRepoSettingsBranchesDrag() {
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
const errorMessage = String(err);
|
||||
showErrorToast(`Failed to update branch protection rule priority:, error: ${errorMessage}`);
|
||||
showErrorToast(`Failed to update branch protection rule priority: ${errorMessage(err)}`);
|
||||
}
|
||||
})();
|
||||
},
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {encodeURLEncodedBase64, decodeURLEncodedBase64} from '../utils.ts';
|
||||
import {hideElem, showElem} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {GET, POST} from '../modules/fetch.ts';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
@ -80,7 +81,7 @@ async function loginPasskey() {
|
||||
|
||||
window.location.href = reply?.redirect ?? `${appSubUrl}/`;
|
||||
} catch (err) {
|
||||
webAuthnError('general', err.message);
|
||||
webAuthnError('general', errorMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +105,7 @@ async function login2FA() {
|
||||
await verifyAssertion(credential);
|
||||
} catch (err) {
|
||||
if (!options.publicKey.extensions?.appid) {
|
||||
webAuthnError('general', err.message);
|
||||
webAuthnError('general', errorMessage(err));
|
||||
return;
|
||||
}
|
||||
delete options.publicKey.extensions.appid;
|
||||
@ -114,7 +115,7 @@ async function login2FA() {
|
||||
});
|
||||
await verifyAssertion(credential);
|
||||
} catch (err) {
|
||||
webAuthnError('general', err.message);
|
||||
webAuthnError('general', errorMessage(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -262,6 +263,6 @@ async function webAuthnRegisterRequest() {
|
||||
});
|
||||
await webauthnRegistered(credential);
|
||||
} catch (err) {
|
||||
webAuthnError('unknown', err);
|
||||
webAuthnError('unknown', errorMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
export function displayError(el: Element, err: Error): void {
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
|
||||
export function displayError(el: Element, err: unknown): void {
|
||||
el.classList.remove('is-loading');
|
||||
const errorNode = document.createElement('pre');
|
||||
errorNode.setAttribute('class', 'ui message error markup-block-error');
|
||||
errorNode.textContent = err.message || String(err);
|
||||
errorNode.textContent = errorMessage(err);
|
||||
el.before(errorNode);
|
||||
el.setAttribute('data-render-done', 'true');
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {generateElemId} from '../utils/dom.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import {isDarkTheme} from '../utils.ts';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
|
||||
@ -11,7 +12,7 @@ function safeRenderIframeLink(link: any): string | null {
|
||||
}
|
||||
return url.href;
|
||||
} catch (e) {
|
||||
console.error(`Failed to parse link: ${link}, error: ${e}`);
|
||||
console.error(`Failed to parse link: ${link}, error: ${errorMessage(e)}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
import {html} from '../utils/html.ts';
|
||||
import type {Intent} from '../types.ts';
|
||||
|
||||
export function errorMessage(err: unknown): string {
|
||||
return (err as Error)?.message || String(err);
|
||||
}
|
||||
|
||||
export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') {
|
||||
const msgContainer = document.querySelector('.page-content') ?? document.body;
|
||||
if (!msgContainer) {
|
||||
|
||||
@ -2,6 +2,7 @@ import emojis from '../../../assets/emoji.json' with {type: 'json'};
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {parseIssuePageInfo} from '../utils.ts';
|
||||
import {errorMessage} from '../modules/errors.ts';
|
||||
import type {Issue, Mention} from '../types.ts';
|
||||
|
||||
const maxMatches = 6;
|
||||
@ -47,7 +48,7 @@ export function fetchMentions(mentionsUrl: string): Promise<Mention[]> {
|
||||
if (!res.ok) throw new Error(res.statusText);
|
||||
return await res.json() as Mention[];
|
||||
} catch (e) {
|
||||
showErrorToast(`Failed to load mentions: ${e}`);
|
||||
showErrorToast(`Failed to load mentions: ${errorMessage(e)}`);
|
||||
return [];
|
||||
}
|
||||
})();
|
||||
|
||||
@ -3,13 +3,13 @@ import {addDelegatedEventListener, generateElemId, isDocumentFragmentOrElementNo
|
||||
import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg';
|
||||
|
||||
window.customElements.define('overflow-menu', class extends HTMLElement {
|
||||
popup: HTMLDivElement;
|
||||
overflowItems: Array<HTMLElement>;
|
||||
button: HTMLButtonElement | null;
|
||||
menuItemsEl: HTMLElement;
|
||||
resizeObserver: ResizeObserver;
|
||||
mutationObserver: MutationObserver;
|
||||
lastWidth: number;
|
||||
popup!: HTMLDivElement;
|
||||
overflowItems: Array<HTMLElement> = [];
|
||||
button: HTMLButtonElement | null = null;
|
||||
menuItemsEl!: HTMLElement;
|
||||
resizeObserver!: ResizeObserver;
|
||||
mutationObserver!: MutationObserver;
|
||||
lastWidth!: number;
|
||||
|
||||
updateButtonActivationState() {
|
||||
if (!this.button || !this.popup) return;
|
||||
@ -100,7 +100,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
|
||||
const itemOverFlowMenuButton = this.querySelector<HTMLButtonElement>('.overflow-menu-button');
|
||||
|
||||
// move items in popup back into the menu items for subsequent measurement
|
||||
for (const item of this.overflowItems || []) {
|
||||
for (const item of this.overflowItems) {
|
||||
if (!itemFlexSpace || item.getAttribute('data-after-flex-space')) {
|
||||
this.menuItemsEl.append(item);
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user