| @@ -58,7 +58,7 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *models.User) *models.PullReq | |||||
| assert.NoError(t, err) | assert.NoError(t, err) | ||||
| assert.NotEmpty(t, baseRepo) | assert.NotEmpty(t, baseRepo) | ||||
| headRepo, err := repo_module.ForkRepository(actor, forkOrg, baseRepo, "repo-pr-update", "desc") | |||||
| headRepo, err := repo_module.ForkRepository(actor, forkOrg, baseRepo, "repo-pr-update", "desc", "") | |||||
| assert.NoError(t, err) | assert.NoError(t, err) | ||||
| assert.NotEmpty(t, headRepo) | assert.NotEmpty(t, headRepo) | ||||
| @@ -164,12 +164,24 @@ func (a *Action) GetRepoName() string { | |||||
| return a.Repo.Name | return a.Repo.Name | ||||
| } | } | ||||
| // GetRepoName returns the name of the action repository. | |||||
| func (a *Action) GetRepoDisplayName() string { | |||||
| a.loadRepo() | |||||
| return a.Repo.DisplayName() | |||||
| } | |||||
| // ShortRepoName returns the name of the action repository | // ShortRepoName returns the name of the action repository | ||||
| // trimmed to max 33 chars. | // trimmed to max 33 chars. | ||||
| func (a *Action) ShortRepoName() string { | func (a *Action) ShortRepoName() string { | ||||
| return base.EllipsisString(a.GetRepoName(), 33) | return base.EllipsisString(a.GetRepoName(), 33) | ||||
| } | } | ||||
| // ShortRepoName returns the name of the action repository | |||||
| // trimmed to max 33 chars. | |||||
| func (a *Action) ShortRepoDisplayName() string { | |||||
| return base.EllipsisString(a.GetRepoDisplayName(), 33) | |||||
| } | |||||
| // GetRepoPath returns the virtual path to the action repository. | // GetRepoPath returns the virtual path to the action repository. | ||||
| func (a *Action) GetRepoPath() string { | func (a *Action) GetRepoPath() string { | ||||
| return path.Join(a.GetRepoUserName(), a.GetRepoName()) | return path.Join(a.GetRepoUserName(), a.GetRepoName()) | ||||
| @@ -181,6 +193,12 @@ func (a *Action) ShortRepoPath() string { | |||||
| return path.Join(a.ShortRepoUserName(), a.ShortRepoName()) | return path.Join(a.ShortRepoUserName(), a.ShortRepoName()) | ||||
| } | } | ||||
| // ShortRepoPath returns the virtual path to the action repository | |||||
| // trimmed to max 20 + 1 + 33 chars. | |||||
| func (a *Action) ShortRepoFullDisplayName() string { | |||||
| return path.Join(a.ShortRepoUserName(), a.ShortRepoDisplayName()) | |||||
| } | |||||
| // GetRepoLink returns relative link to action repository. | // GetRepoLink returns relative link to action repository. | ||||
| func (a *Action) GetRepoLink() string { | func (a *Action) GetRepoLink() string { | ||||
| if len(setting.AppSubURL) > 0 { | if len(setting.AppSubURL) > 0 { | ||||
| @@ -12,6 +12,7 @@ import ( | |||||
| "errors" | "errors" | ||||
| "fmt" | "fmt" | ||||
| "html/template" | "html/template" | ||||
| "math/rand" | |||||
| "xorm.io/xorm" | "xorm.io/xorm" | ||||
| "code.gitea.io/gitea/modules/blockchain" | "code.gitea.io/gitea/modules/blockchain" | ||||
| @@ -139,6 +140,7 @@ func NewRepoContext() { | |||||
| // RepositoryStatus defines the status of repository | // RepositoryStatus defines the status of repository | ||||
| type RepositoryStatus int | type RepositoryStatus int | ||||
| type RepoBlockChainStatus int | type RepoBlockChainStatus int | ||||
| type RepoType int | |||||
| // all kinds of RepositoryStatus | // all kinds of RepositoryStatus | ||||
| const ( | const ( | ||||
| @@ -152,6 +154,11 @@ const ( | |||||
| RepoBlockChainFailed | RepoBlockChainFailed | ||||
| ) | ) | ||||
| const ( | |||||
| RepoNormal RepoType = iota | |||||
| RepoCourse | |||||
| ) | |||||
| // Repository represents a git repository. | // Repository represents a git repository. | ||||
| type Repository struct { | type Repository struct { | ||||
| ID int64 `xorm:"pk autoincr"` | ID int64 `xorm:"pk autoincr"` | ||||
| @@ -165,7 +172,8 @@ type Repository struct { | |||||
| OriginalServiceType api.GitServiceType `xorm:"index"` | OriginalServiceType api.GitServiceType `xorm:"index"` | ||||
| OriginalURL string `xorm:"VARCHAR(2048)"` | OriginalURL string `xorm:"VARCHAR(2048)"` | ||||
| DefaultBranch string | DefaultBranch string | ||||
| CreatorID int64 `xorm:"INDEX NOT NULL DEFAULT 0"` | |||||
| Creator *User `xorm:"-"` | |||||
| NumWatches int | NumWatches int | ||||
| NumStars int | NumStars int | ||||
| NumForks int | NumForks int | ||||
| @@ -174,11 +182,12 @@ type Repository struct { | |||||
| NumOpenIssues int `xorm:"-"` | NumOpenIssues int `xorm:"-"` | ||||
| NumPulls int | NumPulls int | ||||
| NumClosedPulls int | NumClosedPulls int | ||||
| NumOpenPulls int `xorm:"-"` | |||||
| NumMilestones int `xorm:"NOT NULL DEFAULT 0"` | |||||
| NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` | |||||
| NumOpenMilestones int `xorm:"-"` | |||||
| NumCommit int64 `xorm:"NOT NULL DEFAULT 0"` | |||||
| NumOpenPulls int `xorm:"-"` | |||||
| NumMilestones int `xorm:"NOT NULL DEFAULT 0"` | |||||
| NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` | |||||
| NumOpenMilestones int `xorm:"-"` | |||||
| NumCommit int64 `xorm:"NOT NULL DEFAULT 0"` | |||||
| RepoType RepoType `xorm:"NOT NULL DEFAULT 0"` | |||||
| IsPrivate bool `xorm:"INDEX"` | IsPrivate bool `xorm:"INDEX"` | ||||
| IsEmpty bool `xorm:"INDEX"` | IsEmpty bool `xorm:"INDEX"` | ||||
| @@ -223,6 +232,7 @@ type Repository struct { | |||||
| Hot int64 `xorm:"-"` | Hot int64 `xorm:"-"` | ||||
| Active int64 `xorm:"-"` | Active int64 `xorm:"-"` | ||||
| Alias string | |||||
| } | } | ||||
| // SanitizedOriginalURL returns a sanitized OriginalURL | // SanitizedOriginalURL returns a sanitized OriginalURL | ||||
| @@ -233,6 +243,14 @@ func (repo *Repository) SanitizedOriginalURL() string { | |||||
| return util.SanitizeURLCredentials(repo.OriginalURL, false) | return util.SanitizeURLCredentials(repo.OriginalURL, false) | ||||
| } | } | ||||
| // GetAlias returns a sanitized OriginalURL | |||||
| func (repo *Repository) DisplayName() string { | |||||
| if repo.Alias == "" { | |||||
| return repo.Name | |||||
| } | |||||
| return repo.Alias | |||||
| } | |||||
| // ColorFormat returns a colored string to represent this repo | // ColorFormat returns a colored string to represent this repo | ||||
| func (repo *Repository) ColorFormat(s fmt.State) { | func (repo *Repository) ColorFormat(s fmt.State) { | ||||
| var ownerName interface{} | var ownerName interface{} | ||||
| @@ -286,6 +304,11 @@ func (repo *Repository) FullName() string { | |||||
| return repo.OwnerName + "/" + repo.Name | return repo.OwnerName + "/" + repo.Name | ||||
| } | } | ||||
| // FullDisplayName returns the repository full display name | |||||
| func (repo *Repository) FullDisplayName() string { | |||||
| return repo.OwnerName + "/" + repo.DisplayName() | |||||
| } | |||||
| // HTMLURL returns the repository HTML URL | // HTMLURL returns the repository HTML URL | ||||
| func (repo *Repository) HTMLURL() string { | func (repo *Repository) HTMLURL() string { | ||||
| return setting.AppURL + repo.FullName() | return setting.AppURL + repo.FullName() | ||||
| @@ -386,6 +409,7 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) | |||||
| Owner: repo.Owner.APIFormat(), | Owner: repo.Owner.APIFormat(), | ||||
| Name: repo.Name, | Name: repo.Name, | ||||
| FullName: repo.FullName(), | FullName: repo.FullName(), | ||||
| FullDisplayName: repo.FullDisplayName(), | |||||
| Description: repo.Description, | Description: repo.Description, | ||||
| Private: repo.IsPrivate, | Private: repo.IsPrivate, | ||||
| Template: repo.IsTemplate, | Template: repo.IsTemplate, | ||||
| @@ -921,17 +945,23 @@ func (repo *Repository) DescriptionHTML() template.HTML { | |||||
| return template.HTML(markup.Sanitize(string(desc))) | return template.HTML(markup.Sanitize(string(desc))) | ||||
| } | } | ||||
| func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { | |||||
| has, err := e.Get(&Repository{ | |||||
| OwnerID: u.ID, | |||||
| LowerName: strings.ToLower(repoName), | |||||
| }) | |||||
| return has && com.IsDir(RepoPath(u.Name, repoName)), err | |||||
| func isRepositoryExist(e Engine, u *User, repoName string, alias string) (bool, error) { | |||||
| var cond = builder.NewCond() | |||||
| cond = cond.And(builder.Eq{"owner_id": u.ID}) | |||||
| if alias != "" { | |||||
| subCon := builder.NewCond() | |||||
| subCon = subCon.Or(builder.Eq{"alias": alias}, builder.Eq{"lower_name": repoName}) | |||||
| cond = cond.And(subCon) | |||||
| } else { | |||||
| cond = cond.And(builder.Eq{"lower_name": repoName}) | |||||
| } | |||||
| count, err := e.Where(cond).Count(&Repository{}) | |||||
| return count > 0 || com.IsDir(RepoPath(u.Name, repoName)), err | |||||
| } | } | ||||
| // IsRepositoryExist returns true if the repository with given name under user has already existed. | // IsRepositoryExist returns true if the repository with given name under user has already existed. | ||||
| func IsRepositoryExist(u *User, repoName string) (bool, error) { | |||||
| return isRepositoryExist(x, u, repoName) | |||||
| func IsRepositoryExist(u *User, repoName string, alias string) (bool, error) { | |||||
| return isRepositoryExist(x, u, repoName, alias) | |||||
| } | } | ||||
| // CloneLink represents different types of clone URLs of repository. | // CloneLink represents different types of clone URLs of repository. | ||||
| @@ -975,20 +1005,24 @@ func (repo *Repository) CloneLink() (cl *CloneLink) { | |||||
| } | } | ||||
| // CheckCreateRepository check if could created a repository | // CheckCreateRepository check if could created a repository | ||||
| func CheckCreateRepository(doer, u *User, name string) error { | |||||
| func CheckCreateRepository(doer, u *User, repoName, alias string) error { | |||||
| if !doer.CanCreateRepo() { | if !doer.CanCreateRepo() { | ||||
| return ErrReachLimitOfRepo{u.MaxRepoCreation} | return ErrReachLimitOfRepo{u.MaxRepoCreation} | ||||
| } | } | ||||
| if err := IsUsableRepoName(name); err != nil { | |||||
| if err := IsUsableRepoName(repoName); err != nil { | |||||
| return err | return err | ||||
| } | } | ||||
| has, err := isRepositoryExist(x, u, name) | |||||
| if err := IsUsableRepoAlias(alias); err != nil { | |||||
| return err | |||||
| } | |||||
| has, err := isRepositoryExist(x, u, repoName, alias) | |||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("IsRepositoryExist: %v", err) | return fmt.Errorf("IsRepositoryExist: %v", err) | ||||
| } else if has { | } else if has { | ||||
| return ErrRepoAlreadyExist{u.Name, name} | |||||
| return ErrRepoAlreadyExist{u.Name, repoName} | |||||
| } | } | ||||
| return nil | return nil | ||||
| } | } | ||||
| @@ -996,6 +1030,7 @@ func CheckCreateRepository(doer, u *User, name string) error { | |||||
| // CreateRepoOptions contains the create repository options | // CreateRepoOptions contains the create repository options | ||||
| type CreateRepoOptions struct { | type CreateRepoOptions struct { | ||||
| Name string | Name string | ||||
| Alias string | |||||
| Description string | Description string | ||||
| OriginalURL string | OriginalURL string | ||||
| GitServiceType api.GitServiceType | GitServiceType api.GitServiceType | ||||
| @@ -1008,6 +1043,8 @@ type CreateRepoOptions struct { | |||||
| IsMirror bool | IsMirror bool | ||||
| AutoInit bool | AutoInit bool | ||||
| Status RepositoryStatus | Status RepositoryStatus | ||||
| IsCourse bool | |||||
| Topics []string | |||||
| } | } | ||||
| // GetRepoInitFile returns repository init files | // GetRepoInitFile returns repository init files | ||||
| @@ -1036,8 +1073,10 @@ func GetRepoInitFile(tp, name string) ([]byte, error) { | |||||
| } | } | ||||
| var ( | var ( | ||||
| reservedRepoNames = []string{".", ".."} | |||||
| reservedRepoPatterns = []string{"*.git", "*.wiki"} | |||||
| reservedRepoNames = []string{".", ".."} | |||||
| reservedRepoPatterns = []string{"*.git", "*.wiki"} | |||||
| reservedRepoAliasNames = []string{} | |||||
| reservedRepoAliasPatterns = []string{} | |||||
| ) | ) | ||||
| // IsUsableRepoName returns true when repository is usable | // IsUsableRepoName returns true when repository is usable | ||||
| @@ -1045,19 +1084,30 @@ func IsUsableRepoName(name string) error { | |||||
| return isUsableName(reservedRepoNames, reservedRepoPatterns, name) | return isUsableName(reservedRepoNames, reservedRepoPatterns, name) | ||||
| } | } | ||||
| // IsUsableRepoAlias returns true when repository alias is usable | |||||
| func IsUsableRepoAlias(name string) error { | |||||
| return isUsableName(reservedRepoAliasNames, reservedRepoAliasPatterns, name) | |||||
| } | |||||
| // CreateRepository creates a repository for the user/organization. | // CreateRepository creates a repository for the user/organization. | ||||
| func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error) { | |||||
| func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, opts ...CreateRepoOptions) (err error) { | |||||
| if err = IsUsableRepoName(repo.Name); err != nil { | if err = IsUsableRepoName(repo.Name); err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| has, err := isRepositoryExist(ctx.e, u, repo.Name) | |||||
| if err := IsUsableRepoAlias(repo.Alias); err != nil { | |||||
| return err | |||||
| } | |||||
| has, err := isRepositoryExist(ctx.e, u, repo.Name, repo.Alias) | |||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("IsRepositoryExist: %v", err) | return fmt.Errorf("IsRepositoryExist: %v", err) | ||||
| } else if has { | } else if has { | ||||
| return ErrRepoAlreadyExist{u.Name, repo.Name} | return ErrRepoAlreadyExist{u.Name, repo.Name} | ||||
| } | } | ||||
| isCourse := isCourse(opts) | |||||
| if isCourse { | |||||
| repo.CreatorID = doer.ID | |||||
| } | |||||
| if _, err = ctx.e.Insert(repo); err != nil { | if _, err = ctx.e.Insert(repo); err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -1091,17 +1141,23 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||||
| Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true}, | Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true}, | ||||
| }) | }) | ||||
| } else if tp == UnitTypeDatasets { | } else if tp == UnitTypeDatasets { | ||||
| units = append(units, RepoUnit{ | |||||
| RepoID: repo.ID, | |||||
| Type: tp, | |||||
| Config: &DatasetConfig{EnableDataset: true}, | |||||
| }) | |||||
| if !isCourse { | |||||
| units = append(units, RepoUnit{ | |||||
| RepoID: repo.ID, | |||||
| Type: tp, | |||||
| Config: &DatasetConfig{EnableDataset: true}, | |||||
| }) | |||||
| } | |||||
| } else if tp == UnitTypeCloudBrain { | } else if tp == UnitTypeCloudBrain { | ||||
| units = append(units, RepoUnit{ | |||||
| RepoID: repo.ID, | |||||
| Type: tp, | |||||
| Config: &CloudBrainConfig{EnableCloudBrain: true}, | |||||
| }) | |||||
| if !isCourse { | |||||
| units = append(units, RepoUnit{ | |||||
| RepoID: repo.ID, | |||||
| Type: tp, | |||||
| Config: &CloudBrainConfig{EnableCloudBrain: true}, | |||||
| }) | |||||
| } | |||||
| } else if tp == UnitTypeBlockChain { | } else if tp == UnitTypeBlockChain { | ||||
| units = append(units, RepoUnit{ | units = append(units, RepoUnit{ | ||||
| RepoID: repo.ID, | RepoID: repo.ID, | ||||
| @@ -1109,11 +1165,13 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||||
| Config: &BlockChainConfig{EnableBlockChain: true}, | Config: &BlockChainConfig{EnableBlockChain: true}, | ||||
| }) | }) | ||||
| } else if tp == UnitTypeModelManage { | } else if tp == UnitTypeModelManage { | ||||
| units = append(units, RepoUnit{ | |||||
| RepoID: repo.ID, | |||||
| Type: tp, | |||||
| Config: &ModelManageConfig{EnableModelManage: true}, | |||||
| }) | |||||
| if !isCourse { | |||||
| units = append(units, RepoUnit{ | |||||
| RepoID: repo.ID, | |||||
| Type: tp, | |||||
| Config: &ModelManageConfig{EnableModelManage: true}, | |||||
| }) | |||||
| } | |||||
| } else { | } else { | ||||
| units = append(units, RepoUnit{ | units = append(units, RepoUnit{ | ||||
| RepoID: repo.ID, | RepoID: repo.ID, | ||||
| @@ -1183,6 +1241,14 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||||
| return nil | return nil | ||||
| } | } | ||||
| func isCourse(opts []CreateRepoOptions) bool { | |||||
| var isCourse = false | |||||
| if len(opts) > 0 { | |||||
| isCourse = opts[0].IsCourse | |||||
| } | |||||
| return isCourse | |||||
| } | |||||
| func countRepositories(userID int64, private bool) int64 { | func countRepositories(userID int64, private bool) int64 { | ||||
| sess := x.Where("id > 0") | sess := x.Where("id > 0") | ||||
| @@ -1233,7 +1299,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error | |||||
| } | } | ||||
| // Check if new owner has repository with same name. | // Check if new owner has repository with same name. | ||||
| has, err := IsRepositoryExist(newOwner, repo.Name) | |||||
| has, err := IsRepositoryExist(newOwner, repo.Name, repo.Alias) | |||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("IsRepositoryExist: %v", err) | return fmt.Errorf("IsRepositoryExist: %v", err) | ||||
| } else if has { | } else if has { | ||||
| @@ -1366,7 +1432,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err | |||||
| return err | return err | ||||
| } | } | ||||
| has, err := IsRepositoryExist(repo.Owner, newRepoName) | |||||
| has, err := IsRepositoryExist(repo.Owner, newRepoName, "") | |||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("IsRepositoryExist: %v", err) | return fmt.Errorf("IsRepositoryExist: %v", err) | ||||
| } else if has { | } else if has { | ||||
| @@ -2521,6 +2587,14 @@ func UpdateRepositoryCommitNum(repo *Repository) error { | |||||
| return nil | return nil | ||||
| } | } | ||||
| func GenerateDefaultRepoName(ownerName string) string { | |||||
| if len(ownerName) > 5 { | |||||
| ownerName = ownerName[:5] | |||||
| } | |||||
| now := time.Now().Format("20060102150405") | |||||
| return ownerName + now + fmt.Sprint(rand.Intn(10)) | |||||
| } | |||||
| type RepoFile struct { | type RepoFile struct { | ||||
| CommitId string | CommitId string | ||||
| Content []byte | Content []byte | ||||
| @@ -19,6 +19,7 @@ import ( | |||||
| // GenerateRepoOptions contains the template units to generate | // GenerateRepoOptions contains the template units to generate | ||||
| type GenerateRepoOptions struct { | type GenerateRepoOptions struct { | ||||
| Name string | Name string | ||||
| Alias string | |||||
| Description string | Description string | ||||
| Private bool | Private bool | ||||
| GitContent bool | GitContent bool | ||||
| @@ -48,9 +48,12 @@ func (repos RepositoryList) loadAttributes(e Engine) error { | |||||
| set := make(map[int64]struct{}) | set := make(map[int64]struct{}) | ||||
| repoIDs := make([]int64, len(repos)) | repoIDs := make([]int64, len(repos)) | ||||
| setCreator := make(map[int64]struct{}) | |||||
| for i := range repos { | for i := range repos { | ||||
| set[repos[i].OwnerID] = struct{}{} | set[repos[i].OwnerID] = struct{}{} | ||||
| repoIDs[i] = repos[i].ID | repoIDs[i] = repos[i].ID | ||||
| setCreator[repos[i].CreatorID] = struct{}{} | |||||
| } | } | ||||
| // Load owners. | // Load owners. | ||||
| @@ -61,8 +64,18 @@ func (repos RepositoryList) loadAttributes(e Engine) error { | |||||
| Find(&users); err != nil { | Find(&users); err != nil { | ||||
| return fmt.Errorf("find users: %v", err) | return fmt.Errorf("find users: %v", err) | ||||
| } | } | ||||
| //Load creator | |||||
| creators := make(map[int64]*User, len(set)) | |||||
| if err := e. | |||||
| Where("id > 0"). | |||||
| In("id", keysInt64(setCreator)). | |||||
| Find(&creators); err != nil { | |||||
| return fmt.Errorf("find create repo users: %v", err) | |||||
| } | |||||
| for i := range repos { | for i := range repos { | ||||
| repos[i].Owner = users[repos[i].OwnerID] | repos[i].Owner = users[repos[i].OwnerID] | ||||
| repos[i].Creator = creators[repos[i].CreatorID] | |||||
| } | } | ||||
| // Load primary language. | // Load primary language. | ||||
| @@ -174,6 +187,10 @@ type SearchRepoOptions struct { | |||||
| // True -> include just has milestones | // True -> include just has milestones | ||||
| // False -> include just has no milestone | // False -> include just has no milestone | ||||
| HasMilestones util.OptionalBool | HasMilestones util.OptionalBool | ||||
| // None -> include all repos | |||||
| // True -> include just courses | |||||
| // False -> include just no courses | |||||
| Course util.OptionalBool | |||||
| } | } | ||||
| //SearchOrderBy is used to sort the result | //SearchOrderBy is used to sort the result | ||||
| @@ -200,8 +217,8 @@ const ( | |||||
| SearchOrderByForks SearchOrderBy = "num_forks ASC" | SearchOrderByForks SearchOrderBy = "num_forks ASC" | ||||
| SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" | SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" | ||||
| SearchOrderByDownloadTimes SearchOrderBy = "download_times DESC" | SearchOrderByDownloadTimes SearchOrderBy = "download_times DESC" | ||||
| SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC" | |||||
| SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC" | |||||
| SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC" | |||||
| SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC" | |||||
| ) | ) | ||||
| // SearchRepositoryCondition creates a query condition according search repository options | // SearchRepositoryCondition creates a query condition according search repository options | ||||
| @@ -321,6 +338,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { | |||||
| var likes = builder.NewCond() | var likes = builder.NewCond() | ||||
| for _, v := range strings.Split(opts.Keyword, ",") { | for _, v := range strings.Split(opts.Keyword, ",") { | ||||
| likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) | likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) | ||||
| likes = likes.Or(builder.Like{"alias", v}) | |||||
| if opts.IncludeDescription { | if opts.IncludeDescription { | ||||
| likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) | likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) | ||||
| } | } | ||||
| @@ -350,6 +368,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { | |||||
| cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) | cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) | ||||
| } | } | ||||
| if opts.Course == util.OptionalBoolTrue { | |||||
| cond = cond.And(builder.Eq{"repo_type": RepoCourse}) | |||||
| } | |||||
| if opts.Actor != nil && opts.Actor.IsRestricted { | if opts.Actor != nil && opts.Actor.IsRestricted { | ||||
| cond = cond.And(accessibleRepositoryCondition(opts.Actor)) | cond = cond.And(accessibleRepositoryCondition(opts.Actor)) | ||||
| } | } | ||||
| @@ -186,6 +186,8 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro | |||||
| data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field)) | data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field)) | ||||
| case validation.ErrGlobPattern: | case validation.ErrGlobPattern: | ||||
| data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message) | data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message) | ||||
| case validation.ErrAlphaDashDotChinese: | |||||
| data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_chinese_error") | |||||
| default: | default: | ||||
| data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification | data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification | ||||
| } | } | ||||
| @@ -29,6 +29,7 @@ import ( | |||||
| type CreateRepoForm struct { | type CreateRepoForm struct { | ||||
| UID int64 `binding:"Required"` | UID int64 `binding:"Required"` | ||||
| RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` | RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` | ||||
| Alias string `binding:"Required;MaxSize(100);AlphaDashDotChinese"` | |||||
| Private bool | Private bool | ||||
| Description string `binding:"MaxSize(1024)"` | Description string `binding:"MaxSize(1024)"` | ||||
| DefaultBranch string `binding:"GitRefName;MaxSize(100)"` | DefaultBranch string `binding:"GitRefName;MaxSize(100)"` | ||||
| @@ -62,6 +63,7 @@ type MigrateRepoForm struct { | |||||
| UID int64 `json:"uid" binding:"Required"` | UID int64 `json:"uid" binding:"Required"` | ||||
| // required: true | // required: true | ||||
| RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` | RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` | ||||
| Alias string `json:"alias" binding:"Required;AlphaDashDotChinese;MaxSize(100)"` | |||||
| Mirror bool `json:"mirror"` | Mirror bool `json:"mirror"` | ||||
| Private bool `json:"private"` | Private bool `json:"private"` | ||||
| Description string `json:"description" binding:"MaxSize(255)"` | Description string `json:"description" binding:"MaxSize(255)"` | ||||
| @@ -109,6 +111,7 @@ func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) { | |||||
| // RepoSettingForm form for changing repository settings | // RepoSettingForm form for changing repository settings | ||||
| type RepoSettingForm struct { | type RepoSettingForm struct { | ||||
| RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` | RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` | ||||
| Alias string `binding:"Required;AlphaDashDotChinese;MaxSize(100)"` | |||||
| Description string `binding:"MaxSize(255)"` | Description string `binding:"MaxSize(255)"` | ||||
| Website string `binding:"ValidUrl;MaxSize(255)"` | Website string `binding:"ValidUrl;MaxSize(255)"` | ||||
| Interval string | Interval string | ||||
| @@ -725,3 +728,15 @@ type DeadlineForm struct { | |||||
| func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | ||||
| return validate(errs, ctx.Data, f, ctx.Locale) | return validate(errs, ctx.Data, f, ctx.Locale) | ||||
| } | } | ||||
| type CreateCourseForm struct { | |||||
| RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` | |||||
| Alias string `binding:"Required;MaxSize(100);AlphaDashDotChinese"` | |||||
| Topics string | |||||
| Description string `binding:"MaxSize(1024)"` | |||||
| } | |||||
| // Validate validates the fields | |||||
| func (f *CreateCourseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||||
| return validate(errs, ctx.Data, f, ctx.Locale) | |||||
| } | |||||
| @@ -22,12 +22,17 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m | |||||
| Limit: u.MaxRepoCreation, | Limit: u.MaxRepoCreation, | ||||
| } | } | ||||
| } | } | ||||
| var RepoType = models.RepoNormal | |||||
| if opts.IsCourse { | |||||
| RepoType = models.RepoCourse | |||||
| } | |||||
| repo := &models.Repository{ | repo := &models.Repository{ | ||||
| OwnerID: u.ID, | OwnerID: u.ID, | ||||
| Owner: u, | Owner: u, | ||||
| OwnerName: u.Name, | OwnerName: u.Name, | ||||
| Name: opts.Name, | Name: opts.Name, | ||||
| Alias: opts.Alias, | |||||
| LowerName: strings.ToLower(opts.Name), | LowerName: strings.ToLower(opts.Name), | ||||
| Description: opts.Description, | Description: opts.Description, | ||||
| OriginalURL: opts.OriginalURL, | OriginalURL: opts.OriginalURL, | ||||
| @@ -37,10 +42,14 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m | |||||
| CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, | CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, | ||||
| Status: opts.Status, | Status: opts.Status, | ||||
| IsEmpty: !opts.AutoInit, | IsEmpty: !opts.AutoInit, | ||||
| RepoType: RepoType, | |||||
| } | } | ||||
| err = models.WithTx(func(ctx models.DBContext) error { | err = models.WithTx(func(ctx models.DBContext) error { | ||||
| if err = models.CreateRepository(ctx, doer, u, repo); err != nil { | |||||
| if err = models.CreateRepository(ctx, doer, u, repo, opts); err != nil { | |||||
| return err | |||||
| } | |||||
| if err = models.SaveTopics(repo.ID, opts.Topics...); err != nil { | |||||
| return err | return err | ||||
| } | } | ||||
| @@ -15,7 +15,7 @@ import ( | |||||
| ) | ) | ||||
| // ForkRepository forks a repository | // ForkRepository forks a repository | ||||
| func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc string) (_ *models.Repository, err error) { | |||||
| func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc, alias string) (_ *models.Repository, err error) { | |||||
| forkedRepo, err := oldRepo.GetUserFork(owner.ID) | forkedRepo, err := oldRepo.GetUserFork(owner.ID) | ||||
| if err != nil { | if err != nil { | ||||
| return nil, err | return nil, err | ||||
| @@ -33,6 +33,7 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, | |||||
| Owner: owner, | Owner: owner, | ||||
| OwnerName: owner.Name, | OwnerName: owner.Name, | ||||
| Name: name, | Name: name, | ||||
| Alias: alias, | |||||
| LowerName: strings.ToLower(name), | LowerName: strings.ToLower(name), | ||||
| Description: desc, | Description: desc, | ||||
| DefaultBranch: oldRepo.DefaultBranch, | DefaultBranch: oldRepo.DefaultBranch, | ||||
| @@ -18,7 +18,7 @@ func TestForkRepository(t *testing.T) { | |||||
| user := models.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User) | user := models.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User) | ||||
| repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) | repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) | ||||
| fork, err := ForkRepository(user, user, repo, "test", "test") | |||||
| fork, err := ForkRepository(user, user, repo, "test", "test", "test") | |||||
| assert.Nil(t, fork) | assert.Nil(t, fork) | ||||
| assert.Error(t, err) | assert.Error(t, err) | ||||
| assert.True(t, models.IsErrForkAlreadyExist(err)) | assert.True(t, models.IsErrForkAlreadyExist(err)) | ||||
| @@ -236,6 +236,7 @@ func GenerateRepository(ctx models.DBContext, doer, owner *models.User, template | |||||
| Owner: owner, | Owner: owner, | ||||
| OwnerName: owner.Name, | OwnerName: owner.Name, | ||||
| Name: opts.Name, | Name: opts.Name, | ||||
| Alias: opts.Alias, | |||||
| LowerName: strings.ToLower(opts.Name), | LowerName: strings.ToLower(opts.Name), | ||||
| Description: opts.Description, | Description: opts.Description, | ||||
| IsPrivate: opts.Private, | IsPrivate: opts.Private, | ||||
| @@ -568,6 +568,11 @@ var ( | |||||
| }{} | }{} | ||||
| Warn_Notify_Mails []string | Warn_Notify_Mails []string | ||||
| Course = struct { | |||||
| OrgName string | |||||
| TeamName string | |||||
| }{} | |||||
| ) | ) | ||||
| // DateLang transforms standard language locale name to corresponding value in datetime plugin. | // DateLang transforms standard language locale name to corresponding value in datetime plugin. | ||||
| @@ -1331,6 +1336,10 @@ func NewContext() { | |||||
| sec = Cfg.Section("warn_mail") | sec = Cfg.Section("warn_mail") | ||||
| Warn_Notify_Mails = strings.Split(sec.Key("mails").MustString(""), ",") | Warn_Notify_Mails = strings.Split(sec.Key("mails").MustString(""), ",") | ||||
| sec = Cfg.Section("course") | |||||
| Course.OrgName = sec.Key("org_name").MustString("") | |||||
| Course.TeamName = sec.Key("team_name").MustString("") | |||||
| } | } | ||||
| func SetRadarMapConfig() { | func SetRadarMapConfig() { | ||||
| @@ -46,31 +46,32 @@ type ExternalWiki struct { | |||||
| // Repository represents a repository | // Repository represents a repository | ||||
| type Repository struct { | type Repository struct { | ||||
| ID int64 `json:"id"` | |||||
| Owner *User `json:"owner"` | |||||
| Name string `json:"name"` | |||||
| FullName string `json:"full_name"` | |||||
| Description string `json:"description"` | |||||
| Empty bool `json:"empty"` | |||||
| Private bool `json:"private"` | |||||
| Fork bool `json:"fork"` | |||||
| Template bool `json:"template"` | |||||
| Parent *Repository `json:"parent"` | |||||
| Mirror bool `json:"mirror"` | |||||
| Size int `json:"size"` | |||||
| HTMLURL string `json:"html_url"` | |||||
| SSHURL string `json:"ssh_url"` | |||||
| CloneURL string `json:"clone_url"` | |||||
| OriginalURL string `json:"original_url"` | |||||
| Website string `json:"website"` | |||||
| Stars int `json:"stars_count"` | |||||
| Forks int `json:"forks_count"` | |||||
| Watchers int `json:"watchers_count"` | |||||
| OpenIssues int `json:"open_issues_count"` | |||||
| OpenPulls int `json:"open_pr_counter"` | |||||
| Releases int `json:"release_counter"` | |||||
| DefaultBranch string `json:"default_branch"` | |||||
| Archived bool `json:"archived"` | |||||
| ID int64 `json:"id"` | |||||
| Owner *User `json:"owner"` | |||||
| Name string `json:"name"` | |||||
| FullName string `json:"full_name"` | |||||
| FullDisplayName string `json:"full_display_name"` | |||||
| Description string `json:"description"` | |||||
| Empty bool `json:"empty"` | |||||
| Private bool `json:"private"` | |||||
| Fork bool `json:"fork"` | |||||
| Template bool `json:"template"` | |||||
| Parent *Repository `json:"parent"` | |||||
| Mirror bool `json:"mirror"` | |||||
| Size int `json:"size"` | |||||
| HTMLURL string `json:"html_url"` | |||||
| SSHURL string `json:"ssh_url"` | |||||
| CloneURL string `json:"clone_url"` | |||||
| OriginalURL string `json:"original_url"` | |||||
| Website string `json:"website"` | |||||
| Stars int `json:"stars_count"` | |||||
| Forks int `json:"forks_count"` | |||||
| Watchers int `json:"watchers_count"` | |||||
| OpenIssues int `json:"open_issues_count"` | |||||
| OpenPulls int `json:"open_pr_counter"` | |||||
| Releases int `json:"release_counter"` | |||||
| DefaultBranch string `json:"default_branch"` | |||||
| Archived bool `json:"archived"` | |||||
| // swagger:strfmt date-time | // swagger:strfmt date-time | ||||
| Created time.Time `json:"created_at"` | Created time.Time `json:"created_at"` | ||||
| // swagger:strfmt date-time | // swagger:strfmt date-time | ||||
| @@ -217,6 +218,7 @@ type MigrateRepoOption struct { | |||||
| UID int `json:"uid" binding:"Required"` | UID int `json:"uid" binding:"Required"` | ||||
| // required: true | // required: true | ||||
| RepoName string `json:"repo_name" binding:"Required"` | RepoName string `json:"repo_name" binding:"Required"` | ||||
| Alias string `json:"alias" binding:"Required"` | |||||
| Mirror bool `json:"mirror"` | Mirror bool `json:"mirror"` | ||||
| Private bool `json:"private"` | Private bool `json:"private"` | ||||
| Description string `json:"description"` | Description string `json:"description"` | ||||
| @@ -84,6 +84,7 @@ func CreateMigrateTask(doer, u *models.User, opts base.MigrateOptions) (*models. | |||||
| repo, err := repo_module.CreateRepository(doer, u, models.CreateRepoOptions{ | repo, err := repo_module.CreateRepository(doer, u, models.CreateRepoOptions{ | ||||
| Name: opts.RepoName, | Name: opts.RepoName, | ||||
| Alias: opts.Alias, | |||||
| Description: opts.Description, | Description: opts.Description, | ||||
| OriginalURL: opts.OriginalURL, | OriginalURL: opts.OriginalURL, | ||||
| GitServiceType: opts.GitServiceType, | GitServiceType: opts.GitServiceType, | ||||
| @@ -19,6 +19,8 @@ const ( | |||||
| // ErrGlobPattern is returned when glob pattern is invalid | // ErrGlobPattern is returned when glob pattern is invalid | ||||
| ErrGlobPattern = "GlobPattern" | ErrGlobPattern = "GlobPattern" | ||||
| ErrAlphaDashDotChinese = "AlphaDashDotChineseError" | |||||
| ) | ) | ||||
| var ( | var ( | ||||
| @@ -26,6 +28,8 @@ var ( | |||||
| // They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere. | // They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere. | ||||
| // They cannot have question-mark ?, asterisk *, or open bracket [ anywhere | // They cannot have question-mark ?, asterisk *, or open bracket [ anywhere | ||||
| GitRefNamePatternInvalid = regexp.MustCompile(`[\000-\037\177 \\~^:?*[]+`) | GitRefNamePatternInvalid = regexp.MustCompile(`[\000-\037\177 \\~^:?*[]+`) | ||||
| AlphaDashDotChinese = regexp.MustCompile("^[\u4e00-\u9fa5\\.\\-_A-Za-z0-9]+$") | |||||
| ) | ) | ||||
| // CheckGitRefAdditionalRulesValid check name is valid on additional rules | // CheckGitRefAdditionalRulesValid check name is valid on additional rules | ||||
| @@ -53,6 +57,7 @@ func AddBindingRules() { | |||||
| addGitRefNameBindingRule() | addGitRefNameBindingRule() | ||||
| addValidURLBindingRule() | addValidURLBindingRule() | ||||
| addGlobPatternRule() | addGlobPatternRule() | ||||
| addAlphaDashDotChineseRule() | |||||
| } | } | ||||
| func addGitRefNameBindingRule() { | func addGitRefNameBindingRule() { | ||||
| @@ -117,6 +122,21 @@ func addGlobPatternRule() { | |||||
| }) | }) | ||||
| } | } | ||||
| func addAlphaDashDotChineseRule() { | |||||
| binding.AddRule(&binding.Rule{ | |||||
| IsMatch: func(rule string) bool { | |||||
| return strings.HasPrefix(rule, "AlphaDashDotChinese") | |||||
| }, | |||||
| IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { | |||||
| if !ValidAlphaDashDotChinese(fmt.Sprintf("%v", val)) { | |||||
| errs.Add([]string{name}, ErrAlphaDashDotChinese, "ErrAlphaDashDotChinese") | |||||
| return false, errs | |||||
| } | |||||
| return true, errs | |||||
| }, | |||||
| }) | |||||
| } | |||||
| func portOnly(hostport string) string { | func portOnly(hostport string) string { | ||||
| colon := strings.IndexByte(hostport, ':') | colon := strings.IndexByte(hostport, ':') | ||||
| if colon == -1 { | if colon == -1 { | ||||
| @@ -139,3 +159,7 @@ func validPort(p string) bool { | |||||
| } | } | ||||
| return true | return true | ||||
| } | } | ||||
| func ValidAlphaDashDotChinese(value string) bool { | |||||
| return AlphaDashDotChinese.MatchString(value) | |||||
| } | |||||
| @@ -50,6 +50,7 @@ repository = Repository | |||||
| organization = Organization | organization = Organization | ||||
| mirror = Mirror | mirror = Mirror | ||||
| new_repo = New Repository | new_repo = New Repository | ||||
| new_course=Publish Course | |||||
| new_migrate = New Migration | new_migrate = New Migration | ||||
| new_dataset = New Dataset | new_dataset = New Dataset | ||||
| edit_dataset = Edit Dataset | edit_dataset = Edit Dataset | ||||
| @@ -346,7 +347,9 @@ modify = Update | |||||
| [form] | [form] | ||||
| UserName = Username | UserName = Username | ||||
| RepoName = Repository name | |||||
| Alias = Repository name | |||||
| RepoPath = Repository path | |||||
| RepoAdress = Repository Adress | |||||
| Email = Email address | Email = Email address | ||||
| Password = Password | Password = Password | ||||
| Retype = Re-Type Password | Retype = Re-Type Password | ||||
| @@ -370,7 +373,10 @@ SSPIDefaultLanguage = Default Language | |||||
| require_error = ` cannot be empty.` | require_error = ` cannot be empty.` | ||||
| alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.` | alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.` | ||||
| alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.` | alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.` | ||||
| reponame_dash_dot_error=` Please enter Chinese, alphanumeric, dash ('-') ,underscore ('_') and dot ('.')characters, up to 100 characters. ` | |||||
| repoadd_dash_dot_error=` Path only allows input alphanumeric, dash ('-') ,underscore ('_') and dot ('.')characters, up to 100 characters. ` | |||||
| git_ref_name_error = ` must be a well-formed Git reference name.` | git_ref_name_error = ` must be a well-formed Git reference name.` | ||||
| alpha_dash_dot_chinese_error= ` should contain only alphanumeric, chinese, dash ('-') and underscore ('_') characters.` | |||||
| size_error = ` must be size %s.` | size_error = ` must be size %s.` | ||||
| min_size_error = ` must contain at least %s characters.` | min_size_error = ` must contain at least %s characters.` | ||||
| max_size_error = ` must contain at most %s characters.` | max_size_error = ` must contain at most %s characters.` | ||||
| @@ -384,7 +390,8 @@ password_not_match = The passwords do not match. | |||||
| lang_select_error = Select a language from the list. | lang_select_error = Select a language from the list. | ||||
| username_been_taken = The username is already taken. | username_been_taken = The username is already taken. | ||||
| repo_name_been_taken = The repository name is already used. | |||||
| repo_name_been_taken = The repository name or path is already used. | |||||
| course_name_been_taken=The course path is already used. | |||||
| visit_rate_limit = Remote visit addressed rate limitation. | visit_rate_limit = Remote visit addressed rate limitation. | ||||
| 2fa_auth_required = Remote visit required two factors authentication. | 2fa_auth_required = Remote visit required two factors authentication. | ||||
| org_name_been_taken = The organization name is already taken. | org_name_been_taken = The organization name is already taken. | ||||
| @@ -797,6 +804,8 @@ readme = README | |||||
| readme_helper = Select a README file template. | readme_helper = Select a README file template. | ||||
| auto_init = Initialize Repository (Adds .gitignore, License and README) | auto_init = Initialize Repository (Adds .gitignore, License and README) | ||||
| create_repo = Create Repository | create_repo = Create Repository | ||||
| create_course = Publish Course | |||||
| failed_to_create_course=Fail to publish course, please try again later. | |||||
| default_branch = Default Branch | default_branch = Default Branch | ||||
| mirror_prune = Prune | mirror_prune = Prune | ||||
| mirror_prune_desc = Remove obsolete remote-tracking references | mirror_prune_desc = Remove obsolete remote-tracking references | ||||
| @@ -894,7 +903,7 @@ modelarts.train_job.description=Description | |||||
| modelarts.train_job.parameter_setting=Parameter setting | modelarts.train_job.parameter_setting=Parameter setting | ||||
| modelarts.train_job.parameter_setting_info=Parameter Info | modelarts.train_job.parameter_setting_info=Parameter Info | ||||
| modelarts.train_job.fast_parameter_setting=fast_parameter_setting | modelarts.train_job.fast_parameter_setting=fast_parameter_setting | ||||
| modelarts.train_job.fast_parameter_setting_config=fast_parameter_setting_config | |||||
| modelarts.train_job.fast_parameter_setting_config=fast_parameter_setting_config | |||||
| modelarts.train_job.fast_parameter_setting_config_link=fast_parameter_setting_config_link | modelarts.train_job.fast_parameter_setting_config_link=fast_parameter_setting_config_link | ||||
| modelarts.train_job.frames=frames | modelarts.train_job.frames=frames | ||||
| modelarts.train_job.algorithm_origin=Algorithm Origin | modelarts.train_job.algorithm_origin=Algorithm Origin | ||||
| @@ -954,14 +963,21 @@ template.avatar = Avatar | |||||
| template.issue_labels = Issue Labels | template.issue_labels = Issue Labels | ||||
| template.one_item = Must select at least one template item | template.one_item = Must select at least one template item | ||||
| template.invalid = Must select a template repository | template.invalid = Must select a template repository | ||||
| template.repo_adress=Adress | |||||
| template.repo_path=path | |||||
| template.repo_name=Name | |||||
| archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests. | archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests. | ||||
| archive.issue.nocomment = This repo is archived. You cannot comment on issues. | archive.issue.nocomment = This repo is archived. You cannot comment on issues. | ||||
| archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. | archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. | ||||
| form.reach_limit_of_creation = You have already reached your limit of %d repositories. | form.reach_limit_of_creation = You have already reached your limit of %d repositories. | ||||
| form.reach_limit_of_course_creation=You have already reached your limit of %d courses or repositories. | |||||
| form.name_reserved = The repository name '%s' is reserved. | form.name_reserved = The repository name '%s' is reserved. | ||||
| form.course_name_reserved=The course name '%s' is reserved. | |||||
| form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. | form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. | ||||
| form.course_name_pattern_not_allowed=The pattern '%s' is not allowed in a course name. | |||||
| add_course_org_fail=Fail to add organization, please try again later. | |||||
| need_auth = Clone Authorization | need_auth = Clone Authorization | ||||
| migrate_type = Migration Type | migrate_type = Migration Type | ||||
| @@ -1174,7 +1190,7 @@ issues.filter_label_exclude = `Use <code>alt</code> + <code>click/enter</code> t | |||||
| issues.filter_label_no_select = All labels | issues.filter_label_no_select = All labels | ||||
| issues.filter_milestone = Milestone | issues.filter_milestone = Milestone | ||||
| issues.filter_milestone_no_select = All milestones | issues.filter_milestone_no_select = All milestones | ||||
| issues.filter_milestone_no_add = Not add milestones | |||||
| issues.filter_milestone_no_add = Not add milestones | |||||
| issues.filter_assignee = Assignee | issues.filter_assignee = Assignee | ||||
| issues.filter_assginee_no_select = All assignees | issues.filter_assginee_no_select = All assignees | ||||
| issues.filter_type = Type | issues.filter_type = Type | ||||
| @@ -50,6 +50,7 @@ repository=项目 | |||||
| organization=组织 | organization=组织 | ||||
| mirror=镜像 | mirror=镜像 | ||||
| new_repo=创建项目 | new_repo=创建项目 | ||||
| new_course=发布课程 | |||||
| new_dataset=创建数据集 | new_dataset=创建数据集 | ||||
| new_migrate=迁移外部项目 | new_migrate=迁移外部项目 | ||||
| edit_dataset = Edit Dataset | edit_dataset = Edit Dataset | ||||
| @@ -350,7 +351,10 @@ modify=更新 | |||||
| [form] | [form] | ||||
| UserName=用户名 | UserName=用户名 | ||||
| RepoName=项目名称 | |||||
| RepoName=项目路径 | |||||
| Alias=项目名称 | |||||
| RepoPath=项目路径 | |||||
| RepoAdress=项目地址 | |||||
| Email=邮箱地址 | Email=邮箱地址 | ||||
| Password=密码 | Password=密码 | ||||
| Retype=重新输入密码 | Retype=重新输入密码 | ||||
| @@ -374,7 +378,10 @@ SSPIDefaultLanguage=默认语言 | |||||
| require_error=不能为空。 | require_error=不能为空。 | ||||
| alpha_dash_error=应该只包含字母数字、破折号 ('-') 和下划线 ('_') 字符。 | alpha_dash_error=应该只包含字母数字、破折号 ('-') 和下划线 ('_') 字符。 | ||||
| alpha_dash_dot_error=应该只包含字母数字, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。 | alpha_dash_dot_error=应该只包含字母数字, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。 | ||||
| reponame_dash_dot_error=请输入中文、字母、数字和-_.、最多100个字符。 | |||||
| repoadd_dash_dot_error=路径只允许字母、数字和-_.,最多100个字符。 | |||||
| git_ref_name_error=` 必须是格式良好的 git 引用名称。` | git_ref_name_error=` 必须是格式良好的 git 引用名称。` | ||||
| alpha_dash_dot_chinese_error=应该只包含字母数字中文, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。 | |||||
| size_error=长度必须为 %s。 | size_error=长度必须为 %s。 | ||||
| min_size_error=长度最小为 %s 个字符。 | min_size_error=长度最小为 %s 个字符。 | ||||
| max_size_error=长度最大为 %s 个字符。 | max_size_error=长度最大为 %s 个字符。 | ||||
| @@ -388,7 +395,8 @@ password_not_match=密码不匹配。 | |||||
| lang_select_error=从列表中选出语言 | lang_select_error=从列表中选出语言 | ||||
| username_been_taken=用户名已被使用。 | username_been_taken=用户名已被使用。 | ||||
| repo_name_been_taken=项目名称已被使用。 | |||||
| repo_name_been_taken=项目名称或项目路径已被使用。 | |||||
| course_name_been_taken=课程名称或路径已被使用。 | |||||
| visit_rate_limit=远程访问达到速度限制。 | visit_rate_limit=远程访问达到速度限制。 | ||||
| 2fa_auth_required=远程访问需要双重验证。 | 2fa_auth_required=远程访问需要双重验证。 | ||||
| org_name_been_taken=组织名称已被使用。 | org_name_been_taken=组织名称已被使用。 | ||||
| @@ -802,6 +810,8 @@ readme=自述 | |||||
| readme_helper=选择自述文件模板。 | readme_helper=选择自述文件模板。 | ||||
| auto_init=初始化存储库 (添加. gitignore、许可证和自述文件) | auto_init=初始化存储库 (添加. gitignore、许可证和自述文件) | ||||
| create_repo=创建项目 | create_repo=创建项目 | ||||
| create_course=发布课程 | |||||
| failed_to_create_course=发布课程失败,请稍后再试。 | |||||
| default_branch=默认分支 | default_branch=默认分支 | ||||
| mirror_prune=修剪 | mirror_prune=修剪 | ||||
| mirror_prune_desc=删除过时的远程跟踪引用 | mirror_prune_desc=删除过时的远程跟踪引用 | ||||
| @@ -965,14 +975,21 @@ template.avatar=头像 | |||||
| template.issue_labels=任务标签 | template.issue_labels=任务标签 | ||||
| template.one_item=必须至少选择一个模板项 | template.one_item=必须至少选择一个模板项 | ||||
| template.invalid=必须选择一个模板项目 | template.invalid=必须选择一个模板项目 | ||||
| template.repo_adress=项目地址 | |||||
| template.repo_path=项目地址 | |||||
| template.repo_name=项目名称 | |||||
| archive.title=此项目已存档。您可以查看文件和克隆,但不能推送或创建任务/合并请求。 | archive.title=此项目已存档。您可以查看文件和克隆,但不能推送或创建任务/合并请求。 | ||||
| archive.issue.nocomment=此项目已存档,您不能在此任务添加评论。 | archive.issue.nocomment=此项目已存档,您不能在此任务添加评论。 | ||||
| archive.pull.nocomment=此项目已存档,您不能在此合并请求添加评论。 | archive.pull.nocomment=此项目已存档,您不能在此合并请求添加评论。 | ||||
| form.reach_limit_of_creation=你已经达到了您的 %d 项目的限制。 | form.reach_limit_of_creation=你已经达到了您的 %d 项目的限制。 | ||||
| form.reach_limit_of_course_creation=你已经达到了您的 %d 课程的限制。 | |||||
| form.name_reserved=项目名称 '%s' 是被保留的。 | form.name_reserved=项目名称 '%s' 是被保留的。 | ||||
| form.course_name_reserved=课程名称 '%s' 是被保留的。 | |||||
| form.name_pattern_not_allowed=项目名称中不允许使用模式 "%s"。 | form.name_pattern_not_allowed=项目名称中不允许使用模式 "%s"。 | ||||
| form.course_name_pattern_not_allowed=课程名称中不允许使用模式 "%s"。 | |||||
| add_course_org_fail=加入组织失败,请稍后重试。 | |||||
| need_auth=需要授权验证 | need_auth=需要授权验证 | ||||
| migrate_type=迁移类型 | migrate_type=迁移类型 | ||||
| @@ -118,7 +118,7 @@ func CreateFork(ctx *context.APIContext, form api.CreateForkOption) { | |||||
| forker = org | forker = org | ||||
| } | } | ||||
| fork, err := repo_service.ForkRepository(ctx.User, forker, repo, repo.Name, repo.Description) | |||||
| fork, err := repo_service.ForkRepository(ctx.User, forker, repo, repo.Name, repo.Description, repo.Alias) | |||||
| if err != nil { | if err != nil { | ||||
| ctx.Error(http.StatusInternalServerError, "ForkRepository", err) | ctx.Error(http.StatusInternalServerError, "ForkRepository", err) | ||||
| return | return | ||||
| @@ -7,11 +7,11 @@ package routers | |||||
| import ( | import ( | ||||
| "bytes" | "bytes" | ||||
| "fmt" | |||||
| "io/ioutil" | |||||
| "net/http" | "net/http" | ||||
| "strings" | "strings" | ||||
| "code.gitea.io/gitea/services/repository" | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
| "code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
| @@ -133,6 +133,7 @@ type RepoSearchOptions struct { | |||||
| Restricted bool | Restricted bool | ||||
| PageSize int | PageSize int | ||||
| TplName base.TplName | TplName base.TplName | ||||
| Course util.OptionalBool | |||||
| } | } | ||||
| var ( | var ( | ||||
| @@ -211,6 +212,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { | |||||
| AllLimited: true, | AllLimited: true, | ||||
| TopicName: topic, | TopicName: topic, | ||||
| IncludeDescription: setting.UI.SearchRepoDescription, | IncludeDescription: setting.UI.SearchRepoDescription, | ||||
| Course: opts.Course, | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.ServerError("SearchRepository", err) | ctx.ServerError("SearchRepository", err) | ||||
| @@ -559,7 +561,7 @@ func NotFound(ctx *context.Context) { | |||||
| func RecommendOrgFromPromote(ctx *context.Context) { | func RecommendOrgFromPromote(ctx *context.Context) { | ||||
| url := setting.RecommentRepoAddr + "organizations" | url := setting.RecommentRepoAddr + "organizations" | ||||
| result, err := recommendFromPromote(url) | |||||
| result, err := repository.RecommendFromPromote(url) | |||||
| if err != nil { | if err != nil { | ||||
| ctx.ServerError("500", err) | ctx.ServerError("500", err) | ||||
| return | return | ||||
| @@ -586,62 +588,11 @@ func RecommendOrgFromPromote(ctx *context.Context) { | |||||
| ctx.JSON(200, resultOrg) | ctx.JSON(200, resultOrg) | ||||
| } | } | ||||
| func recommendFromPromote(url string) ([]string, error) { | |||||
| resp, err := http.Get(url) | |||||
| if err != nil || resp.StatusCode != 200 { | |||||
| log.Info("Get organizations url error=" + err.Error()) | |||||
| return nil, err | |||||
| } | |||||
| bytes, err := ioutil.ReadAll(resp.Body) | |||||
| resp.Body.Close() | |||||
| if err != nil { | |||||
| log.Info("Get organizations url error=" + err.Error()) | |||||
| return nil, err | |||||
| } | |||||
| allLineStr := string(bytes) | |||||
| lines := strings.Split(allLineStr, "\n") | |||||
| result := make([]string, len(lines)) | |||||
| for i, line := range lines { | |||||
| log.Info("i=" + fmt.Sprint(i) + " line=" + line) | |||||
| result[i] = strings.Trim(line, " ") | |||||
| } | |||||
| return result, nil | |||||
| } | |||||
| func RecommendRepoFromPromote(ctx *context.Context) { | func RecommendRepoFromPromote(ctx *context.Context) { | ||||
| url := setting.RecommentRepoAddr + "projects" | |||||
| result, err := recommendFromPromote(url) | |||||
| result, err := repository.GetRecommendRepoFromPromote("projects") | |||||
| if err != nil { | if err != nil { | ||||
| ctx.ServerError("500", err) | ctx.ServerError("500", err) | ||||
| return | |||||
| } | |||||
| resultRepo := make([]map[string]interface{}, 0) | |||||
| //resultRepo := make([]*models.Repository, 0) | |||||
| for _, repoName := range result { | |||||
| tmpIndex := strings.Index(repoName, "/") | |||||
| if tmpIndex == -1 { | |||||
| log.Info("error repo name format.") | |||||
| } else { | |||||
| ownerName := strings.Trim(repoName[0:tmpIndex], " ") | |||||
| repoName := strings.Trim(repoName[tmpIndex+1:], " ") | |||||
| repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | |||||
| if err == nil { | |||||
| repoMap := make(map[string]interface{}) | |||||
| repoMap["ID"] = fmt.Sprint(repo.ID) | |||||
| repoMap["Name"] = repo.Name | |||||
| repoMap["OwnerName"] = repo.OwnerName | |||||
| repoMap["NumStars"] = repo.NumStars | |||||
| repoMap["NumForks"] = repo.NumForks | |||||
| repoMap["Description"] = repo.Description | |||||
| repoMap["NumWatchs"] = repo.NumWatches | |||||
| repoMap["Topics"] = repo.Topics | |||||
| repoMap["Avatar"] = repo.RelAvatarLink() | |||||
| resultRepo = append(resultRepo, repoMap) | |||||
| } else { | |||||
| log.Info("query repo error," + err.Error()) | |||||
| } | |||||
| } | |||||
| } else { | |||||
| ctx.JSON(200, result) | |||||
| } | } | ||||
| ctx.JSON(200, resultRepo) | |||||
| } | } | ||||
| @@ -7,6 +7,10 @@ package org | |||||
| import ( | import ( | ||||
| "strings" | "strings" | ||||
| "code.gitea.io/gitea/services/repository" | |||||
| "code.gitea.io/gitea/modules/util" | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
| "code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
| @@ -14,7 +18,8 @@ import ( | |||||
| ) | ) | ||||
| const ( | const ( | ||||
| tplOrgHome base.TplName = "org/home" | |||||
| tplOrgHome base.TplName = "org/home" | |||||
| tplOrgCourseHome base.TplName = "org/home_courses" | |||||
| ) | ) | ||||
| // Home show organization home page | // Home show organization home page | ||||
| @@ -59,10 +64,16 @@ func Home(ctx *context.Context) { | |||||
| case "fewestforks": | case "fewestforks": | ||||
| orderBy = models.SearchOrderByForks | orderBy = models.SearchOrderByForks | ||||
| default: | default: | ||||
| ctx.Data["SortType"] = "recentupdate" | |||||
| orderBy = models.SearchOrderByRecentUpdated | |||||
| } | |||||
| if setting.Course.OrgName == org.Name { | |||||
| ctx.Data["SortType"] = "newest" | |||||
| orderBy = models.SearchOrderByNewest | |||||
| } else { | |||||
| ctx.Data["SortType"] = "recentupdate" | |||||
| orderBy = models.SearchOrderByRecentUpdated | |||||
| } | |||||
| } | |||||
| orderBy = orderBy + ",id" | |||||
| keyword := strings.Trim(ctx.Query("q"), " ") | keyword := strings.Trim(ctx.Query("q"), " ") | ||||
| ctx.Data["Keyword"] = keyword | ctx.Data["Keyword"] = keyword | ||||
| @@ -76,9 +87,21 @@ func Home(ctx *context.Context) { | |||||
| count int64 | count int64 | ||||
| err error | err error | ||||
| ) | ) | ||||
| pageSize := setting.UI.User.RepoPagingNum | |||||
| var CourseOptional util.OptionalBool = util.OptionalBoolNone | |||||
| if setting.Course.OrgName == org.Name { | |||||
| pageSize = 15 | |||||
| CourseOptional = util.OptionalBoolTrue | |||||
| recommendCourses, _ := repository.GetRecommendRepoFromPromote("courses") | |||||
| ctx.Data["RecommendCourses"] = recommendCourses | |||||
| recommendCourseKeyWords, _ := repository.GetRecommendCourseKeyWords() | |||||
| ctx.Data["CoursesKeywords"] = recommendCourseKeyWords | |||||
| } | |||||
| repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | ||||
| ListOptions: models.ListOptions{ | ListOptions: models.ListOptions{ | ||||
| PageSize: setting.UI.User.RepoPagingNum, | |||||
| PageSize: pageSize, | |||||
| Page: page, | Page: page, | ||||
| }, | }, | ||||
| Keyword: keyword, | Keyword: keyword, | ||||
| @@ -87,6 +110,7 @@ func Home(ctx *context.Context) { | |||||
| Private: ctx.IsSigned, | Private: ctx.IsSigned, | ||||
| Actor: ctx.User, | Actor: ctx.User, | ||||
| IncludeDescription: setting.UI.SearchRepoDescription, | IncludeDescription: setting.UI.SearchRepoDescription, | ||||
| Course: CourseOptional, | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.ServerError("SearchRepository", err) | ctx.ServerError("SearchRepository", err) | ||||
| @@ -138,5 +162,10 @@ func Home(ctx *context.Context) { | |||||
| } | } | ||||
| ctx.Data["tags"] = tags | ctx.Data["tags"] = tags | ||||
| ctx.HTML(200, tplOrgHome) | |||||
| if setting.Course.OrgName == org.Name { | |||||
| ctx.HTML(200, tplOrgCourseHome) | |||||
| } else { | |||||
| ctx.HTML(200, tplOrgHome) | |||||
| } | |||||
| } | } | ||||
| @@ -17,7 +17,8 @@ import ( | |||||
| const ( | const ( | ||||
| // tplMembers template for organization members page | // tplMembers template for organization members page | ||||
| tplMembers base.TplName = "org/member/members" | |||||
| tplMembers base.TplName = "org/member/members" | |||||
| tplCourseMembers base.TplName = "org/member/course_members" | |||||
| ) | ) | ||||
| // Members render organization users page | // Members render organization users page | ||||
| @@ -64,8 +65,11 @@ func Members(ctx *context.Context) { | |||||
| ctx.Data["MembersIsPublicMember"] = membersIsPublic | ctx.Data["MembersIsPublicMember"] = membersIsPublic | ||||
| ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID) | ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID) | ||||
| ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus() | ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus() | ||||
| ctx.HTML(200, tplMembers) | |||||
| if setting.Course.OrgName == org.Name { | |||||
| ctx.HTML(200, tplCourseMembers) | |||||
| } else { | |||||
| ctx.HTML(200, tplMembers) | |||||
| } | |||||
| } | } | ||||
| // MembersAction response for operation to a member of organization | // MembersAction response for operation to a member of organization | ||||
| @@ -10,6 +10,8 @@ import ( | |||||
| "path" | "path" | ||||
| "strings" | "strings" | ||||
| "code.gitea.io/gitea/modules/setting" | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/auth" | "code.gitea.io/gitea/modules/auth" | ||||
| "code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
| @@ -22,7 +24,8 @@ import ( | |||||
| const ( | const ( | ||||
| // tplTeams template path for teams list page | // tplTeams template path for teams list page | ||||
| tplTeams base.TplName = "org/team/teams" | |||||
| tplTeams base.TplName = "org/team/teams" | |||||
| tplCourseTeams base.TplName = "org/team/courseTeams" | |||||
| // tplTeamNew template path for create new team page | // tplTeamNew template path for create new team page | ||||
| tplTeamNew base.TplName = "org/team/new" | tplTeamNew base.TplName = "org/team/new" | ||||
| // tplTeamMembers template path for showing team members page | // tplTeamMembers template path for showing team members page | ||||
| @@ -44,8 +47,12 @@ func Teams(ctx *context.Context) { | |||||
| } | } | ||||
| } | } | ||||
| ctx.Data["Teams"] = org.Teams | ctx.Data["Teams"] = org.Teams | ||||
| if setting.Course.OrgName == org.Name { | |||||
| ctx.HTML(200, tplCourseTeams) | |||||
| } else { | |||||
| ctx.HTML(200, tplTeams) | |||||
| } | |||||
| ctx.HTML(200, tplTeams) | |||||
| } | } | ||||
| // TeamsAction response for join, leave, remove, add operations to team | // TeamsAction response for join, leave, remove, add operations to team | ||||
| @@ -0,0 +1,189 @@ | |||||
| package repo | |||||
| import ( | |||||
| "net/http" | |||||
| "strings" | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/auth" | |||||
| "code.gitea.io/gitea/modules/base" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| repo_service "code.gitea.io/gitea/services/repository" | |||||
| ) | |||||
| const ( | |||||
| tplCreateCourse base.TplName = "repo/createCourse" | |||||
| ) | |||||
| func CreateCourse(ctx *context.Context) { | |||||
| ctx.Data["Title"] = ctx.Tr("new_course") | |||||
| ctx.Data["Owner"] = setting.Course.OrgName | |||||
| ctx.HTML(200, tplCreateCourse) | |||||
| } | |||||
| func CreateCoursePost(ctx *context.Context, form auth.CreateCourseForm) { | |||||
| ctx.Data["Title"] = ctx.Tr("new_course") | |||||
| if ctx.Written() { | |||||
| return | |||||
| } | |||||
| var topics = make([]string, 0) | |||||
| var topicsStr = strings.TrimSpace(form.Topics) | |||||
| if len(topicsStr) > 0 { | |||||
| topics = strings.Split(topicsStr, ",") | |||||
| } | |||||
| validTopics, invalidTopics := models.SanitizeAndValidateTopics(topics) | |||||
| if len(validTopics) > 25 { | |||||
| ctx.RenderWithErr(ctx.Tr("repo.topic.count_prompt"), tplCreateCourse, form) | |||||
| return | |||||
| } | |||||
| if len(invalidTopics) > 0 { | |||||
| ctx.RenderWithErr(ctx.Tr("repo.topic.format_prompt"), tplCreateCourse, form) | |||||
| return | |||||
| } | |||||
| var repo *models.Repository | |||||
| var err error | |||||
| if setting.Course.OrgName == "" || setting.Course.TeamName == "" { | |||||
| log.Error("then organization name or team name of course is empty.") | |||||
| ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form) | |||||
| return | |||||
| } | |||||
| org, team, err := getOrgAndTeam() | |||||
| if err != nil { | |||||
| log.Error("Failed to get team from db.", err) | |||||
| ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form) | |||||
| return | |||||
| } | |||||
| isInTeam, err := models.IsUserInTeams(ctx.User.ID, []int64{team.ID}) | |||||
| if err != nil { | |||||
| log.Error("Failed to get user in team from db.") | |||||
| ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form) | |||||
| return | |||||
| } | |||||
| if !isInTeam { | |||||
| err = models.AddTeamMember(team, ctx.User.ID) | |||||
| if err != nil { | |||||
| log.Error("Failed to add user to team.") | |||||
| ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form) | |||||
| return | |||||
| } | |||||
| } | |||||
| if ctx.HasError() { | |||||
| ctx.HTML(200, tplCreateCourse) | |||||
| return | |||||
| } | |||||
| repo, err = repo_service.CreateRepository(ctx.User, org, models.CreateRepoOptions{ | |||||
| Name: form.RepoName, | |||||
| Alias: form.Alias, | |||||
| Description: form.Description, | |||||
| Gitignores: "", | |||||
| IssueLabels: "", | |||||
| License: "", | |||||
| Readme: "Default", | |||||
| IsPrivate: false, | |||||
| DefaultBranch: "master", | |||||
| AutoInit: true, | |||||
| IsCourse: true, | |||||
| Topics: validTopics, | |||||
| }) | |||||
| if err == nil { | |||||
| log.Trace("Repository created [%d]: %s/%s", repo.ID, org.Name, repo.Name) | |||||
| ctx.Redirect(setting.AppSubURL + "/" + org.Name + "/" + repo.Name) | |||||
| return | |||||
| } | |||||
| handleCreateCourseError(ctx, org, err, "CreateCoursePost", tplCreateCourse, &form) | |||||
| } | |||||
| func AddCourseOrg(ctx *context.Context) { | |||||
| _, team, err := getOrgAndTeam() | |||||
| if err != nil { | |||||
| log.Error("Failed to get team from db.", err) | |||||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||||
| "code": 1, | |||||
| "message": ctx.Tr("repo.addCourseOrgFail"), | |||||
| }) | |||||
| return | |||||
| } | |||||
| isInTeam, err := models.IsUserInTeams(ctx.User.ID, []int64{team.ID}) | |||||
| if err != nil { | |||||
| log.Error("Failed to get user in team from db.", err) | |||||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||||
| "code": 1, | |||||
| "message": ctx.Tr("repo.add_course_org_fail"), | |||||
| }) | |||||
| return | |||||
| } | |||||
| if !isInTeam { | |||||
| err = models.AddTeamMember(team, ctx.User.ID) | |||||
| if err != nil { | |||||
| log.Error("Failed to add user to team.", err) | |||||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||||
| "code": 1, | |||||
| "message": ctx.Tr("repo.add_course_org_fail"), | |||||
| }) | |||||
| return | |||||
| } | |||||
| } | |||||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||||
| "code": 0, | |||||
| "message": "", | |||||
| }) | |||||
| } | |||||
| func getOrgAndTeam() (*models.User, *models.Team, error) { | |||||
| org, err := models.GetUserByName(setting.Course.OrgName) | |||||
| if err != nil { | |||||
| log.Error("Failed to get organization from db.", err) | |||||
| return nil, nil, err | |||||
| } | |||||
| team, err := models.GetTeam(org.ID, setting.Course.TeamName) | |||||
| if err != nil { | |||||
| log.Error("Failed to get team from db.", err) | |||||
| return nil, nil, err | |||||
| } | |||||
| return org, team, nil | |||||
| } | |||||
| func handleCreateCourseError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) { | |||||
| switch { | |||||
| case models.IsErrReachLimitOfRepo(err): | |||||
| ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_course_creation", owner.MaxCreationLimit()), tpl, form) | |||||
| case models.IsErrRepoAlreadyExist(err): | |||||
| ctx.Data["Err_RepoName"] = true | |||||
| ctx.RenderWithErr(ctx.Tr("form.course_name_been_taken"), tpl, form) | |||||
| case models.IsErrNameReserved(err): | |||||
| ctx.Data["Err_RepoName"] = true | |||||
| ctx.RenderWithErr(ctx.Tr("repo.form.course_name_reserved", err.(models.ErrNameReserved).Name), tpl, form) | |||||
| case models.IsErrNamePatternNotAllowed(err): | |||||
| ctx.Data["Err_RepoName"] = true | |||||
| ctx.RenderWithErr(ctx.Tr("repo.form.course_name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) | |||||
| default: | |||||
| ctx.ServerError(name, err) | |||||
| } | |||||
| } | |||||
| @@ -105,6 +105,7 @@ func getForkRepository(ctx *context.Context) *models.Repository { | |||||
| return nil | return nil | ||||
| } | } | ||||
| ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name | ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name | ||||
| ctx.Data["ForkDisplayName"] = forkRepo.FullDisplayName() | |||||
| ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID | ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID | ||||
| if err := ctx.User.GetOwnedOrganizations(); err != nil { | if err := ctx.User.GetOwnedOrganizations(); err != nil { | ||||
| @@ -221,7 +222,7 @@ func ForkPost(ctx *context.Context, form auth.CreateRepoForm) { | |||||
| } | } | ||||
| } | } | ||||
| repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description) | |||||
| repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description, form.Alias) | |||||
| if err != nil { | if err != nil { | ||||
| ctx.Data["Err_RepoName"] = true | ctx.Data["Err_RepoName"] = true | ||||
| switch { | switch { | ||||
| @@ -6,10 +6,12 @@ | |||||
| package repo | package repo | ||||
| import ( | import ( | ||||
| "code.gitea.io/gitea/modules/validation" | |||||
| "fmt" | "fmt" | ||||
| "net/url" | "net/url" | ||||
| "os" | "os" | ||||
| "path" | "path" | ||||
| "regexp" | |||||
| "strings" | "strings" | ||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| @@ -201,6 +203,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { | |||||
| if form.RepoTemplate > 0 { | if form.RepoTemplate > 0 { | ||||
| opts := models.GenerateRepoOptions{ | opts := models.GenerateRepoOptions{ | ||||
| Name: form.RepoName, | Name: form.RepoName, | ||||
| Alias: form.Alias, | |||||
| Description: form.Description, | Description: form.Description, | ||||
| Private: form.Private, | Private: form.Private, | ||||
| GitContent: form.GitContent, | GitContent: form.GitContent, | ||||
| @@ -235,6 +238,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { | |||||
| } else { | } else { | ||||
| repo, err = repo_service.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ | repo, err = repo_service.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ | ||||
| Name: form.RepoName, | Name: form.RepoName, | ||||
| Alias: form.Alias, | |||||
| Description: form.Description, | Description: form.Description, | ||||
| Gitignores: form.Gitignores, | Gitignores: form.Gitignores, | ||||
| IssueLabels: form.IssueLabels, | IssueLabels: form.IssueLabels, | ||||
| @@ -358,6 +362,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { | |||||
| GitServiceType: gitServiceType, | GitServiceType: gitServiceType, | ||||
| CloneAddr: remoteAddr, | CloneAddr: remoteAddr, | ||||
| RepoName: form.RepoName, | RepoName: form.RepoName, | ||||
| Alias: form.Alias, | |||||
| Description: form.Description, | Description: form.Description, | ||||
| Private: form.Private || setting.Repository.ForcePrivate, | Private: form.Private || setting.Repository.ForcePrivate, | ||||
| Mirror: form.Mirror, | Mirror: form.Mirror, | ||||
| @@ -380,7 +385,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { | |||||
| opts.Releases = false | opts.Releases = false | ||||
| } | } | ||||
| err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName) | |||||
| err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, opts.Alias) | |||||
| if err != nil { | if err != nil { | ||||
| handleMigrateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form) | handleMigrateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form) | ||||
| return | return | ||||
| @@ -552,3 +557,27 @@ func Status(ctx *context.Context) { | |||||
| "err": task.Errors, | "err": task.Errors, | ||||
| }) | }) | ||||
| } | } | ||||
| var repoNamePattern = regexp.MustCompile("^[0-9a-zA-Z\\.\\-_]{1,100}$") | |||||
| var repoAliasPattern = regexp.MustCompile("^[\u4e00-\u9fa5\\.\\-_A-Za-z0-9]{1,100}$") | |||||
| // CheckName returns repository's default name(by given alias) | |||||
| func CheckName(ctx *context.Context) { | |||||
| var r = make(map[string]string, 1) | |||||
| q := ctx.Query("q") | |||||
| owner := ctx.Query("owner") | |||||
| if q == "" || owner == "" || len(q) > 100 || !validation.ValidAlphaDashDotChinese(q) { | |||||
| r["name"] = "" | |||||
| ctx.JSON(200, r) | |||||
| return | |||||
| } | |||||
| if repoNamePattern.MatchString(q) { | |||||
| r["name"] = q | |||||
| ctx.JSON(200, r) | |||||
| return | |||||
| } | |||||
| n := models.GenerateDefaultRepoName(owner) | |||||
| r["name"] = n | |||||
| ctx.JSON(200, r) | |||||
| return | |||||
| } | |||||
| @@ -98,6 +98,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { | |||||
| repo.Description = form.Description | repo.Description = form.Description | ||||
| repo.Website = form.Website | repo.Website = form.Website | ||||
| repo.IsTemplate = form.Template | repo.IsTemplate = form.Template | ||||
| repo.Alias = form.Alias | |||||
| // Visibility of forked repository is forced sync with base repository. | // Visibility of forked repository is forced sync with base repository. | ||||
| if repo.IsFork { | if repo.IsFork { | ||||
| @@ -708,6 +708,13 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| }, reqSignIn) | }, reqSignIn) | ||||
| // ***** END: Organization ***** | // ***** END: Organization ***** | ||||
| m.Group("/course", func() { | |||||
| m.Get("/create", repo.CreateCourse) | |||||
| m.Post("/create", bindIgnErr(auth.CreateCourseForm{}), repo.CreateCoursePost) | |||||
| m.Get("/addOrg", repo.AddCourseOrg) | |||||
| }, reqSignIn) | |||||
| // ***** START: Repository ***** | // ***** START: Repository ***** | ||||
| m.Group("/repo", func() { | m.Group("/repo", func() { | ||||
| m.Get("/create", repo.Create) | m.Get("/create", repo.Create) | ||||
| @@ -718,6 +725,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Combo("/:repoid").Get(repo.Fork). | m.Combo("/:repoid").Get(repo.Fork). | ||||
| Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost) | Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost) | ||||
| }, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader) | }, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader) | ||||
| m.Get("/check_name", repo.CheckName) | |||||
| }, reqSignIn) | }, reqSignIn) | ||||
| // ***** Release Attachment Download without Signin | // ***** Release Attachment Download without Signin | ||||
| @@ -5,12 +5,17 @@ | |||||
| package repository | package repository | ||||
| import ( | import ( | ||||
| "fmt" | |||||
| "io/ioutil" | |||||
| "net/http" | |||||
| "strings" | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
| "code.gitea.io/gitea/modules/notification" | "code.gitea.io/gitea/modules/notification" | ||||
| repo_module "code.gitea.io/gitea/modules/repository" | repo_module "code.gitea.io/gitea/modules/repository" | ||||
| "code.gitea.io/gitea/modules/setting" | |||||
| pull_service "code.gitea.io/gitea/services/pull" | pull_service "code.gitea.io/gitea/services/pull" | ||||
| "fmt" | |||||
| ) | ) | ||||
| // CreateRepository creates a repository for the user/organization. | // CreateRepository creates a repository for the user/organization. | ||||
| @@ -31,8 +36,8 @@ func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) ( | |||||
| } | } | ||||
| // ForkRepository forks a repository | // ForkRepository forks a repository | ||||
| func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc string) (*models.Repository, error) { | |||||
| repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc) | |||||
| func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc, alias string) (*models.Repository, error) { | |||||
| repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc, alias) | |||||
| if err != nil { | if err != nil { | ||||
| if repo != nil { | if repo != nil { | ||||
| if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil { | if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil { | ||||
| @@ -86,3 +91,79 @@ func PushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repo | |||||
| return repo, nil | return repo, nil | ||||
| } | } | ||||
| func GetRecommendCourseKeyWords() ([]string, error) { | |||||
| url := setting.RecommentRepoAddr + "course_keywords" | |||||
| result, err := RecommendFromPromote(url) | |||||
| if err != nil { | |||||
| return []string{}, err | |||||
| } | |||||
| return result, err | |||||
| } | |||||
| func GetRecommendRepoFromPromote(filename string) ([]map[string]interface{}, error) { | |||||
| resultRepo := make([]map[string]interface{}, 0) | |||||
| url := setting.RecommentRepoAddr + filename | |||||
| result, err := RecommendFromPromote(url) | |||||
| if err != nil { | |||||
| return resultRepo, err | |||||
| } | |||||
| //resultRepo := make([]*models.Repository, 0) | |||||
| for _, repoName := range result { | |||||
| tmpIndex := strings.Index(repoName, "/") | |||||
| if tmpIndex == -1 { | |||||
| log.Info("error repo name format.") | |||||
| } else { | |||||
| ownerName := strings.Trim(repoName[0:tmpIndex], " ") | |||||
| repoName := strings.Trim(repoName[tmpIndex+1:], " ") | |||||
| repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | |||||
| if err == nil { | |||||
| repoMap := make(map[string]interface{}) | |||||
| repoMap["ID"] = fmt.Sprint(repo.ID) | |||||
| repoMap["Name"] = repo.Name | |||||
| repoMap["Alias"] = repo.Alias | |||||
| repoMap["Creator"] = repo.Creator | |||||
| repoMap["OwnerName"] = repo.OwnerName | |||||
| repoMap["NumStars"] = repo.NumStars | |||||
| repoMap["NumForks"] = repo.NumForks | |||||
| repoMap["Description"] = repo.Description | |||||
| repoMap["NumWatchs"] = repo.NumWatches | |||||
| repoMap["Topics"] = repo.Topics | |||||
| repoMap["Avatar"] = repo.RelAvatarLink() | |||||
| resultRepo = append(resultRepo, repoMap) | |||||
| } else { | |||||
| log.Info("query repo error," + err.Error()) | |||||
| } | |||||
| } | |||||
| } | |||||
| return resultRepo, nil | |||||
| } | |||||
| func RecommendFromPromote(url string) ([]string, error) { | |||||
| resp, err := http.Get(url) | |||||
| if err != nil || resp.StatusCode != 200 { | |||||
| log.Info("Get organizations url error=" + err.Error()) | |||||
| return nil, err | |||||
| } | |||||
| bytes, err := ioutil.ReadAll(resp.Body) | |||||
| resp.Body.Close() | |||||
| if err != nil { | |||||
| log.Info("Get organizations url error=" + err.Error()) | |||||
| return nil, err | |||||
| } | |||||
| allLineStr := string(bytes) | |||||
| lines := strings.Split(allLineStr, "\n") | |||||
| result := make([]string, len(lines)) | |||||
| for i, line := range lines { | |||||
| log.Info("i=" + fmt.Sprint(i) + " line=" + line) | |||||
| result[i] = strings.Trim(line, " ") | |||||
| } | |||||
| return result, nil | |||||
| } | |||||
| @@ -5,7 +5,7 @@ | |||||
| border-radius: 0.8rem; | border-radius: 0.8rem; | ||||
| margin-bottom: 1.0rem; | margin-bottom: 1.0rem; | ||||
| padding: 1.0rem !important; | padding: 1.0rem !important; | ||||
| } | |||||
| } | |||||
| .ui.repository.list>.item .header { | .ui.repository.list>.item .header { | ||||
| font-size: 1.4rem !important; | font-size: 1.4rem !important; | ||||
| font-weight: 200; | font-weight: 200; | ||||
| @@ -24,7 +24,7 @@ | |||||
| content: ""; | content: ""; | ||||
| height: 1px; | height: 1px; | ||||
| background-color: #E1E3E6; | background-color: #E1E3E6; | ||||
| bottom: 2.8rem; | |||||
| bottom: 2.8rem; | |||||
| } | } | ||||
| .repository .ui.mini.menu{ | .repository .ui.mini.menu{ | ||||
| font-size: .6rem; | font-size: .6rem; | ||||
| @@ -43,13 +43,13 @@ | |||||
| <a class="{{if eq .SortType "hot"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&topic={{$.Topic}}&sort=hot&tab={{$.TabName}}"> | <a class="{{if eq .SortType "hot"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&topic={{$.Topic}}&sort=hot&tab={{$.TabName}}"> | ||||
| <svg class="svg octicon-repo" width="16" height="16" aria-hidden="true"> | <svg class="svg octicon-repo" width="16" height="16" aria-hidden="true"> | ||||
| <use xlink:href="#octicon-repo" /> | <use xlink:href="#octicon-repo" /> | ||||
| </svg> | |||||
| </svg> | |||||
| 热门{{.i18n.Tr "explore.repos"}} | 热门{{.i18n.Tr "explore.repos"}} | ||||
| </a> | </a> | ||||
| <a class="{{if eq .SortType "active"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&topic={{$.Topic}}&sort=active&tab={{$.TabName}}"> | <a class="{{if eq .SortType "active"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&topic={{$.Topic}}&sort=active&tab={{$.TabName}}"> | ||||
| <svg class="svg octicon-inbox" width="16" height="16" aria-hidden="true"> | <svg class="svg octicon-inbox" width="16" height="16" aria-hidden="true"> | ||||
| <use xlink:href="#octicon-inbox" /> | <use xlink:href="#octicon-inbox" /> | ||||
| </svg> | |||||
| </svg> | |||||
| 活跃{{.i18n.Tr "explore.repos"}} | 活跃{{.i18n.Tr "explore.repos"}} | ||||
| </a> | </a> | ||||
| {{end}} | {{end}} | ||||
| @@ -93,7 +93,7 @@ | |||||
| <div class="ui grid"> | <div class="ui grid"> | ||||
| <div class="ui sixteen wide mobile ten wide tablet twelve wide computer column"> | <div class="ui sixteen wide mobile ten wide tablet twelve wide computer column"> | ||||
| <a class="name" href="{{.Link}}"> | <a class="name" href="{{.Link}}"> | ||||
| {{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} <span>/</span> {{end}}{{end}}<strong>{{.Name}}</strong> | |||||
| {{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} <span>/</span> {{end}}{{end}}<strong>{{.DisplayName}}</strong> | |||||
| {{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}} | {{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}} | ||||
| </a> | </a> | ||||
| {{if .IsPrivate}} | {{if .IsPrivate}} | ||||
| @@ -114,7 +114,7 @@ | |||||
| <a class="item"> | <a class="item"> | ||||
| <svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24"> | <svg class="svg octicon-inbox" width="16" height="16" viewBox="0 0 24 24"> | ||||
| <path fill="currentColor" d="M17.66 11.2C17.43 10.9 17.15 10.64 16.89 10.38C16.22 9.78 15.46 9.35 14.82 8.72C13.33 7.26 13 4.85 13.95 3C13 3.23 12.17 3.75 11.46 4.32C8.87 6.4 7.85 10.07 9.07 13.22C9.11 13.32 9.15 13.42 9.15 13.55C9.15 13.77 9 13.97 8.8 14.05C8.57 14.15 8.33 14.09 8.14 13.93C8.08 13.88 8.04 13.83 8 13.76C6.87 12.33 6.69 10.28 7.45 8.64C5.78 10 4.87 12.3 5 14.47C5.06 14.97 5.12 15.47 5.29 15.97C5.43 16.57 5.7 17.17 6 17.7C7.08 19.43 8.95 20.67 10.96 20.92C13.1 21.19 15.39 20.8 17.03 19.32C18.86 17.66 19.5 15 18.56 12.72L18.43 12.46C18.22 12 17.66 11.2 17.66 11.2M14.5 17.5C14.22 17.74 13.76 18 13.4 18.1C12.28 18.5 11.16 17.94 10.5 17.28C11.69 17 12.4 16.12 12.61 15.23C12.78 14.43 12.46 13.77 12.33 13C12.21 12.26 12.23 11.63 12.5 10.94C12.69 11.32 12.89 11.7 13.13 12C13.9 13 15.11 13.44 15.37 14.8C15.41 14.94 15.43 15.08 15.43 15.23C15.46 16.05 15.1 16.95 14.5 17.5H14.5Z" /> | <path fill="currentColor" d="M17.66 11.2C17.43 10.9 17.15 10.64 16.89 10.38C16.22 9.78 15.46 9.35 14.82 8.72C13.33 7.26 13 4.85 13.95 3C13 3.23 12.17 3.75 11.46 4.32C8.87 6.4 7.85 10.07 9.07 13.22C9.11 13.32 9.15 13.42 9.15 13.55C9.15 13.77 9 13.97 8.8 14.05C8.57 14.15 8.33 14.09 8.14 13.93C8.08 13.88 8.04 13.83 8 13.76C6.87 12.33 6.69 10.28 7.45 8.64C5.78 10 4.87 12.3 5 14.47C5.06 14.97 5.12 15.47 5.29 15.97C5.43 16.57 5.7 17.17 6 17.7C7.08 19.43 8.95 20.67 10.96 20.92C13.1 21.19 15.39 20.8 17.03 19.32C18.86 17.66 19.5 15 18.56 12.72L18.43 12.46C18.22 12 17.66 11.2 17.66 11.2M14.5 17.5C14.22 17.74 13.76 18 13.4 18.1C12.28 18.5 11.16 17.94 10.5 17.28C11.69 17 12.4 16.12 12.61 15.23C12.78 14.43 12.46 13.77 12.33 13C12.21 12.26 12.23 11.63 12.5 10.94C12.69 11.32 12.89 11.7 13.13 12C13.9 13 15.11 13.44 15.37 14.8C15.41 14.94 15.43 15.08 15.43 15.23C15.46 16.05 15.1 16.95 14.5 17.5H14.5Z" /> | ||||
| </svg> | |||||
| </svg> | |||||
| {{.Hot}} | {{.Hot}} | ||||
| </a> | </a> | ||||
| {{else if eq $.SortType "active"}} | {{else if eq $.SortType "active"}} | ||||
| @@ -130,7 +130,7 @@ | |||||
| <a class="item"> | <a class="item"> | ||||
| {{svg "octicon-git-branch" 16}} {{.NumForks}} | {{svg "octicon-git-branch" 16}} {{.NumForks}} | ||||
| </a> | </a> | ||||
| {{end}} | |||||
| {{end}} | |||||
| <a class="item"> | <a class="item"> | ||||
| {{svg "octicon-star" 16}} {{.NumStars}} | {{svg "octicon-star" 16}} {{.NumStars}} | ||||
| </a> | </a> | ||||
| @@ -2,43 +2,15 @@ | |||||
| <div class="repository new repo" style="margin-top: 40px;"> | <div class="repository new repo" style="margin-top: 40px;"> | ||||
| <div class="ui middle very relaxed page grid"> | <div class="ui middle very relaxed page grid"> | ||||
| <div class="column"> | <div class="column"> | ||||
| <form class="ui form" action="{{.Link}}" method="post"> | |||||
| <form class="ui form" action="{{.Link}}" method="post" id="create_repo_form"> | |||||
| {{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
| <h3 class="ui top attached header"> | <h3 class="ui top attached header"> | ||||
| {{.i18n.Tr "new_repo"}} | {{.i18n.Tr "new_repo"}} | ||||
| </h3> | </h3> | ||||
| <div class="ui attached segment"> | <div class="ui attached segment"> | ||||
| {{template "base/alert" .}} | {{template "base/alert" .}} | ||||
| <div class="inline required field {{if .Err_Owner}}error{{end}}"> | |||||
| <label>{{.i18n.Tr "repo.owner"}}</label> | |||||
| <div class="ui selection owner dropdown"> | |||||
| <input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required> | |||||
| <span class="text" title="{{.ContextUser.Name}}"> | |||||
| <img class="ui mini image" src="{{.ContextUser.RelAvatarLink}}"> | |||||
| {{.ContextUser.ShortName 20}} | |||||
| </span> | |||||
| <i class="dropdown icon"></i> | |||||
| <div class="menu"> | |||||
| <div class="item" data-value="{{.SignedUser.ID}}" title="{{.SignedUser.Name}}"> | |||||
| <img class="ui mini image" src="{{.SignedUser.RelAvatarLink}}"> | |||||
| {{.SignedUser.ShortName 20}} | |||||
| </div> | |||||
| {{range .Orgs}} | |||||
| <div class="item" data-value="{{.ID}}" title="{{.Name}}"> | |||||
| <img class="ui mini image" src="{{.RelAvatarLink}}"> | |||||
| {{.ShortName 20}} | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| <span class="help">{{.i18n.Tr "repo.repo_owner_helper"}}</span> | |||||
| </div> | |||||
| <div class="inline required field {{if .Err_RepoName}}error{{end}}"> | |||||
| <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> | |||||
| <input id="repo_name" name="repo_name" value="{{.repo_name}}" autofocus required> | |||||
| <span class="help"></span> | |||||
| </div> | |||||
| {{template "repo/repo_name" .}} | |||||
| <!-- <div class="js-project-full-path" id="repoAdress" ></div> --> | |||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label>{{.i18n.Tr "repo.visibility"}}</label> | <label>{{.i18n.Tr "repo.visibility"}}</label> | ||||
| <div class="ui checkbox"> | <div class="ui checkbox"> | ||||
| @@ -172,7 +144,7 @@ | |||||
| <br/> | <br/> | ||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label></label> | <label></label> | ||||
| <button class="ui green button"> | |||||
| <button class="ui green button" id="submit_reponame"> | |||||
| {{.i18n.Tr "repo.create_repo"}} | {{.i18n.Tr "repo.create_repo"}} | ||||
| </button> | </button> | ||||
| <a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a> | <a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a> | ||||
| @@ -24,7 +24,7 @@ | |||||
| {{end}} | {{end}} | ||||
| <a href="{{AppSubUrl}}/{{.Owner.Name}}">{{.Owner.Name}}</a> | <a href="{{AppSubUrl}}/{{.Owner.Name}}">{{.Owner.Name}}</a> | ||||
| <div class="divider"> / </div> | <div class="divider"> / </div> | ||||
| <a href="{{$.RepoLink}}">{{.Name}}</a> | |||||
| <a href="{{$.RepoLink}}">{{.DisplayName}}</a> | |||||
| {{if .RelAvatarLink}} | {{if .RelAvatarLink}} | ||||
| {{if .IsTemplate}} | {{if .IsTemplate}} | ||||
| {{if .IsPrivate}} | {{if .IsPrivate}} | ||||
| @@ -46,7 +46,7 @@ | |||||
| {{end}} | {{end}} | ||||
| {{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}} | {{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}} | ||||
| {{if .IsMirror}}<div class="fork-flag">{{$.i18n.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{if .SanitizedOriginalURL}}{{.SanitizedOriginalURL}}{{else}}{{MirrorAddress $.Mirror}}{{end}}">{{if .SanitizedOriginalURL}}{{.SanitizedOriginalURL}}{{else}}{{MirrorAddress $.Mirror}}{{end}}</a></div>{{end}} | {{if .IsMirror}}<div class="fork-flag">{{$.i18n.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{if .SanitizedOriginalURL}}{{.SanitizedOriginalURL}}{{else}}{{MirrorAddress $.Mirror}}{{end}}">{{if .SanitizedOriginalURL}}{{.SanitizedOriginalURL}}{{else}}{{MirrorAddress $.Mirror}}{{end}}</a></div>{{end}} | ||||
| {{if .IsFork}}<div class="fork-flag">{{$.i18n.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{SubStr .BaseRepo.RelLink 1 -1}}</a></div>{{end}} | |||||
| {{if .IsFork}}<div class="fork-flag">{{$.i18n.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{.BaseRepo.FullDisplayName}}</a></div>{{end}} | |||||
| {{if .IsGenerated}}<div class="fork-flag">{{$.i18n.Tr "repo.generated_from"}} <a href="{{.TemplateRepo.Link}}">{{SubStr .TemplateRepo.RelLink 1 -1}}</a></div>{{end}} | {{if .IsGenerated}}<div class="fork-flag">{{$.i18n.Tr "repo.generated_from"}} <a href="{{.TemplateRepo.Link}}">{{SubStr .TemplateRepo.RelLink 1 -1}}</a></div>{{end}} | ||||
| </div> | </div> | ||||
| {{if not .IsBeingCreated}} | {{if not .IsBeingCreated}} | ||||
| @@ -114,9 +114,9 @@ | |||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{end}} | {{end}} | ||||
| {{if .Permission.CanRead $.UnitTypeIssues}} | {{if .Permission.CanRead $.UnitTypeIssues}} | ||||
| @@ -152,7 +152,7 @@ | |||||
| {{if .Permission.CanRead $.UnitTypeCloudBrain}} | {{if .Permission.CanRead $.UnitTypeCloudBrain}} | ||||
| <a class="{{if .PageIsCloudBrain}}active{{end}} item" href="{{.RepoLink}}/debugjob?debugListType=all"> | <a class="{{if .PageIsCloudBrain}}active{{end}} item" href="{{.RepoLink}}/debugjob?debugListType=all"> | ||||
| <span> | <span> | ||||
| <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"/><path d="M4 3h16a1 1 0 0 1 1 1v7H3V4a1 1 0 0 1 1-1zM3 13h18v7a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-7zm4 3v2h3v-2H7zM7 6v2h3V6H7z"/></svg> | |||||
| <svg class="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"/><path d="M4 3h16a1 1 0 0 1 1 1v7H3V4a1 1 0 0 1 1-1zM3 13h18v7a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-7zm4 3v2h3v-2H7zM7 6v2h3V6H7z"/></svg> | |||||
| {{.i18n.Tr "repo.cloudbrain"}} | {{.i18n.Tr "repo.cloudbrain"}} | ||||
| <i class="question circle icon link cloudbrain-question" data-content={{.i18n.Tr "repo.cloudbrain_helper"}} data-position="top center" data-variation="mini"></i> | <i class="question circle icon link cloudbrain-question" data-content={{.i18n.Tr "repo.cloudbrain_helper"}} data-position="top center" data-variation="mini"></i> | ||||
| </span> | </span> | ||||
| @@ -2,7 +2,7 @@ | |||||
| <div class="repository new migrate" style="margin-top: 40px;"> | <div class="repository new migrate" style="margin-top: 40px;"> | ||||
| <div class="ui middle very relaxed page grid"> | <div class="ui middle very relaxed page grid"> | ||||
| <div class="column"> | <div class="column"> | ||||
| <form class="ui form" action="{{.Link}}" method="post"> | |||||
| <form class="ui form" action="{{.Link}}" method="post" id="create_repo_form"> | |||||
| {{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
| <h3 class="ui top attached header"> | <h3 class="ui top attached header"> | ||||
| {{.i18n.Tr "new_migrate"}} | {{.i18n.Tr "new_migrate"}} | ||||
| @@ -37,35 +37,7 @@ | |||||
| </div> | </div> | ||||
| <div class="ui divider"></div> | <div class="ui divider"></div> | ||||
| <div class="inline required field {{if .Err_Owner}}error{{end}}"> | |||||
| <label>{{.i18n.Tr "repo.owner"}}</label> | |||||
| <div class="ui selection owner dropdown"> | |||||
| <input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required> | |||||
| <span class="text" title="{{.ContextUser.Name}}"> | |||||
| <img class="ui mini image" src="{{.ContextUser.RelAvatarLink}}"> | |||||
| {{.ContextUser.ShortName 20}} | |||||
| </span> | |||||
| <i class="dropdown icon"></i> | |||||
| <div class="menu" title="{{.SignedUser.Name}}"> | |||||
| <div class="item" data-value="{{.SignedUser.ID}}"> | |||||
| <img class="ui mini image" src="{{.SignedUser.RelAvatarLink}}"> | |||||
| {{.SignedUser.ShortName 20}} | |||||
| </div> | |||||
| {{range .Orgs}} | |||||
| <div class="item" data-value="{{.ID}}" title="{{.Name}}"> | |||||
| <img class="ui mini image" src="{{.RelAvatarLink}}"> | |||||
| {{.ShortName 20}} | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="inline required field {{if .Err_RepoName}}error{{end}}"> | |||||
| <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> | |||||
| <input id="repo_name" name="repo_name" value="{{.repo_name}}" required> | |||||
| </div> | |||||
| {{template "repo/repo_name" .}} | |||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label>{{.i18n.Tr "repo.visibility"}}</label> | <label>{{.i18n.Tr "repo.visibility"}}</label> | ||||
| <div class="ui checkbox"> | <div class="ui checkbox"> | ||||
| @@ -127,7 +99,7 @@ | |||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label></label> | <label></label> | ||||
| <button class="ui green button"> | |||||
| <button class="ui green button" id="submit_reponame"> | |||||
| {{.i18n.Tr "repo.migrate_repo"}} | {{.i18n.Tr "repo.migrate_repo"}} | ||||
| </button> | </button> | ||||
| <a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a> | <a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a> | ||||
| @@ -2,47 +2,18 @@ | |||||
| <div class="repository new fork"> | <div class="repository new fork"> | ||||
| <div class="ui middle very relaxed page grid"> | <div class="ui middle very relaxed page grid"> | ||||
| <div class="column"> | <div class="column"> | ||||
| <form class="ui form" action="{{.Link}}" method="post"> | |||||
| <form class="ui form" action="{{.Link}}" method="post" id="create_repo_form"> | |||||
| {{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
| <h3 class="ui top attached header"> | <h3 class="ui top attached header"> | ||||
| {{.i18n.Tr "new_fork"}} | {{.i18n.Tr "new_fork"}} | ||||
| </h3> | </h3> | ||||
| <div class="ui attached segment"> | <div class="ui attached segment"> | ||||
| {{template "base/alert" .}} | {{template "base/alert" .}} | ||||
| <div class="inline required field {{if .Err_Owner}}error{{end}}"> | |||||
| <label>{{.i18n.Tr "repo.owner"}}</label> | |||||
| <div class="ui selection owner dropdown"> | |||||
| <input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required> | |||||
| <span class="text" title="{{.ContextUser.Name}}"> | |||||
| <img class="ui mini image" src="{{.ContextUser.RelAvatarLink}}"> | |||||
| {{.ContextUser.ShortName 20}} | |||||
| </span> | |||||
| <i class="dropdown icon"></i> | |||||
| <div class="menu"> | |||||
| {{if .CanForkToUser}} | |||||
| <div class="item" data-value="{{.SignedUser.ID}}" title="{{.SignedUser.Name}}"> | |||||
| <img class="ui mini image" src="{{.SignedUser.RelAvatarLink}}"> | |||||
| {{.SignedUser.ShortName 20}} | |||||
| </div> | |||||
| {{end}} | |||||
| {{range .Orgs}} | |||||
| <div class="item" data-value="{{.ID}}" title="{{.Name}}"> | |||||
| <img class="ui mini image" src="{{.RelAvatarLink}}"> | |||||
| {{.ShortName 20}} | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label>{{.i18n.Tr "repo.fork_from"}}</label> | <label>{{.i18n.Tr "repo.fork_from"}}</label> | ||||
| <a href="{{AppSubUrl}}/{{.ForkFrom}}">{{.ForkFrom}}</a> | |||||
| </div> | |||||
| <div class="inline required field {{if .Err_RepoName}}error{{end}}"> | |||||
| <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> | |||||
| <input id="repo_name" name="repo_name" value="{{.repo_name}}" required> | |||||
| <a href="{{AppSubUrl}}/{{.ForkFrom}}">{{.ForkDisplayName}}</a> | |||||
| </div> | </div> | ||||
| {{template "repo/repo_name" .}} | |||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label>{{.i18n.Tr "repo.visibility"}}</label> | <label>{{.i18n.Tr "repo.visibility"}}</label> | ||||
| <div class="ui read-only checkbox"> | <div class="ui read-only checkbox"> | ||||
| @@ -0,0 +1,50 @@ | |||||
| <div class="inline required field" > | |||||
| <label for="Alias">{{.i18n.Tr "form.Alias"}}</label> | |||||
| <input id="alias" name="alias" value="" autofocus required> | |||||
| <span class="help">{{.i18n.Tr "form.reponame_dash_dot_error"}}</span> | |||||
| </div> | |||||
| <div class="inline required fields" style="margin-bottom: 0;"> | |||||
| <label style="text-align: right;width: 250px!important;word-wrap: break-word;">{{.i18n.Tr "form.RepoPath"}}</label> | |||||
| <div class="required field {{if .Err_Owner}}error{{end}}" style="padding: 0;"> | |||||
| <!-- <label>{{.i18n.Tr "repo.owner"}}</label> --> | |||||
| <div class="ui selection owner dropdown" id="ownerDropdown"> | |||||
| <input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required> | |||||
| <div class="text" title="{{.ContextUser.Name}}"> | |||||
| <img class="ui mini image" src="{{.ContextUser.RelAvatarLink}}"> | |||||
| {{.ContextUser.ShortName 20}} | |||||
| </div> | |||||
| <i class="dropdown icon"></i> | |||||
| <div class="menu"> | |||||
| <div class="item" data-value="{{.SignedUser.ID}}" title="{{.SignedUser.Name}}"> | |||||
| <img class="ui mini image" src="{{.SignedUser.RelAvatarLink}}"> | |||||
| {{.SignedUser.ShortName 20}} | |||||
| </div> | |||||
| {{range .Orgs}} | |||||
| <div class="item" data-value="{{.ID}}" title="{{.Name}}"> | |||||
| <img class="ui mini image" src="{{.RelAvatarLink}}"> | |||||
| {{.ShortName 20}} | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| <!-- <span class="help">{{.i18n.Tr "repo.repo_owner_helper"}}</span> --> | |||||
| </div> | |||||
| <!-- <div class="required field {{if .Err_RepoName}}error{{end}}"> | |||||
| <input id="repo_name" name="repo_name" value="{{.repo_name}}" autofocus required> | |||||
| </div> --> | |||||
| <div class="ui interval" style="width: 0.6em;font-size: 2rem;line-height: 0px;text-align: center;">/</div> | |||||
| <div class="required field {{if .Err_RepoName}}error{{end}}"> | |||||
| <!-- <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> --> | |||||
| <input style="width: 100% !important;" id="repo_name" name="repo_name" value="{{.repo_name}}" autofocus required> | |||||
| </div> | |||||
| </div> | |||||
| <span style="display: block;margin-bottom: 1em;" class="help">{{.i18n.Tr "form.repoadd_dash_dot_error"}}</span> | |||||
| <div class="inline field" id="repoAdress" style="display: none;word-break: break-all;"> | |||||
| <label for="">{{.i18n.Tr "form.RepoAdress"}}:</label> | |||||
| <span></span> | |||||
| </div> | |||||
| @@ -8,12 +8,46 @@ | |||||
| {{.i18n.Tr "repo.settings.basic_settings"}} | {{.i18n.Tr "repo.settings.basic_settings"}} | ||||
| </h4> | </h4> | ||||
| <div class="ui attached segment"> | <div class="ui attached segment"> | ||||
| <form class="ui form" action="{{.Link}}" method="post"> | |||||
| <form class="ui form" action="{{.Link}}" method="post" id="create_repo_form"> | |||||
| {{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
| <input type="hidden" name="action" value="update"> | <input type="hidden" name="action" value="update"> | ||||
| <div class="required field {{if .Err_RepoName}}error{{end}}"> | |||||
| <!-- <div class="required field {{if .Err_RepoName}}error{{end}}"> | |||||
| <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> | <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> | ||||
| <input id="repo_name" name="repo_name" value="{{.Repository.Name}}" data-repo-name="{{.Repository.Name}}" autofocus required> | |||||
| <input id="repo_name" name="alias" value="{{.Repository.Alias}}" data-repo-name="{{.Repository.Alias}}" autofocus required> | |||||
| <input type="hidden" name="repo_name" value="{{.Repository.Name}}" data-repo-name="{{.Repository.Name}}" autofocus required> | |||||
| </div> --> | |||||
| <div class="required field"> | |||||
| <label for="Alias">{{.i18n.Tr "form.Alias"}}</label> | |||||
| <input id="alias" name="alias" value="{{.Repository.Alias}}" data-repo-name="{{.Repository.Alias}}" autofocus required> | |||||
| <span class="help">{{.i18n.Tr "form.reponame_dash_dot_error"}}</span> | |||||
| </div> | |||||
| <div class="required field"> | |||||
| <label>{{.i18n.Tr "form.RepoPath"}}</label> | |||||
| <div class="fields"> | |||||
| <div class="eight wide required field {{if .Err_Owner}}error{{end}}"> | |||||
| <div class="ui selection owner dropdown" id="ownerDropdown"> | |||||
| <input type="hidden" id="uid" name="uid" value="{{.Owner.Name}}" required> | |||||
| <div class="text" title="{{.Owner.Name}}"> | |||||
| <img class="ui mini image" src="{{.Owner.RelAvatarLink}}"> | |||||
| {{.Owner.ShortName 20}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="ui interval" style="width: 0.6em;font-size: 2rem;line-height: 38px;text-align: center;">/</div> | |||||
| <div class="eight wide required field {{if .Err_RepoName}}error{{end}}"> | |||||
| <!-- <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> --> | |||||
| <input style="width: 100% !important;" id="repo_name" name="repo_name" value="{{.Repository.Name}}" data-repo-name="{{.Repository.Name}}" autofocus required> | |||||
| <span class="help">{{.i18n.Tr "form.repoadd_dash_dot_error"}}</span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="inline field" id="repoAdress" style="display: none;"> | |||||
| <label for="">{{.i18n.Tr "form.RepoAdress"}}:</label> | |||||
| <span></span> | |||||
| </div> | </div> | ||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label>{{.i18n.Tr "repo.repo_size"}}</label> | <label>{{.i18n.Tr "repo.repo_size"}}</label> | ||||
| @@ -13,12 +13,12 @@ | |||||
| {{.ShortActUserName}} | {{.ShortActUserName}} | ||||
| {{end}} | {{end}} | ||||
| {{if eq .GetOpType 1}} | {{if eq .GetOpType 1}} | ||||
| {{$.i18n.Tr "action.create_repo" .GetRepoLink .ShortRepoPath | Str2html}} | |||||
| {{$.i18n.Tr "action.create_repo" .GetRepoLink .ShortRepoFullDisplayName | Str2html}} | |||||
| {{else if eq .GetOpType 2}} | {{else if eq .GetOpType 2}} | ||||
| {{$.i18n.Tr "action.rename_repo" .GetContent .GetRepoLink .ShortRepoPath | Str2html}} | |||||
| {{$.i18n.Tr "action.rename_repo" .GetContent .GetRepoLink .ShortRepoFullDisplayName | Str2html}} | |||||
| {{else if eq .GetOpType 5}} | {{else if eq .GetOpType 5}} | ||||
| {{ $branchLink := .GetBranch | EscapePound | Escape}} | {{ $branchLink := .GetBranch | EscapePound | Escape}} | ||||
| {{$.i18n.Tr "action.commit_repo" .GetRepoLink $branchLink (Escape .GetBranch) .ShortRepoPath | Str2html}} | |||||
| {{$.i18n.Tr "action.commit_repo" .GetRepoLink $branchLink (Escape .GetBranch) .ShortRepoFullDisplayName | Str2html}} | |||||
| {{else if eq .GetOpType 6}} | {{else if eq .GetOpType 6}} | ||||
| {{ $index := index .GetIssueInfos 0}} | {{ $index := index .GetIssueInfos 0}} | ||||
| {{$.i18n.Tr "action.create_issue" .GetRepoLink $index .ShortRepoPath | Str2html}} | {{$.i18n.Tr "action.create_issue" .GetRepoLink $index .ShortRepoPath | Str2html}} | ||||
| @@ -104,7 +104,7 @@ | |||||
| <li v-for="repo in repos" :class="{'private': repo.private}" v-show="showRepo(repo)"> | <li v-for="repo in repos" :class="{'private': repo.private}" v-show="showRepo(repo)"> | ||||
| <a :href="suburl + '/' + repo.full_name"> | <a :href="suburl + '/' + repo.full_name"> | ||||
| <svg :class="'svg ' + repoClass(repo)" width="16" height="16" aria-hidden="true"><use :xlink:href="'#' + repoClass(repo)" /></svg> | <svg :class="'svg ' + repoClass(repo)" width="16" height="16" aria-hidden="true"><use :xlink:href="'#' + repoClass(repo)" /></svg> | ||||
| <strong class="text truncate item-name">${repo.full_name}</strong> | |||||
| <strong class="text truncate item-name">${repo.full_display_name}</strong> | |||||
| <i v-if="repo.archived" class="archive icon archived-icon"></i> | <i v-if="repo.archived" class="archive icon archived-icon"></i> | ||||
| <span class="ui right text light grey"> | <span class="ui right text light grey"> | ||||
| ${repo.stars_count} <span class="rear">{{svg "octicon-star" 16}}</span> | ${repo.stars_count} <span class="rear">{{svg "octicon-star" 16}}</span> | ||||
| @@ -21,7 +21,7 @@ | |||||
| {{else}} | {{else}} | ||||
| <span class="iconFloat">{{svg "octicon-repo" 16}}</span> | <span class="iconFloat">{{svg "octicon-repo" 16}}</span> | ||||
| {{end}} | {{end}} | ||||
| <a class="name" href="{{AppSubUrl}}/{{$.Owner.Name}}/{{.Name}}">{{$.Owner.Name}}/{{.Name}}</a> | |||||
| <a class="name" href="{{AppSubUrl}}/{{$.Owner.Name}}/{{.Name}}">{{$.Owner.Name}}/{{.DisplayName}}</a> | |||||
| <span>{{SizeFmt .Size}}</span> | <span>{{SizeFmt .Size}}</span> | ||||
| {{if .IsFork}} | {{if .IsFork}} | ||||
| {{$.i18n.Tr "repo.forked_from"}} | {{$.i18n.Tr "repo.forked_from"}} | ||||
| @@ -4136,4 +4136,84 @@ $('.question.circle.icon').hover(function(){ | |||||
| //云脑详情页面跳转回上一个页面 | //云脑详情页面跳转回上一个页面 | ||||
| $(".section.backTodeBug").attr("href",localStorage.getItem('all')) | $(".section.backTodeBug").attr("href",localStorage.getItem('all')) | ||||
| //新建调试取消跳转 | //新建调试取消跳转 | ||||
| $(".ui.button.cancel").attr("href",localStorage.getItem('all')) | |||||
| $(".ui.button.cancel").attr("href",localStorage.getItem('all')) | |||||
| function initcreateRepo(){ | |||||
| let timeout; | |||||
| let keydown_flag = false | |||||
| const urlAdd = location.href.split('/')[0] + '//' + location.href.split('/')[2] | |||||
| let owner = $('#ownerDropdown div.text').attr("title") | |||||
| $(document).ready(function(){ | |||||
| $('#ownerDropdown').dropdown({ | |||||
| onChange:function(value,text,$choice){ | |||||
| owner = $choice[0].getAttribute("title") | |||||
| $('#repoAdress').css("display","block") | |||||
| $('#repoAdress span').text(urlAdd+'/'+owner+'/'+$('#repo_name').val()+'.git') | |||||
| } | |||||
| }); | |||||
| }) | |||||
| $('#repo_name').keyup(function(){ | |||||
| keydown_flag = $('#repo_name').val() ? true : false | |||||
| if(keydown_flag){ | |||||
| $('#repoAdress').css("display","block") | |||||
| $('#repoAdress span').text(urlAdd+'/'+owner+'/'+$('#repo_name').val()+'.git') | |||||
| } | |||||
| else{ | |||||
| $('#repoAdress').css("display","none") | |||||
| $('#repo_name').attr("placeholder","") | |||||
| } | |||||
| }) | |||||
| $("#create_repo_form") | |||||
| .form({ | |||||
| on: 'blur', | |||||
| // inline:true, | |||||
| fields: { | |||||
| alias: { | |||||
| identifier : 'alias', | |||||
| rules: [ | |||||
| { | |||||
| type: 'regExp[/^[\u4E00-\u9FA5A-Za-z0-9_.-]{1,100}$/]', | |||||
| } | |||||
| ] | |||||
| }, | |||||
| repo_name:{ | |||||
| identifier : 'repo_name', | |||||
| rules: [ | |||||
| { | |||||
| type: 'regExp[/^[A-Za-z0-9_.-]{1,100}$/]', | |||||
| } | |||||
| ] | |||||
| }, | |||||
| }, | |||||
| onFailure: function(e){ | |||||
| return false; | |||||
| } | |||||
| }) | |||||
| $('#alias').bind('input propertychange', function (event) { | |||||
| clearTimeout(timeout) | |||||
| timeout = setTimeout(() => { | |||||
| //在此处写调用的方法,可以实现仅最后一次操作生效 | |||||
| const aliasValue = $('#alias').val() | |||||
| if(keydown_flag){ | |||||
| $('#repo_name').attr("placeholder","") | |||||
| } | |||||
| else if(aliasValue){ | |||||
| $('#repo_name').attr("placeholder","正在获取路径...") | |||||
| $.get(`${window.config.AppSubUrl}/repo/check_name?q=${aliasValue}&owner=${owner}`,(data)=>{ | |||||
| const repo_name = data.name | |||||
| $('#repo_name').val(repo_name) | |||||
| $('#repoAdress').css("display","block") | |||||
| $('#repoAdress span').text(urlAdd+'/'+owner+'/'+$('#repo_name').val()+'.git') | |||||
| $('#repo_name').attr("placeholder","") | |||||
| }) | |||||
| }else{ | |||||
| $('#repo_name').val('') | |||||
| $('#repo_name').attr("placeholder","") | |||||
| $('#repoAdress').css("display","none") | |||||
| } | |||||
| }, 500) | |||||
| }); | |||||
| } | |||||
| initcreateRepo() | |||||
| @@ -2,7 +2,7 @@ | |||||
| .help { | .help { | ||||
| color: #999999; | color: #999999; | ||||
| padding-top: .6em; | padding-top: .6em; | ||||
| padding-bottom: .6em; | |||||
| display: inline-block; | display: inline-block; | ||||
| } | } | ||||
| } | } | ||||