Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/1425 Reviewed-by: lewis <747342561@qq.com>tags/v1.22.1.3
| @@ -140,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 ( | ||||
| @@ -153,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"` | ||||
| @@ -166,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 | ||||
| @@ -175,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"` | ||||
| @@ -566,6 +574,19 @@ func (repo *Repository) GetOwner() error { | |||||
| return repo.getOwner(x) | return repo.getOwner(x) | ||||
| } | } | ||||
| func (repo *Repository) getCreator(e Engine) (err error) { | |||||
| if repo.CreatorID == 0 { | |||||
| return nil | |||||
| } | |||||
| repo.Creator, err = getUserByID(e, repo.CreatorID) | |||||
| return err | |||||
| } | |||||
| func (repo *Repository) GetCreator() error { | |||||
| return repo.getCreator(x) | |||||
| } | |||||
| func (repo *Repository) mustOwner(e Engine) *User { | func (repo *Repository) mustOwner(e Engine) *User { | ||||
| if err := repo.getOwner(e); err != nil { | if err := repo.getOwner(e); err != nil { | ||||
| return &User{ | return &User{ | ||||
| @@ -1064,6 +1085,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 | ||||
| @@ -1109,7 +1132,7 @@ func IsUsableRepoAlias(name string) error { | |||||
| } | } | ||||
| // 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) { | |||||
| repo.LowerAlias = strings.ToLower(repo.Alias) | repo.LowerAlias = strings.ToLower(repo.Alias) | ||||
| if err = IsUsableRepoName(repo.Name); err != nil { | if err = IsUsableRepoName(repo.Name); err != nil { | ||||
| return err | return err | ||||
| @@ -1124,7 +1147,10 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||||
| } 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 | ||||
| } | } | ||||
| @@ -1158,17 +1184,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, | ||||
| @@ -1176,11 +1208,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, | ||||
| @@ -1250,6 +1284,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") | ||||
| @@ -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 | ||||
| @@ -351,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)) | ||||
| } | } | ||||
| @@ -42,7 +42,7 @@ type TagsDetail struct { | |||||
| TagId int64 | TagId int64 | ||||
| TagName string | TagName string | ||||
| TagLimit int | TagLimit int | ||||
| RepoList []Repository | |||||
| RepoList []*Repository | |||||
| } | } | ||||
| func GetTagByID(id int64) (*OfficialTag, error) { | func GetTagByID(id int64) (*OfficialTag, error) { | ||||
| @@ -146,8 +146,8 @@ func GetAllOfficialTagRepos(orgID int64, isOwner bool) ([]TagsDetail, error) { | |||||
| return result, nil | return result, nil | ||||
| } | } | ||||
| func GetOfficialTagDetail(orgID, tagId int64) ([]Repository, error) { | |||||
| t := make([]Repository, 0) | |||||
| func GetOfficialTagDetail(orgID, tagId int64) ([]*Repository, error) { | |||||
| t := make([]*Repository, 0) | |||||
| const SQLCmd = "select t2.* from official_tag_repos t1 inner join repository t2 on t1.repo_id = t2.id where t1.org_id = ? and t1.tag_id=? order by t2.updated_unix desc" | const SQLCmd = "select t2.* from official_tag_repos t1 inner join repository t2 on t1.repo_id = t2.id where t1.org_id = ? and t1.tag_id=? order by t2.updated_unix desc" | ||||
| if err := x.SQL(SQLCmd, orgID, tagId).Find(&t); err != nil { | if err := x.SQL(SQLCmd, orgID, tagId).Find(&t); err != nil { | ||||
| @@ -728,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) | |||||
| } | |||||
| @@ -328,7 +328,7 @@ func Contexter() macaron.Handler { | |||||
| } | } | ||||
| } | } | ||||
| ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) | |||||
| //ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) | |||||
| ctx.Data["CsrfToken"] = html.EscapeString(x.GetToken()) | ctx.Data["CsrfToken"] = html.EscapeString(x.GetToken()) | ||||
| ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`) | ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`) | ||||
| @@ -63,6 +63,8 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { | |||||
| org := ctx.Org.Organization | org := ctx.Org.Organization | ||||
| ctx.Data["Org"] = org | ctx.Data["Org"] = org | ||||
| ctx.Data["IsCourse"] = ctx.Org.Organization.Name == setting.Course.OrgName | |||||
| // Force redirection when username is actually a user. | // Force redirection when username is actually a user. | ||||
| if !org.IsOrganization() { | if !org.IsOrganization() { | ||||
| ctx.Redirect(setting.AppSubURL + "/" + org.Name) | ctx.Redirect(setting.AppSubURL + "/" + org.Name) | ||||
| @@ -402,6 +402,7 @@ func RepoAssignment() macaron.Handler { | |||||
| } | } | ||||
| ctx.Repo.Owner = owner | ctx.Repo.Owner = owner | ||||
| ctx.Data["Username"] = ctx.Repo.Owner.Name | ctx.Data["Username"] = ctx.Repo.Owner.Name | ||||
| ctx.Data["IsCourse"] = owner.Name == setting.Course.OrgName | |||||
| // Get repository. | // Get repository. | ||||
| repo, err := models.GetRepositoryByName(owner.ID, repoName) | repo, err := models.GetRepositoryByName(owner.ID, repoName) | ||||
| @@ -22,6 +22,10 @@ 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, | ||||
| @@ -38,10 +42,15 @@ 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, | |||||
| Topics: opts.Topics, | |||||
| } | } | ||||
| 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 | ||||
| } | } | ||||
| @@ -572,6 +572,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. | ||||
| @@ -1339,6 +1344,11 @@ 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() { | ||||
| @@ -93,6 +93,7 @@ func NewFuncMap() []template.FuncMap { | |||||
| "TimeSince": timeutil.TimeSince, | "TimeSince": timeutil.TimeSince, | ||||
| "TimeSinceUnix": timeutil.TimeSinceUnix, | "TimeSinceUnix": timeutil.TimeSinceUnix, | ||||
| "TimeSinceUnix1": timeutil.TimeSinceUnix1, | "TimeSinceUnix1": timeutil.TimeSinceUnix1, | ||||
| "TimeSinceUnixShort": timeutil.TimeSinceUnixShort, | |||||
| "RawTimeSince": timeutil.RawTimeSince, | "RawTimeSince": timeutil.RawTimeSince, | ||||
| "FileSize": base.FileSize, | "FileSize": base.FileSize, | ||||
| "PrettyNumber": base.PrettyNumber, | "PrettyNumber": base.PrettyNumber, | ||||
| @@ -342,6 +343,7 @@ func NewTextFuncMap() []texttmpl.FuncMap { | |||||
| "TimeSince": timeutil.TimeSince, | "TimeSince": timeutil.TimeSince, | ||||
| "TimeSinceUnix": timeutil.TimeSinceUnix, | "TimeSinceUnix": timeutil.TimeSinceUnix, | ||||
| "TimeSinceUnix1": timeutil.TimeSinceUnix1, | "TimeSinceUnix1": timeutil.TimeSinceUnix1, | ||||
| "TimeSinceUnixShort": timeutil.TimeSinceUnixShort, | |||||
| "RawTimeSince": timeutil.RawTimeSince, | "RawTimeSince": timeutil.RawTimeSince, | ||||
| "DateFmtLong": func(t time.Time) string { | "DateFmtLong": func(t time.Time) string { | ||||
| return t.Format(time.RFC1123Z) | return t.Format(time.RFC1123Z) | ||||
| @@ -165,5 +165,8 @@ func htmlTimeSinceUnix(then, now TimeStamp, lang string) template.HTML { | |||||
| func TimeSinceUnix1(then TimeStamp) string { | func TimeSinceUnix1(then TimeStamp) string { | ||||
| format := time.Unix(int64(then), 0).Format("2006-01-02 15:04:05") | format := time.Unix(int64(then), 0).Format("2006-01-02 15:04:05") | ||||
| return format | return format | ||||
| } | |||||
| func TimeSinceUnixShort(then TimeStamp) string { | |||||
| format := time.Unix(int64(then), 0).Format("2006-01-02") | |||||
| return format | |||||
| } | } | ||||
| @@ -50,6 +50,8 @@ repository = Repository | |||||
| organization = Organization | organization = Organization | ||||
| mirror = Mirror | mirror = Mirror | ||||
| new_repo = New Repository | new_repo = New Repository | ||||
| new_course=Publish Course | |||||
| course_desc = Course Description | |||||
| new_migrate = New Migration | new_migrate = New Migration | ||||
| new_dataset = New Dataset | new_dataset = New Dataset | ||||
| edit_dataset = Edit Dataset | edit_dataset = Edit Dataset | ||||
| @@ -219,6 +221,7 @@ show_only_public = Showing only public | |||||
| issues.in_your_repos = In your repositories | issues.in_your_repos = In your repositories | ||||
| contributors = Contributors | contributors = Contributors | ||||
| contributor = Contributor | |||||
| page_title=Explore Better AI | page_title=Explore Better AI | ||||
| page_small_title=OpenI AI Development Cooperation Platform | page_small_title=OpenI AI Development Cooperation Platform | ||||
| @@ -268,8 +271,8 @@ org_no_results = No matching organizations found. | |||||
| code_no_results = No source code matching your search term found. | code_no_results = No source code matching your search term found. | ||||
| code_search_results = Search results for '%s' | code_search_results = Search results for '%s' | ||||
| code_last_indexed_at = Last indexed %s | code_last_indexed_at = Last indexed %s | ||||
| save=save | |||||
| cancel=cancel | |||||
| save=Save | |||||
| cancel=Cancel | |||||
| [auth] | [auth] | ||||
| create_new_account = Register Account | create_new_account = Register Account | ||||
| @@ -349,8 +352,11 @@ modify = Update | |||||
| [form] | [form] | ||||
| UserName = Username | UserName = Username | ||||
| Alias = Repository name | Alias = Repository name | ||||
| courseAlias = Course Name | |||||
| courseAdress = Course Path | |||||
| RepoPath = Repository path | RepoPath = Repository path | ||||
| RepoAdress = Repository Adress | RepoAdress = Repository Adress | ||||
| course_Adress = Course Address | |||||
| Email = Email address | Email = Email address | ||||
| Password = Password | Password = Password | ||||
| Retype = Re-Type Password | Retype = Re-Type Password | ||||
| @@ -392,6 +398,7 @@ 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 or path is already used. | repo_name_been_taken = The repository name or path is already used. | ||||
| course_name_been_taken=The course name or 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. | ||||
| @@ -796,6 +803,7 @@ generate_from = Generate From | |||||
| repo_desc = Description | repo_desc = Description | ||||
| repo_lang = Language | repo_lang = Language | ||||
| repo_gitignore_helper = Select .gitignore templates. | repo_gitignore_helper = Select .gitignore templates. | ||||
| repo_label_helpe = Press Enter to complete | |||||
| issue_labels = Issue Labels | issue_labels = Issue Labels | ||||
| issue_labels_helper = Select an issue label set. | issue_labels_helper = Select an issue label set. | ||||
| license = License | license = License | ||||
| @@ -804,6 +812,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 | ||||
| @@ -867,6 +877,11 @@ get_repo_info_error=Can not get the information of the repository. | |||||
| generate_statistic_file_error=Fail to generate file. | generate_statistic_file_error=Fail to generate file. | ||||
| repo_stat_inspect=ProjectAnalysis | repo_stat_inspect=ProjectAnalysis | ||||
| all=All | all=All | ||||
| computing.all = All | |||||
| computing.Introduction=Introduction | |||||
| computing.success=Join Success | |||||
| modelarts.status=Status | modelarts.status=Status | ||||
| modelarts.createtime=CreateTime | modelarts.createtime=CreateTime | ||||
| modelarts.version_nums = Version Nums | modelarts.version_nums = Version Nums | ||||
| @@ -988,8 +1003,12 @@ 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 | ||||
| @@ -2034,6 +2053,7 @@ org_full_name_holder = Organization Full Name | |||||
| org_name_helper = Organization names should be short and memorable. | org_name_helper = Organization names should be short and memorable. | ||||
| create_org = Create Organization | create_org = Create Organization | ||||
| repo_updated = Updated | repo_updated = Updated | ||||
| repo_released = Post | |||||
| home = Home | home = Home | ||||
| people = People | people = People | ||||
| teams = Teams | teams = Teams | ||||
| @@ -2050,6 +2070,14 @@ team_access_desc = Repository access | |||||
| team_permission_desc = Permission | team_permission_desc = Permission | ||||
| team_unit_desc = Allow Access to Repository Sections | team_unit_desc = Allow Access to Repository Sections | ||||
| team_unit_disabled = (Disabled) | team_unit_disabled = (Disabled) | ||||
| selected_couse=Selected Courses | |||||
| release_course = Publish Course | |||||
| all_keywords=All keywords | |||||
| max_selectedPro= Select up to 9 public projects | |||||
| custom_select_courses = Customize selected courses | |||||
| recommend_remain_pro = Remain | |||||
| save_fail_tips = The upper limit is exceeded | |||||
| select_again = Select more than 9, please select again! | |||||
| form.name_reserved = The organization name '%s' is reserved. | form.name_reserved = The organization name '%s' is reserved. | ||||
| form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. | form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. | ||||
| @@ -2093,6 +2121,8 @@ members.remove = Remove | |||||
| members.leave = Leave | members.leave = Leave | ||||
| members.invite_desc = Add a new member to %s: | members.invite_desc = Add a new member to %s: | ||||
| members.invite_now = Invite Now | members.invite_now = Invite Now | ||||
| course_members.remove = Remove | |||||
| course_members.leave = Leave | |||||
| teams.join = Join | teams.join = Join | ||||
| teams.leave = Leave | teams.leave = Leave | ||||
| @@ -2135,6 +2165,7 @@ teams.all_repositories_helper = Team has access to all repositories. Selecting t | |||||
| teams.all_repositories_read_permission_desc = This team grants <strong>Read</strong> access to <strong>all repositories</strong>: members can view and clone repositories. | teams.all_repositories_read_permission_desc = This team grants <strong>Read</strong> access to <strong>all repositories</strong>: members can view and clone repositories. | ||||
| teams.all_repositories_write_permission_desc = This team grants <strong>Write</strong> access to <strong>all repositories</strong>: members can read from and push to repositories. | teams.all_repositories_write_permission_desc = This team grants <strong>Write</strong> access to <strong>all repositories</strong>: members can read from and push to repositories. | ||||
| teams.all_repositories_admin_permission_desc = This team grants <strong>Admin</strong> access to <strong>all repositories</strong>: members can read from, push to and add collaborators to repositories. | teams.all_repositories_admin_permission_desc = This team grants <strong>Admin</strong> access to <strong>all repositories</strong>: members can read from, push to and add collaborators to repositories. | ||||
| teams.join_teams=Join in | |||||
| [admin] | [admin] | ||||
| dashboard = Dashboard | dashboard = Dashboard | ||||
| @@ -50,6 +50,8 @@ repository=项目 | |||||
| organization=组织 | organization=组织 | ||||
| mirror=镜像 | mirror=镜像 | ||||
| new_repo=创建项目 | new_repo=创建项目 | ||||
| new_course=发布课程 | |||||
| course_desc=课程描述 | |||||
| new_dataset=创建数据集 | new_dataset=创建数据集 | ||||
| new_migrate=迁移外部项目 | new_migrate=迁移外部项目 | ||||
| edit_dataset = Edit Dataset | edit_dataset = Edit Dataset | ||||
| @@ -221,6 +223,7 @@ show_only_public=只显示公开的 | |||||
| issues.in_your_repos=属于该用户项目的 | issues.in_your_repos=属于该用户项目的 | ||||
| contributors=贡献者 | contributors=贡献者 | ||||
| contributor=贡献者 | |||||
| page_title=探索更好的AI | page_title=探索更好的AI | ||||
| page_small_title=启智AI开发协作平台 | page_small_title=启智AI开发协作平台 | ||||
| @@ -352,8 +355,11 @@ modify=更新 | |||||
| UserName=用户名 | UserName=用户名 | ||||
| RepoName=项目路径 | RepoName=项目路径 | ||||
| Alias=项目名称 | Alias=项目名称 | ||||
| courseAlias=课程名称 | |||||
| courseAdress=课程路径 | |||||
| RepoPath=项目路径 | RepoPath=项目路径 | ||||
| RepoAdress=项目地址 | RepoAdress=项目地址 | ||||
| course_Adress = 课程地址 | |||||
| Email=邮箱地址 | Email=邮箱地址 | ||||
| Password=密码 | Password=密码 | ||||
| Retype=重新输入密码 | Retype=重新输入密码 | ||||
| @@ -395,6 +401,7 @@ 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=组织名称已被使用。 | ||||
| @@ -800,6 +807,7 @@ generate_from=生成自 | |||||
| repo_desc=项目描述 | repo_desc=项目描述 | ||||
| repo_lang=项目语言 | repo_lang=项目语言 | ||||
| repo_gitignore_helper=选择 .gitignore 模板。 | repo_gitignore_helper=选择 .gitignore 模板。 | ||||
| repo_label_helpe=输入完成后回车键完成标签确定。 | |||||
| issue_labels=任务标签 | issue_labels=任务标签 | ||||
| issue_labels_helper=选择一个任务标签集 | issue_labels_helper=选择一个任务标签集 | ||||
| license=授权许可 | license=授权许可 | ||||
| @@ -808,6 +816,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=删除过时的远程跟踪引用 | ||||
| @@ -873,6 +883,10 @@ generate_statistic_file_error=生成文件失败。 | |||||
| repo_stat_inspect=项目分析 | repo_stat_inspect=项目分析 | ||||
| all=所有 | all=所有 | ||||
| computing.all=全部 | |||||
| computing.Introduction=简介 | |||||
| computing.success=加入成功 | |||||
| modelarts.status=状态 | modelarts.status=状态 | ||||
| modelarts.createtime=创建时间 | modelarts.createtime=创建时间 | ||||
| modelarts.version_nums=版本数 | modelarts.version_nums=版本数 | ||||
| @@ -999,8 +1013,12 @@ 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=迁移类型 | ||||
| @@ -2045,6 +2063,7 @@ org_full_name_holder=组织全名 | |||||
| org_name_helper=组织名字应该简单明了。 | org_name_helper=组织名字应该简单明了。 | ||||
| create_org=创建组织 | create_org=创建组织 | ||||
| repo_updated=最后更新于 | repo_updated=最后更新于 | ||||
| repo_released=发布于 | |||||
| home=组织主页 | home=组织主页 | ||||
| people=组织成员 | people=组织成员 | ||||
| teams=组织团队 | teams=组织团队 | ||||
| @@ -2061,6 +2080,14 @@ team_access_desc=项目权限 | |||||
| team_permission_desc=权限 | team_permission_desc=权限 | ||||
| team_unit_desc=允许访问项目单元 | team_unit_desc=允许访问项目单元 | ||||
| team_unit_disabled=(已禁用) | team_unit_disabled=(已禁用) | ||||
| selected_couse=精选课程 | |||||
| release_course = 发布课程 | |||||
| all_keywords=全部关键字 | |||||
| max_selectedPro= 最多可选9个公开项目 | |||||
| custom_select_courses = 自定义精选课程 | |||||
| recommend_remain_pro = 还能推荐 | |||||
| save_fail_tips = 最多可选9个,保存失败 | |||||
| select_again = 选择超过9个,请重新选择! | |||||
| form.name_reserved=组织名称 '%s' 是被保留的。 | form.name_reserved=组织名称 '%s' 是被保留的。 | ||||
| form.name_pattern_not_allowed=组织名称中不允许使用 "%s"。 | form.name_pattern_not_allowed=组织名称中不允许使用 "%s"。 | ||||
| @@ -2104,6 +2131,8 @@ members.remove=移除成员 | |||||
| members.leave=离开组织 | members.leave=离开组织 | ||||
| members.invite_desc=邀请新的用户加入 %s: | members.invite_desc=邀请新的用户加入 %s: | ||||
| members.invite_now=立即邀请 | members.invite_now=立即邀请 | ||||
| course_members.remove=移除 | |||||
| course_members.leave=离开 | |||||
| teams.join=加入团队 | teams.join=加入团队 | ||||
| teams.leave=离开团队 | teams.leave=离开团队 | ||||
| @@ -2147,6 +2176,10 @@ teams.all_repositories_read_permission_desc=此团队授予<strong>读取</stron | |||||
| teams.all_repositories_write_permission_desc=此团队授予<strong>修改</strong><strong>所有项目</strong>的访问权限: 成员可以查看和推送至项目。 | teams.all_repositories_write_permission_desc=此团队授予<strong>修改</strong><strong>所有项目</strong>的访问权限: 成员可以查看和推送至项目。 | ||||
| teams.all_repositories_admin_permission_desc=该团队拥有 <strong>管理</strong> <strong>所有项目</strong>的权限:团队成员可以读取、克隆、推送以及添加其它项目协作者。 | teams.all_repositories_admin_permission_desc=该团队拥有 <strong>管理</strong> <strong>所有项目</strong>的权限:团队成员可以读取、克隆、推送以及添加其它项目协作者。 | ||||
| teams.join_teams=加入该组织 | |||||
| [admin] | [admin] | ||||
| dashboard=管理面板 | dashboard=管理面板 | ||||
| users=帐户管理 | users=帐户管理 | ||||
| @@ -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,63 +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.GetRepositoryByOwnerAndAlias(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["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,18 @@ 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 | |||||
| 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 +107,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) | ||||
| @@ -132,11 +153,27 @@ func Home(ctx *context.Context) { | |||||
| //find org tag info | //find org tag info | ||||
| tags, err := models.GetAllOfficialTagRepos(org.ID, ctx.Org.IsOwner) | tags, err := models.GetAllOfficialTagRepos(org.ID, ctx.Org.IsOwner) | ||||
| if setting.Course.OrgName == org.Name { | |||||
| for _, tag := range tags { | |||||
| for _, repo := range tag.RepoList { | |||||
| repo.GetCreator() | |||||
| repo.GetOwner() | |||||
| } | |||||
| } | |||||
| } | |||||
| if err != nil { | if err != nil { | ||||
| ctx.ServerError("GetAllOfficialTagRepos", err) | ctx.ServerError("GetAllOfficialTagRepos", err) | ||||
| return | return | ||||
| } | } | ||||
| 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,196 @@ | |||||
| 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") | |||||
| org, _ := models.GetUserByName(setting.Course.OrgName) | |||||
| ctx.Data["Owner"] = org | |||||
| ctx.Data["IsCourse"] = true | |||||
| ctx.HTML(200, tplCreateCourse) | |||||
| } | |||||
| func CreateCoursePost(ctx *context.Context, form auth.CreateCourseForm) { | |||||
| ctx.Data["Title"] = ctx.Tr("new_course") | |||||
| if ctx.Written() { | |||||
| return | |||||
| } | |||||
| org, _ := models.GetUserByName(setting.Course.OrgName) | |||||
| ctx.Data["Owner"] = org | |||||
| ctx.Data["IsCourse"] = true | |||||
| 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) | |||||
| } | |||||
| } | |||||
| @@ -35,6 +35,7 @@ import ( | |||||
| const ( | const ( | ||||
| tplRepoEMPTY base.TplName = "repo/empty" | tplRepoEMPTY base.TplName = "repo/empty" | ||||
| tplRepoHome base.TplName = "repo/home" | tplRepoHome base.TplName = "repo/home" | ||||
| tplCourseHome base.TplName = "repo/courseHome" | |||||
| tplWatchers base.TplName = "repo/watchers" | tplWatchers base.TplName = "repo/watchers" | ||||
| tplForks base.TplName = "repo/forks" | tplForks base.TplName = "repo/forks" | ||||
| tplMigrating base.TplName = "repo/migrating" | tplMigrating base.TplName = "repo/migrating" | ||||
| @@ -855,7 +856,12 @@ func renderCode(ctx *context.Context) { | |||||
| ctx.Data["TreeLink"] = treeLink | ctx.Data["TreeLink"] = treeLink | ||||
| ctx.Data["TreeNames"] = treeNames | ctx.Data["TreeNames"] = treeNames | ||||
| ctx.Data["BranchLink"] = branchLink | ctx.Data["BranchLink"] = branchLink | ||||
| ctx.HTML(200, tplRepoHome) | |||||
| if ctx.Repo.Repository.RepoType == models.RepoCourse { | |||||
| ctx.HTML(200, tplCourseHome) | |||||
| } else { | |||||
| ctx.HTML(200, tplRepoHome) | |||||
| } | |||||
| } | } | ||||
| // RenderUserCards render a page show users according the input templaet | // RenderUserCards render a page show users according the input templaet | ||||
| @@ -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) | ||||
| @@ -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. | ||||
| @@ -86,3 +91,84 @@ 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.GetRepositoryByOwnerAndAlias(ownerName, repoName) | |||||
| if err == nil { | |||||
| repoMap := make(map[string]interface{}) | |||||
| repoMap["ID"] = fmt.Sprint(repo.ID) | |||||
| repoMap["Name"] = repo.Name | |||||
| repoMap["Alias"] = repo.Alias | |||||
| if repo.RepoType == models.RepoCourse { | |||||
| //Load creator | |||||
| repo.GetCreator() | |||||
| 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 | |||||
| } | |||||
| @@ -200,14 +200,16 @@ var _hmt = _hmt || []; | |||||
| <div class="ui top secondary stackable main menu following bar dark"> | <div class="ui top secondary stackable main menu following bar dark"> | ||||
| {{template "base/head_navbar" .}} | {{template "base/head_navbar" .}} | ||||
| </div><!-- end bar --> | </div><!-- end bar --> | ||||
| <div class="notic_content" id ="notic_content" > | |||||
| <a href={{.notice.Link}} class="a_width"> | |||||
| <marquee behavior="scroll" direction="left"> | |||||
| {{.notice.Title}} | |||||
| </marquee> | |||||
| </a> | |||||
| <i class="ri-close-fill x_icon" onclick="closeNoice()"></i> | |||||
| </div> | |||||
| <!-- {{if not .IsCourse}} --> | |||||
| <div class="notic_content" id ="notic_content" style="display: none;"> | |||||
| <a href={{.notice.Link}} class="a_width"> | |||||
| <marquee behavior="scroll" direction="left"> | |||||
| {{.notice.Title}} | |||||
| </marquee> | |||||
| </a> | |||||
| <i class="ri-close-fill x_icon" onclick="closeNoice()"></i> | |||||
| </div> | |||||
| <!-- {{end}} --> | |||||
| {{end}} | {{end}} | ||||
| {{/* | {{/* | ||||
| </div> | </div> | ||||
| @@ -247,5 +249,7 @@ var _hmt = _hmt || []; | |||||
| document.getElementById("notic_content").style.display='none' | document.getElementById("notic_content").style.display='none' | ||||
| } | } | ||||
| } | } | ||||
| isShowNotice(); | |||||
| </script> | |||||
| if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||||
| isShowNotice(); | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,209 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="{{.Language}}"> | |||||
| <head data-suburl="{{AppSubUrl}}"> | |||||
| <meta charset="utf-8"> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <meta http-equiv="x-ua-compatible" content="ie=edge"> | |||||
| <title>{{if .Title}}{{.Title}} - {{end}} {{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title> | |||||
| <link rel="manifest" href="{{AppSubUrl}}/manifest.json" crossorigin="use-credentials"> | |||||
| {{if UseServiceWorker}} | |||||
| <script> | |||||
| if ('serviceWorker' in navigator) { | |||||
| navigator.serviceWorker.register('{{AppSubUrl}}/serviceworker.js').then(function(registration) { | |||||
| // Registration was successful | |||||
| console.info('ServiceWorker registration successful with scope: ', registration.scope); | |||||
| }, function(err) { | |||||
| // registration failed :( | |||||
| console.info('ServiceWorker registration failed: ', err); | |||||
| }); | |||||
| } | |||||
| </script> | |||||
| {{else}} | |||||
| <script> | |||||
| if ('serviceWorker' in navigator) { | |||||
| navigator.serviceWorker.getRegistrations().then(function(registrations) { | |||||
| registrations.forEach(function(registration) { | |||||
| registration.unregister(); | |||||
| console.info('ServiceWorker unregistered'); | |||||
| }); | |||||
| }); | |||||
| } | |||||
| </script> | |||||
| {{end}} | |||||
| <meta name="theme-color" content="{{ThemeColorMetaTag}}"> | |||||
| <meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}" /> | |||||
| <meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}" /> | |||||
| <meta name="keywords" content="{{MetaKeywords}}"> | |||||
| <meta name="referrer" content="no-referrer" /> | |||||
| <meta name="_csrf" content="{{.CsrfToken}}" /> | |||||
| {{if .IsSigned}} | |||||
| <meta name="_uid" content="{{.SignedUser.ID}}" /> | |||||
| {{end}} | |||||
| {{if .ContextUser}} | |||||
| <meta name="_context_uid" content="{{.ContextUser.ID}}" /> | |||||
| {{end}} | |||||
| {{if .SearchLimit}} | |||||
| <meta name="_search_limit" content="{{.SearchLimit}}" /> | |||||
| {{end}} | |||||
| {{if .GoGetImport}} | |||||
| <meta name="go-import" content="{{.GoGetImport}} git {{.CloneLink.HTTPS}}"> | |||||
| <meta name="go-source" content="{{.GoGetImport}} _ {{.GoDocDirectory}} {{.GoDocFile}}"> | |||||
| {{end}} | |||||
| <script> | |||||
| {{SafeJS `/* | |||||
| @licstart The following is the entire license notice for the | |||||
| JavaScript code in this page. | |||||
| Copyright (c) 2016 The Gitea Authors | |||||
| Copyright (c) 2015 The Gogs Authors | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in | |||||
| all copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
| THE SOFTWARE. | |||||
| --- | |||||
| Licensing information for additional javascript libraries can be found at: | |||||
| {{StaticUrlPrefix}}/vendor/librejs.html | |||||
| @licend The above is the entire license notice | |||||
| for the JavaScript code in this page. | |||||
| */`}} | |||||
| </script> | |||||
| <script> | |||||
| window.config = { | |||||
| AppSubUrl: '{{AppSubUrl}}', | |||||
| StaticUrlPrefix: '{{StaticUrlPrefix}}', | |||||
| csrf: '{{.CsrfToken}}', | |||||
| HighlightJS: {{if .RequireHighlightJS}}true{{else}}false{{end}}, | |||||
| Minicolors: {{if .RequireMinicolors}}true{{else}}false{{end}}, | |||||
| SimpleMDE: {{if .RequireSimpleMDE}}true{{else}}false{{end}}, | |||||
| Tribute: {{if .RequireTribute}}true{{else}}false{{end}}, | |||||
| U2F: {{if .RequireU2F}}true{{else}}false{{end}}, | |||||
| Heatmap: {{if .EnableHeatmap}}true{{else}}false{{end}}, | |||||
| heatmapUser: {{if .HeatmapUser}}'{{.HeatmapUser}}'{{else}}null{{end}}, | |||||
| NotificationSettings: { | |||||
| MinTimeout: {{NotificationSettings.MinTimeout}}, | |||||
| TimeoutStep: {{NotificationSettings.TimeoutStep}}, | |||||
| MaxTimeout: {{NotificationSettings.MaxTimeout}}, | |||||
| EventSourceUpdateTime: {{NotificationSettings.EventSourceUpdateTime}}, | |||||
| }, | |||||
| {{if .RequireTribute}} | |||||
| tributeValues: [ | |||||
| {{ range .Assignees }} | |||||
| {key: '{{.Name}} {{.FullName}}', value: '{{.Name}}', | |||||
| name: '{{.Name}}', fullname: '{{.FullName}}', avatar: '{{.RelAvatarLink}}'}, | |||||
| {{ end }} | |||||
| ], | |||||
| {{end}} | |||||
| }; | |||||
| </script> | |||||
| <link rel="shortcut icon" href="{{StaticUrlPrefix}}/img/favicon.png"> | |||||
| <link rel="mask-icon" href="{{StaticUrlPrefix}}/img/openi-safari.svg" color="#609926"> | |||||
| <link rel="fluid-icon" href="{{StaticUrlPrefix}}/img/gitea-lg.png" title="{{AppName}}"> | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/assets/font-awesome/css/font-awesome.min.css"> | |||||
| <link rel="preload" as="font" href="{{StaticUrlPrefix}}/fomantic/themes/default/assets/fonts/icons.woff2" type="font/woff2" crossorigin="anonymous"> | |||||
| <link rel="preload" as="font" href="{{StaticUrlPrefix}}/fomantic/themes/default/assets/fonts/outline-icons.woff2" type="font/woff2" crossorigin="anonymous"> | |||||
| {{if .RequireSimpleMDE}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.css"> | |||||
| {{end}} | |||||
| {{if .RequireTribute}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/tribute/tribute.css"> | |||||
| {{end}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/fomantic/semantic.min.css?v={{MD5 AppVer}}"> | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/index.css?v={{MD5 AppVer}}"> | |||||
| <noscript> | |||||
| <style> | |||||
| .dropdown:hover > .menu { display: block; } | |||||
| .ui.secondary.menu .dropdown.item > .menu { margin-top: 0; } | |||||
| </style> | |||||
| </noscript> | |||||
| {{if .RequireMinicolors}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/jquery.minicolors/jquery.minicolors.css"> | |||||
| {{end}} | |||||
| <style class="list-search-style"></style> | |||||
| {{if .PageIsUserProfile}} | |||||
| <meta property="og:title" content="{{.Owner.Name}}" /> | |||||
| <meta property="og:type" content="profile" /> | |||||
| <meta property="og:image" content="{{.Owner.AvatarLink}}" /> | |||||
| <meta property="og:url" content="{{.Owner.HTMLURL}}" /> | |||||
| {{if .Owner.Description}} | |||||
| <meta property="og:description" content="{{.Owner.Description}}"> | |||||
| {{end}} | |||||
| {{else if .Repository}} | |||||
| {{if .Issue}} | |||||
| <meta property="og:title" content="{{.Issue.Title}}" /> | |||||
| <meta property="og:url" content="{{.Issue.HTMLURL}}" /> | |||||
| {{if .Issue.Content}} | |||||
| <meta property="og:description" content="{{.Issue.Content}}" /> | |||||
| {{end}} | |||||
| {{else}} | |||||
| <meta property="og:title" content="{{.Repository.Name}}" /> | |||||
| <meta property="og:url" content="{{.Repository.HTMLURL}}" /> | |||||
| {{if .Repository.Description}} | |||||
| <meta property="og:description" content="{{.Repository.Description}}" /> | |||||
| {{end}} | |||||
| {{end}} | |||||
| <meta property="og:type" content="object" /> | |||||
| <meta property="og:image" content="{{.Repository.Owner.AvatarLink}}" /> | |||||
| {{else}} | |||||
| <meta property="og:title" content="{{AppName}}"> | |||||
| <meta property="og:type" content="website" /> | |||||
| <meta property="og:image" content="{{StaticUrlPrefix}}/img/gitea-lg.png" /> | |||||
| <meta property="og:url" content="{{AppUrl}}" /> | |||||
| <meta property="og:description" content="{{MetaDescription}}"> | |||||
| {{end}} | |||||
| <meta property="og:site_name" content="{{AppName}}" /> | |||||
| {{if .IsSigned }} | |||||
| {{ if ne .SignedUser.Theme "gitea" }} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/theme-{{.SignedUser.Theme}}.css?v={{MD5 AppVer}}"> | |||||
| {{end}} | |||||
| {{else if ne DefaultTheme "gitea"}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/theme-{{DefaultTheme}}.css?v={{MD5 AppVer}}"> | |||||
| {{end}} | |||||
| <link rel="stylesheet" href="/RemixIcon_Fonts_v2.5.0/fonts/remixicon.css"> | |||||
| {{template "custom/header" .}} | |||||
| <script> | |||||
| var _hmt = _hmt || []; | |||||
| (function() { | |||||
| var hm = document.createElement("script"); | |||||
| hm.src = "https://hm.baidu.com/hm.js?46149a0b61fdeddfe427ff4de63794ba"; | |||||
| var s = document.getElementsByTagName("script")[0]; | |||||
| s.parentNode.insertBefore(hm, s); | |||||
| })(); | |||||
| </script> | |||||
| <script src="/self/func.js" type="text/javascript"></script> | |||||
| </head> | |||||
| <body> | |||||
| {{template "custom/body_outer_pre" .}} | |||||
| <div class="full height"> | |||||
| <noscript>{{.i18n.Tr "enable_javascript"}}</noscript> | |||||
| {{template "custom/body_inner_pre" .}} | |||||
| {{if not .PageIsInstall}} | |||||
| <div class="ui top secondary stackable main menu following bar dark"> | |||||
| {{template "base/head_navbar" .}} | |||||
| </div><!-- end bar --> | |||||
| {{end}} | |||||
| {{/* | |||||
| </div> | |||||
| </body> | |||||
| </html> | |||||
| */}} | |||||
| @@ -201,7 +201,7 @@ var _hmt = _hmt || []; | |||||
| <div class="ui top secondary stackable main menu following bar dark"> | <div class="ui top secondary stackable main menu following bar dark"> | ||||
| {{template "base/head_navbar_fluid" .}} | {{template "base/head_navbar_fluid" .}} | ||||
| </div><!-- end bar --> | </div><!-- end bar --> | ||||
| <div class="notic_content" id ="notic_content" > | |||||
| <div class="notic_content" id ="notic_content" style="display: none;"> | |||||
| <a href={{.notice.Link}} class="a_width"> | <a href={{.notice.Link}} class="a_width"> | ||||
| <marquee behavior="scroll" direction="left"> | <marquee behavior="scroll" direction="left"> | ||||
| {{.notice.Title}} | {{.notice.Title}} | ||||
| @@ -248,5 +248,7 @@ var _hmt = _hmt || []; | |||||
| document.getElementById("notic_content").style.display='none' | document.getElementById("notic_content").style.display='none' | ||||
| } | } | ||||
| } | } | ||||
| isShowNotice(); | |||||
| if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||||
| isShowNotice(); | |||||
| } | |||||
| </script> | </script> | ||||
| @@ -205,7 +205,7 @@ var _hmt = _hmt || []; | |||||
| <div class="ui top secondary stackable main menu following bar dark"> | <div class="ui top secondary stackable main menu following bar dark"> | ||||
| {{template "base/head_navbar" .}} | {{template "base/head_navbar" .}} | ||||
| </div><!-- end bar --> | </div><!-- end bar --> | ||||
| <div class="notic_content" id ="notic_content" > | |||||
| <div class="notic_content" id ="notic_content" style="display: none;" > | |||||
| <a href={{.notice.Link}} class="a_width"> | <a href={{.notice.Link}} class="a_width"> | ||||
| <marquee behavior="scroll" direction="left"> | <marquee behavior="scroll" direction="left"> | ||||
| {{.notice.Title}} | {{.notice.Title}} | ||||
| @@ -252,5 +252,7 @@ var _hmt = _hmt || []; | |||||
| document.getElementById("notic_content").style.display='none' | document.getElementById("notic_content").style.display='none' | ||||
| } | } | ||||
| } | } | ||||
| isShowNotice(); | |||||
| if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||||
| isShowNotice(); | |||||
| } | |||||
| </script> | </script> | ||||
| @@ -201,7 +201,7 @@ var _hmt = _hmt || []; | |||||
| <div class="ui top secondary stackable main menu following bar dark"> | <div class="ui top secondary stackable main menu following bar dark"> | ||||
| {{template "base/head_navbar_pro" .}} | {{template "base/head_navbar_pro" .}} | ||||
| </div><!-- end bar --> | </div><!-- end bar --> | ||||
| <div class="notic_content" id ="notic_content" > | |||||
| <div class="notic_content" id ="notic_content" style="display: none;" > | |||||
| <a href={{.notice.Link}} class="a_width"> | <a href={{.notice.Link}} class="a_width"> | ||||
| <marquee behavior="scroll" direction="left"> | <marquee behavior="scroll" direction="left"> | ||||
| {{.notice.Title}} | {{.notice.Title}} | ||||
| @@ -249,5 +249,7 @@ var _hmt = _hmt || []; | |||||
| document.getElementById("notic_content").style.display='none' | document.getElementById("notic_content").style.display='none' | ||||
| } | } | ||||
| } | } | ||||
| isShowNotice(); | |||||
| if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||||
| isShowNotice(); | |||||
| } | |||||
| </script> | </script> | ||||
| @@ -26,7 +26,7 @@ | |||||
| <div class="item"> | <div class="item"> | ||||
| <div class="ui header"> | <div class="ui header"> | ||||
| <a class="name" href="{{.Repo.Link}}/datasets?type=0"> | <a class="name" href="{{.Repo.Link}}/datasets?type=0"> | ||||
| {{.Repo.OwnerName}} / {{.Title}} | |||||
| {{.Repo.OwnerName}} / {{.Repo.Alias}} | |||||
| </a> | </a> | ||||
| <div class="ui right metas"> | <div class="ui right metas"> | ||||
| {{if .Task}} | {{if .Task}} | ||||
| @@ -53,4 +53,4 @@ | |||||
| </div> | </div> | ||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| </div> | |||||
| </div> | |||||
| @@ -0,0 +1,137 @@ | |||||
| <style> | |||||
| .text-right{ | |||||
| float:right !important; | |||||
| } | |||||
| .header{ | |||||
| font-weight:bold; | |||||
| font-size: 18px; | |||||
| font-family: SourceHanSansSC-medium; | |||||
| } | |||||
| .cor{ | |||||
| color:#0366D6 !important; | |||||
| } | |||||
| .header_card{ | |||||
| /* color:#003A8C !important; */ | |||||
| color:#101010 !important; | |||||
| margin: 10px 0 0px 0; | |||||
| height: 25px; | |||||
| font-size: 18px; | |||||
| } | |||||
| .marg{ | |||||
| margin: 0 5px !important; | |||||
| } | |||||
| .content_list{ | |||||
| max-height: 130px; | |||||
| overflow: auto; | |||||
| } | |||||
| .Relist{ | |||||
| color:#0366D6 !important; | |||||
| } | |||||
| .descript_height{ | |||||
| color: #999999 !important; | |||||
| margin: 10px 0; | |||||
| height: 40px !important; | |||||
| word-break:break-all; | |||||
| line-height: 20px; | |||||
| overflow: hidden; | |||||
| /* overflow: hidden!important; | |||||
| word-wrap:break-word!important; */ | |||||
| } | |||||
| .tags_height{ | |||||
| height: 30px !important; | |||||
| } | |||||
| .full_height{ | |||||
| height: 100%; | |||||
| } | |||||
| .omit{ | |||||
| overflow: hidden; white-space: nowrap; text-overflow: ellipsis; | |||||
| } | |||||
| /deep/ ui.checkbox input[type=checkbox]::after{ | |||||
| border: 1px solid #0366D6 !important; | |||||
| } | |||||
| .nowrap-2 { | |||||
| /* height: 2.837em; */ | |||||
| /* line-height: 1.4285em; */ | |||||
| overflow: hidden; | |||||
| overflow: hidden; | |||||
| display: -webkit-box; | |||||
| -webkit-line-clamp: 2; | |||||
| -webkit-box-orient: vertical; | |||||
| } | |||||
| .course_topic{ | |||||
| color:#0366D6 !important; | |||||
| background-color: rgba(179, 219, 219, 0.4) !important; | |||||
| font-weight:normal !important | |||||
| } | |||||
| .tag_text{ | |||||
| /* margin-top: 2px; */ | |||||
| /* margin-left: 0.5em; */ | |||||
| font-size: 14px; | |||||
| } | |||||
| .card{ | |||||
| box-shadow: 0px 4px 4px 0px rgba(232, 232, 232, 60) !important; | |||||
| border-radius: 5px; | |||||
| border:1px solid #E8E8E8 !important; | |||||
| } | |||||
| .tags{ | |||||
| position: relative; | |||||
| overflow: hidden; | |||||
| height: 30px; | |||||
| line-height: 30px; | |||||
| -webkit-line-clamp: 1; | |||||
| -webkit-box-orient: vertical; | |||||
| } | |||||
| </style> | |||||
| <div style="width: 100%;"> | |||||
| <div class="ui three cards" style="margin-bottom: 10px;"> | |||||
| {{range .Repos}} | |||||
| <div class="card " > | |||||
| <div class="extra full_height cor" > | |||||
| <div class="content " > | |||||
| {{if .Topics }} | |||||
| <div class="omit tags " style="position: relative;"> | |||||
| {{range .Topics}} | |||||
| {{if ne . "" }}<a style="max-width:100%;margin: 5px 0;display:inline-flex;" ><span class="ui small label topic course_topic" >{{.}}</span></a>{{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| <div class=" header header_card omit" > | |||||
| <a class="header_card image poping up " href="{{.Link}}" data-content="{{.Alias}}" data-position="top left" data-variation="tiny inverted"> {{.Alias}}</a> | |||||
| </div> | |||||
| <div class='content descript_height nowrap-2'> | |||||
| {{.Description}} | |||||
| </div> | |||||
| </div> | |||||
| <div class=" extra content" style="color:#888888;border-top: none !important;padding-top: 0px;margin-bottom: 15px;"> | |||||
| <div class="left aligned author"> | |||||
| <!-- <span > --> | |||||
| {{if .Creator }} | |||||
| <a href="{{.Creator.Name}}" title="{{.Creator.Name}}"> | |||||
| <img class="ui avatar image" style="width: 22px;height:22px;margin-top:-5px" src="{{.Creator.RelAvatarLink}}"> | |||||
| </a> | |||||
| {{else}} | |||||
| <a href="{{.Owner.Name}}" title="{{.Owner.Name}}"> | |||||
| <img class="ui avatar image" style="width: 22px;height:22px;margin-top:-5px" src="{{.Owner.RelAvatarLink}}"> | |||||
| </a> | |||||
| {{end}} | |||||
| {{$.i18n.Tr "org.repo_released"}} : {{TimeSinceUnixShort .CreatedUnix}} | |||||
| <!-- </span> --> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| @@ -8,20 +8,58 @@ | |||||
| <img class="ui image" src="{{.SizedRelAvatarLink 100}}"> | <img class="ui image" src="{{.SizedRelAvatarLink 100}}"> | ||||
| <span class="text thin grey"><a href="{{.HomeLink}}">{{.DisplayName}}</a></span> | <span class="text thin grey"><a href="{{.HomeLink}}">{{.DisplayName}}</a></span> | ||||
| {{end}} | {{end}} | ||||
| {{if .IsOrganizationOwner}} | |||||
| <div class="ui right"> | |||||
| <a class="ui green button" href="{{.OrgLink}}/teams/new">{{svg "octicon-plus" 16}} {{.i18n.Tr "org.create_new_team"}}</a> | |||||
| </div> | |||||
| {{end}} | |||||
| {{if .CanCreateOrgRepo}} | |||||
| <div class="ui right"> | |||||
| <a class="ui green button" href="{{AppSubUrl}}/repo/create?org={{.Org.ID}}">{{svg "octicon-plus" 16}} {{.i18n.Tr "new_repo"}}</a> | |||||
| </div> | |||||
| {{end}} | |||||
| {{if .IsCourse}} | |||||
| {{if .CanCreateOrgRepo}} | |||||
| <div class="ui right"> | |||||
| <a class="ui green button" onclick="jion_course_team()">{{svg "octicon-plus" 16}} {{.i18n.Tr "org.teams.join_teams"}}</a> | |||||
| </div> | |||||
| {{end}} | |||||
| {{else}} | |||||
| {{if .IsOrganizationOwner}} | |||||
| <div class="ui right"> | |||||
| <a class="ui green button" href="{{.OrgLink}}/teams/new">{{svg "octicon-plus" 16}} {{.i18n.Tr "org.create_new_team"}}</a> | |||||
| </div> | |||||
| {{end}} | |||||
| {{if .CanCreateOrgRepo}} | |||||
| <div class="ui right"> | |||||
| <a class="ui green button" href="{{AppSubUrl}}/repo/create?org={{.Org.ID}}">{{svg "octicon-plus" 16}} {{.i18n.Tr "new_repo"}}</a> | |||||
| </div> | |||||
| {{end}} | |||||
| {{end}} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <script> | |||||
| function jion_course_team(){ | |||||
| $.ajax({ | |||||
| type:"GET", | |||||
| url:"/course/addOrg", | |||||
| dataType:"json", | |||||
| async:false, | |||||
| success:function(json){ | |||||
| data = json; | |||||
| if (data.code==0) { | |||||
| $('.alert').html('{{.i18n.Tr "repo.computing.success"}}').removeClass('alert-danger').addClass('alert-success').show().delay(2000).fadeOut(); | |||||
| } else { | |||||
| $('.alert').html(data.error_msg).removeClass('alert-success').addClass('alert-danger').show().delay(5000).fadeOut(); | |||||
| } | |||||
| setTimeout("location.reload()",2000); | |||||
| // location.reload() | |||||
| // if(data.code==0){ | |||||
| // alert("Join success") | |||||
| // location.reload() | |||||
| // }else{ | |||||
| // alert("Join failure") | |||||
| // } | |||||
| }, | |||||
| }); | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,32 @@ | |||||
| <div class="organization-header"> | |||||
| <div class="ui container"> | |||||
| <div class="ui vertically grid head"> | |||||
| <div class="column"> | |||||
| <div class="ui header"> | |||||
| {{with .Org}} | |||||
| <img class="ui image" src="{{.SizedRelAvatarLink 100}}"> | |||||
| <span class="text thin grey"><a href="{{.HomeLink}}">{{.DisplayName}}</a></span> | |||||
| {{end}} | |||||
| {{if .IsCourse}} | |||||
| {{if .IsOrganizationOwner}} | |||||
| <div class="ui right"> | |||||
| <a class="ui green button" href="{{.OrgLink}}/teams/new">{{svg "octicon-plus" 16}} {{.i18n.Tr "org.create_new_team"}}</a> | |||||
| </div> | |||||
| {{end}} | |||||
| {{else}} | |||||
| {{if .IsOrganizationOwner}} | |||||
| <div class="ui right"> | |||||
| <a class="ui green button" href="{{.OrgLink}}/teams/new">{{svg "octicon-plus" 16}} {{.i18n.Tr "org.create_new_team"}}</a> | |||||
| </div> | |||||
| {{end}} | |||||
| {{if .CanCreateOrgRepo}} | |||||
| <div class="ui right"> | |||||
| <a class="ui green button" href="{{AppSubUrl}}/repo/create?org={{.Org.ID}}">{{svg "octicon-plus" 16}} {{.i18n.Tr "new_repo"}}</a> | |||||
| </div> | |||||
| {{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| @@ -0,0 +1,454 @@ | |||||
| <style> | |||||
| .organization-info_1000{ | |||||
| background: #F5F5F6 !important; | |||||
| padding-top: 30px; | |||||
| margin-bottom: 0px !important; | |||||
| } | |||||
| .organization-info >.container { | |||||
| overflow: auto; | |||||
| background: #f5f5f6 !important; | |||||
| padding-top: 30px; | |||||
| padding-bottom: 20px; | |||||
| background-size: cover; | |||||
| border-radius: 5px; | |||||
| border: none !important | |||||
| } | |||||
| .organization.profile #org-avatar { | |||||
| border:none !important | |||||
| } | |||||
| .desc { | |||||
| font-size: 14px; | |||||
| margin-bottom: 10px !important; | |||||
| } | |||||
| .item { | |||||
| display: inline-block; | |||||
| margin-right: 10px; | |||||
| } | |||||
| .item .icon { | |||||
| margin-right: 5px; | |||||
| } | |||||
| .organization-info >.container { | |||||
| padding-bottom:0px !important; | |||||
| } | |||||
| .tag_bg{ | |||||
| background-color: #0366D6 !important; | |||||
| color:#FFFFFF !important; | |||||
| } | |||||
| .course{ | |||||
| padding:10px 0 15px !important; | |||||
| } | |||||
| .course_color{ | |||||
| color: #FA8C16; | |||||
| } | |||||
| .tag_lable{ | |||||
| border: 1px solid rgba(232, 232, 232, 100) ; | |||||
| border-radius: 4px; | |||||
| color: rgba(65, 80, 88, 100); | |||||
| font-family: Microsoft Yahei; | |||||
| font-size: 14px; | |||||
| padding: 0.3em 0.5em; | |||||
| height: 30px; | |||||
| text-align: center; | |||||
| margin: 0.2em; | |||||
| } | |||||
| .tag_lable_first{ | |||||
| border: 1px solid rgba(232, 232, 232, 100) ; | |||||
| border-radius: 4px; | |||||
| color: rgba(65, 80, 88, 100); | |||||
| font-family: Microsoft Yahei; | |||||
| font-size: 14px; | |||||
| padding: 0.3em 0.5em; | |||||
| height: 30px; | |||||
| text-align: center; | |||||
| margin: 0.2em; | |||||
| margin-left: none; | |||||
| } | |||||
| .tag_key{ | |||||
| max-width:100%; | |||||
| margin: 3px 3px; | |||||
| display:inline-flex; | |||||
| } | |||||
| .bpadding{ | |||||
| padding:10px 40px | |||||
| } | |||||
| .omit{ | |||||
| overflow: hidden; white-space: nowrap; text-overflow: ellipsis; | |||||
| } | |||||
| .noborder{ | |||||
| border: none !important; | |||||
| } | |||||
| .div_bt{ | |||||
| text-align: center; | |||||
| margin-top: 5px; | |||||
| margin-top: 10px; | |||||
| } | |||||
| </style> | |||||
| {{template "base/head" .}} | |||||
| <!-- 提示框 --> | |||||
| <div class="alert"></div> | |||||
| <div class="organization profile"> | |||||
| {{/* overflow: auto is the clearfix - this avoids the image going beyond | |||||
| the container where it is supposed to stay inside. */}} | |||||
| <div class="organization-info organization-info_1000"> | |||||
| <div class="ui center aligned container " style="overflow: auto"> | |||||
| <img class="ui left image" id="org-avatar" src="{{.Org.SizedRelAvatarLink 140}}"/> | |||||
| <div class="content" style="text-align: left;margin-left:100px" > | |||||
| <div class="ui header" > | |||||
| {{.Org.DisplayName}} | |||||
| </div> | |||||
| <div class="description" > | |||||
| {{if .Org.Description}}<p class="text grey desc">{{.Org.Description}}</p>{{end}} | |||||
| </div> | |||||
| <div class="meta" style="display: inline-flex;"> | |||||
| {{if .Org.Location}}<div class="item">{{svg "octicon-location" 16}} <span>{{.Org.Location}}</span></div>{{end}} | |||||
| {{if .Org.Website}}<div class="item">{{svg "octicon-link" 16}} <a target="_blank" rel="noopener noreferrer" href="{{.Org.Website}}">{{.Org.Website}}</a></div>{{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{template "org/navber_course" .}} | |||||
| <div class="ui container"> | |||||
| <!-- 新增 --> | |||||
| <div class="ui stackable grid"> | |||||
| <div class="ui sixteen wide computer column"> | |||||
| <div class="ui mobile reversed stackable grid"> | |||||
| <div class="ui ten wide tablet twelve wide computer column" id='tag'> | |||||
| <a class="{{if eq $.Keyword "" }} tag_bg {{end}} tag_key ui small tag_lable topic omit" href="{{$.Link}}?" >{{$.i18n.Tr "org.all_keywords"}}</span></a> | |||||
| {{range .CoursesKeywords}} | |||||
| {{if ne . ""}} | |||||
| <a class="{{if eq $.Keyword . }} tag_bg {{end}} tag_key ui small tag_lable topic omit" href="{{$.Link}}?q={{.}}" > | |||||
| {{.}} | |||||
| </a> | |||||
| {{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| <div class="ui sixteen wide mobile six wide tablet four wide computer column"> | |||||
| <div class=" ui bottom attached segment text center noborder text center" > | |||||
| {{if .IsSigned}} | |||||
| <a style="width: 80%;" class="ui green button bpadding" href="{{AppSubUrl}}/course/create"><i class="ri-folder-add-line" style="vertical-align: middle;"></i> {{.i18n.Tr "org.release_course"}} </a> | |||||
| {{else}} | |||||
| <a style="width: 80%;" class="ui green button bpadding" href="{{AppSubUrl}}/user/login"><i class="ri-folder-add-line" style="vertical-align: middle;"></i> {{.i18n.Tr "org.release_course"}} </a> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <!-- 全部 --> | |||||
| <div class="ui stackable grid"> | |||||
| <div class="ui sixteen wide computer column"> | |||||
| <div class="ui mobile reversed stackable grid"> | |||||
| <div class="ui ten wide tablet twelve wide computer column"> | |||||
| {{template "org/course_list" .}} | |||||
| {{template "base/paginate" .}} | |||||
| </div> | |||||
| <div class="ui sixteen wide mobile six wide tablet four wide computer column"> | |||||
| {{if .tags}} | |||||
| <h4 class="ui top attached header noborder"> | |||||
| <strong>{{.i18n.Tr "org.selected_couse"}}</strong> | |||||
| {{if .IsOrganizationOwner}} | |||||
| <div class="ui right"> | |||||
| <a class="text grey" id="model" onclick="showcreate()">{{svg "octicon-gear" 16}}</a> | |||||
| </div> | |||||
| {{end}} | |||||
| </h4> | |||||
| <div class="ui attached table segment course items noborder"> | |||||
| {{ range .tags}} | |||||
| {{if eq .TagName "精选项目"}} | |||||
| {{range $i, $v := .RepoList}} | |||||
| {{if gt $i 0}} | |||||
| <div class="ui divider" style="margin-bottom:10px;"></div> | |||||
| {{end}} | |||||
| <div class="item"> | |||||
| <i class="large icon ri-bookmark-3-line course_color"></i> | |||||
| <div class="content" style="margin-left: 10px;"> | |||||
| <a href="{{.Link}}"><strong class="team-name">{{.Alias}}</strong></a> | |||||
| <p class="text grey"> | |||||
| {{if ne .CreatorID 0}} | |||||
| {{$.i18n.Tr "home.contributor"}} : {{.Creator.Name}} | |||||
| {{else}} | |||||
| {{$.i18n.Tr "home.contributor"}}:{{.Owner.Name}} | |||||
| {{end}} | |||||
| </p> | |||||
| </div> | |||||
| </div> | |||||
| {{end}} | |||||
| {{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| {{end}} | |||||
| <h4 class="ui top attached header noborder"> | |||||
| <strong>{{.i18n.Tr "org.people"}}</strong> | |||||
| <div class="ui right"> | |||||
| <a class="text grey" href="{{.OrgLink}}/members">{{.MembersTotal}} {{svg "octicon-chevron-right" 16}}</a> | |||||
| </div> | |||||
| </h4> | |||||
| <div class="ui attached segment members course noborder"> | |||||
| {{$isMember := .IsOrganizationMember}} | |||||
| {{range .Members}} | |||||
| {{if or $isMember (.IsPublicMember $.Org.ID)}} | |||||
| <a href="{{.HomeLink}}" title="{{.Name}}{{if .FullName}} ({{.FullName}}){{end}}"><img class="ui avatar" src="{{.RelAvatarLink}}"></a> | |||||
| {{end}} | |||||
| {{end}} | |||||
| <div class="ui bottom attached segment text center noborder"> | |||||
| {{if .IsSigned}} | |||||
| <a class="ui blue basic button" onclick="jion_course_team()" style="width: 80%;"> <i class="ri-user-add-line"></i> {{.i18n.Tr "org.teams.join_teams"}}</a> | |||||
| {{else}} | |||||
| <a class="ui blue basic button" href="{{AppSubUrl}}/user/login" style="width: 80%;"> <i class="ri-user-add-line"></i> {{.i18n.Tr "org.teams.join_teams"}}</a> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| {{if .IsOrganizationMember}} | |||||
| <div class="ui top attached header noborder"> | |||||
| <strong>{{.i18n.Tr "org.teams"}}</strong> | |||||
| <div class="ui right"> | |||||
| <a class="text grey" href="{{.OrgLink}}/teams"><span>{{.Org.NumTeams}}</span> {{svg "octicon-chevron-right" 16}}</a> | |||||
| </div> | |||||
| </div> | |||||
| <div class="ui attached table segment teams noborder"> | |||||
| {{range .Teams}} | |||||
| <div style="margin-top: 10px;"> | |||||
| <a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong class="team-name">{{.Name}}</strong></a> | |||||
| <p class="text grey"> | |||||
| <a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.NumMembers}}</strong> {{$.i18n.Tr "org.lower_members"}}</a> · | |||||
| <a href="{{$.OrgLink}}/teams/{{.LowerName}}/repositories"><strong>{{.NumRepos}}</strong> {{$.i18n.Tr "org.lower_repositories"}}</a> | |||||
| </p> | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| {{if .IsOrganizationOwner}} | |||||
| <div class="ui bottom attached segment text center noborder"> | |||||
| <a class="ui blue basic button" style="width: 80%;" href="{{.OrgLink}}/teams/new">{{.i18n.Tr "org.create_new_team"}}</a> | |||||
| </div> | |||||
| {{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="ui modal"> | |||||
| <div class="header" style="padding: 1rem;background-color: rgba(240, 240, 240, 100);"> | |||||
| <h4 id="model_header">{{.i18n.Tr "org.custom_select_courses"}}</h4> | |||||
| </div> | |||||
| <div class="content content-padding" style="color: black;"> | |||||
| <p>{{.i18n.Tr "org.max_selectedPro"}}</p> | |||||
| <div class="ui search" > | |||||
| <div class="ui input" style="width: 100%;"> | |||||
| <input type="text" id = 'search_selectPro' placeholder="Search ..." value = '' oninput="search()"> | |||||
| </div> | |||||
| </div> | |||||
| <div style="margin: 10px ;"> | |||||
| <div id ='org_list' style="margin-bottom: 20px;"class="content_list" > | |||||
| </div> | |||||
| </div> | |||||
| <p id='recommend'></p> | |||||
| <div class="inline field" style="margin-left: 37%;"> | |||||
| <div class="actions"> | |||||
| <button id="submitId" type="button" class="ui create_train_job green deny button" onclick="saveSeletedPro(1)"> | |||||
| {{.i18n.Tr "explore.save"}} | |||||
| </button> | |||||
| <button class="ui button cancel" >{{.i18n.Tr "explore.cancel"}}</button> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{template "base/footer" .}} | |||||
| <script> | |||||
| var data; | |||||
| var filterData=[]; | |||||
| var num=0; | |||||
| function showcreate(obj){ | |||||
| document.getElementById("search_selectPro").value='' | |||||
| $('.ui.modal') | |||||
| .modal({ | |||||
| centered: false, | |||||
| onShow:function(){ | |||||
| $("#org_list").empty() | |||||
| getPro(1) | |||||
| }, | |||||
| onHide:function(){ | |||||
| } | |||||
| }) | |||||
| .modal('show') | |||||
| } | |||||
| function getPro(typeTag){ | |||||
| $.ajax({ | |||||
| type:"GET", | |||||
| url:"/org/{{.Org.Name}}/org_tag/repo_list?tagId="+typeTag, | |||||
| dataType:"json", | |||||
| async:false, | |||||
| success:function(json){ | |||||
| data = json.data; | |||||
| var n_length = data.length | |||||
| pro_html = getHTML(data) | |||||
| $("#org_list").append(pro_html) | |||||
| // console.log('原始',data) | |||||
| checkedNum(0) | |||||
| } | |||||
| }); | |||||
| } | |||||
| function getHTML(data){ | |||||
| let pro_html='' | |||||
| for (let i=0;i<data.length;i++){ | |||||
| if (data[i].Selected==true){ | |||||
| console.log("data[i]:",data[i]) | |||||
| pro_html += `<div class="ui checkbox" style="width: 33%;margin-bottom:10px" > <input type="checkbox" id = " ${i}" checked="" onclick="checkedNum(${i})" class="Relist" name ='select_pro_name' data-repoid="${data[i].RepoID}" data-reponame="${data[i].RepoName}" data-selected=${data[i].Selected} > <label class='omit image poping up' data-content=${data[i].RepoName} data-position="top left " data-variation="mini"> ${data[i].RepoName}</label></div>` | |||||
| pro_html += '</div>' | |||||
| } | |||||
| else{ | |||||
| pro_html += `<div class="ui checkbox" style="width: 33%;margin-bottom:10px" > <input type="checkbox" id = "${i}" onclick="checkedNum(${i})" class="Relist" name ='select_pro_name' data-repoid="${data[i].RepoID}" data-reponame="${data[i].RepoName}" data-selected= ${data[i].Selected}> <label class='omit image poping up' data-content=${data[i].RepoName} data-position="top left " data-variation="mini"> ${data[i].RepoName} </label></div>` | |||||
| pro_html += '</div>' | |||||
| } | |||||
| } | |||||
| return pro_html | |||||
| } | |||||
| function saveSeletedPro(typeTag){ | |||||
| var saveData=[]; | |||||
| $('input[name="select_pro_name"]:checked').each(function(){ | |||||
| // console.log('值',this.dataset.repoid) | |||||
| saveData.push(parseInt(this.dataset.repoid)); | |||||
| }) | |||||
| if(saveData.length>9){ | |||||
| alert("{{.i18n.Tr "org.save_fail_tips"}}") | |||||
| return | |||||
| } | |||||
| $.ajax({ | |||||
| type:"POST", | |||||
| url:"/org/{{.Org.Name}}/org_tag/repo_submit?tagId="+typeTag, | |||||
| contentType:'application/json', | |||||
| dataType:"json", | |||||
| async:false, | |||||
| data:JSON.stringify({'repoList':saveData | |||||
| }), | |||||
| success:function(res){ | |||||
| // console.log('保存成功'); | |||||
| location.reload() | |||||
| } | |||||
| }); | |||||
| } | |||||
| function getSelecteData(){ | |||||
| var selectedData=[]; | |||||
| $('input[name="select_pro_name"]:checked').each(function(){ | |||||
| // console.log(this) | |||||
| // console.log('值',this.dataset.selected) | |||||
| selectedData.push({"RepoID":parseInt(this.dataset.repoid),"RepoName":this.dataset.reponame,"Selected":JSON.parse(this.dataset.selected)}); | |||||
| }) | |||||
| return selectedData | |||||
| } | |||||
| function search(){ | |||||
| var selectedData = getSelecteData(); | |||||
| var searchValue = document.getElementById("search_selectPro").value; | |||||
| filterData=[]; | |||||
| console.log("searchValue:",searchValue) | |||||
| for (let i=0;i<data.length;i++){ | |||||
| var isInclude=false; | |||||
| if(data[i].RepoName.toLowerCase().includes(searchValue.toLowerCase())){ | |||||
| filterData.push(data[i]) | |||||
| } | |||||
| } | |||||
| // console.log("选中的值:",selectedData) | |||||
| // console.log("筛选包括选中的值:",filterData) | |||||
| var showData=[]; | |||||
| for(i=0;i<selectedData.length;i++){ | |||||
| filterData =filterData.filter((item)=>{ | |||||
| return item.RepoID!=selectedData[i].RepoID | |||||
| }); | |||||
| } | |||||
| // console.log("筛选后不包括选中的值:",filterData) | |||||
| $("#org_list").empty() | |||||
| if(searchValue!=""){ | |||||
| if (filterData.length!=0){ | |||||
| var pro_html = getHTML(selectedData); | |||||
| console.log("selectedData_pro_html:",pro_html) | |||||
| $("#org_list").append(pro_html) | |||||
| pro_html= getHTML(filterData); | |||||
| $("#org_list").append(pro_html) | |||||
| }else{ | |||||
| var pro_html = getHTML(selectedData); | |||||
| $("#org_list").append(pro_html) | |||||
| } | |||||
| }else{ | |||||
| var pro_html = getHTML(data); | |||||
| $("#org_list").append(pro_html) | |||||
| } | |||||
| } | |||||
| function checkedNum(id){ | |||||
| num=0; | |||||
| var inputs = document.getElementsByName("select_pro_name") | |||||
| for (var i=0;i<inputs.length;i++){ | |||||
| if(inputs[i].checked){ | |||||
| num++ | |||||
| if(num>9){ | |||||
| document.getElementById(id).checked=false | |||||
| alert( "{{.i18n.Tr "org.select_again"}}") | |||||
| return | |||||
| } | |||||
| } | |||||
| } | |||||
| var show_num = 9-num; | |||||
| let rec = "{{.i18n.Tr "org.recommend_remain_pro"}}" | |||||
| document.getElementById("recommend").innerHTML=rec +" : "+ show_num | |||||
| } | |||||
| function jion_course_team(){ | |||||
| $.ajax({ | |||||
| type:"GET", | |||||
| url:"/course/addOrg", | |||||
| dataType:"json", | |||||
| async:false, | |||||
| success:function(json){ | |||||
| data = json; | |||||
| if (data.code==0) { | |||||
| $('.alert').html('{{.i18n.Tr "repo.computing.success"}}').removeClass('alert-danger').addClass('alert-success').show().delay(2000).fadeOut(); | |||||
| } else { | |||||
| $('.alert').html(data.error_msg).removeClass('alert-success').addClass('alert-danger').show().delay(5000).fadeOut(); | |||||
| } | |||||
| setTimeout("location.reload()",2000); | |||||
| }, | |||||
| }); | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,130 @@ | |||||
| <style> | |||||
| .organization-header{ | |||||
| margin-bottom: 0px !important; | |||||
| border-bottom:none !important | |||||
| } | |||||
| .course_header{ | |||||
| color: rgba(3, 102, 214, 100); | |||||
| font-size: 16px; | |||||
| text-align: left; | |||||
| font-family: SourceHanSansSC-medium; | |||||
| font-weight: bolder; | |||||
| vertical-align: bottom; | |||||
| } | |||||
| .meb_label{ | |||||
| border-radius: 5px; | |||||
| background-color: rgba(255, 255, 255, 100) !important; | |||||
| color: rgba(255, 255, 255, 100); | |||||
| font-size: 12px; | |||||
| text-align: center; | |||||
| font-family: Roboto; | |||||
| border: 1px solid rgba(212, 212, 213, 100) !important; | |||||
| margin-left: 1em !important; | |||||
| } | |||||
| .ui.small.label.topic { | |||||
| margin-bottom: 0px !important; | |||||
| } | |||||
| .cor{ | |||||
| color:#888888 !important | |||||
| } | |||||
| .card_course{ | |||||
| padding:1em; | |||||
| border: 1px solid #F5F5F6; | |||||
| margin:1em;box-shadow: 0px 4px 4px 0px rgba(232, 232, 232, 60); | |||||
| border-radius: 5px;border: 1px solid rgba(232, 232, 232, 100); | |||||
| display: flex; width:calc(33.33333333333333% - 2em) | |||||
| } | |||||
| .button_leaveOrg{ | |||||
| position:absolute;right: -1px;top:0px; | |||||
| } | |||||
| .bt_mr{ | |||||
| margin-right: 0px !important; | |||||
| font-size: 12px !important; | |||||
| padding: 5px !important; | |||||
| } | |||||
| </style> | |||||
| {{template "base/head" .}} | |||||
| <!-- 提示框 --> | |||||
| <div class="alert"></div> | |||||
| <div class="organization members"> | |||||
| {{template "org/header" .}} | |||||
| {{template "org/navber_course" .}} | |||||
| <div class="ui container"> | |||||
| {{template "base/alert" .}} | |||||
| <div class="ui stackable grid"> | |||||
| <div class="ui sixteen wide computer column list"> | |||||
| <div class="ui three cards" > | |||||
| {{ range .Members}} | |||||
| <div class="card_course" style="position: relative;" id = "{{.ID}}" onmouseover ="show_bt( {{.ID}} )" onmouseout="hide_bt({{.ID}})"> | |||||
| <div > | |||||
| <img class="ui avatar " style="width: 45px;height:45px;margin-top: 2px;" src="{{.SizedRelAvatarLink 48}}"> | |||||
| <div class="meta" style="text-align: center; margin-top: 0.5em;"> | |||||
| {{ $isPublic := index $.MembersIsPublicMember .ID}} | |||||
| {{if $isPublic}} | |||||
| {{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}} <a class="link-action" href data-url="{{$.OrgLink}}/members/action/private?uid={{.ID}}">{{$.i18n.Tr "org.members.public_helper"}}</a> {{end}} | |||||
| {{else}} | |||||
| {{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}} <a class="link-action" href data-url="{{$.OrgLink}}/members/action/public?uid={{.ID}}">{{$.i18n.Tr "org.members.private_helper"}}</a> {{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| <div style="padding-left: 0.8em;"> | |||||
| <div> | |||||
| <a href="{{.HomeLink}}" class="course_header"> {{.Name}}</a> | |||||
| <div class="ui small label topic meb_label" > | |||||
| {{if index $.MembersIsUserOrgOwner .ID}} {{$.i18n.Tr "org.members.owner"}}{{else}}{{$.i18n.Tr "org.members.member"}}{{end}} | |||||
| </div> | |||||
| </div> | |||||
| <div class="meta" style="margin-top: 0.5em;"> | |||||
| {{.FullName}} | |||||
| </div> | |||||
| <div class="meta" style="margin-top: 0.5em;"> | |||||
| {{svg "octicon-mail" 16}} | |||||
| <a class="cor" href="mailto:{{.Email}}" rel="nofollow"> {{.Email}}</a> | |||||
| </div> | |||||
| </div> | |||||
| <div class="button_leaveOrg" style="display:none" > | |||||
| {{if eq $.SignedUser.ID .ID}} | |||||
| <form method="post" action="{{$.OrgLink}}/members/action/leave"> | |||||
| {{$.CsrfTokenHtml}} | |||||
| <button type="submit" class="ui red basic button bt_mr" name="uid" value="{{.ID}}">{{$.i18n.Tr "org.course_members.leave"}}</button> | |||||
| </form> | |||||
| {{else if $.IsOrganizationOwner}} | |||||
| <form method="post" action="{{$.OrgLink}}/members/action/remove"> | |||||
| {{$.CsrfTokenHtml}} | |||||
| <button type="submit" class="ui red basic button bt_mr" name="uid" value="{{.ID}}">{{$.i18n.Tr "org.course_members.remove"}}</button> | |||||
| </form> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| {{template "base/paginate" .}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{template "base/footer" .}} | |||||
| <script> | |||||
| function show_bt(bt_id){ | |||||
| console.log(bt_id) | |||||
| document.getElementById(""+bt_id).getElementsByClassName("button_leaveOrg")[0].style.display="inline-block"; | |||||
| } | |||||
| function hide_bt(bt_id){ | |||||
| document.getElementById(""+bt_id).getElementsByClassName("button_leaveOrg")[0].style.display="none"; | |||||
| } | |||||
| </script> | |||||
| @@ -0,0 +1,28 @@ | |||||
| <style> | |||||
| .navber_course{ | |||||
| background: #F5F5F6 !important; | |||||
| padding-top: 30px; | |||||
| margin-bottom: 20px; | |||||
| } | |||||
| </style> | |||||
| <div class="navber_course"> | |||||
| <div class="ui tabs container"> | |||||
| <div class="ui tabular stackable menu navbar"> | |||||
| {{with .Org}} | |||||
| <a class="{{if $.PageIsOrgHome}}active{{end}} item " href="{{.HomeLink}}"> | |||||
| {{svg "octicon-home" 16}} {{$.i18n.Tr "org.home"}} | |||||
| </a> | |||||
| {{end}} | |||||
| <a class="{{if $.PageIsOrgMembers}}active{{end}} item" href="{{$.OrgLink}}/members"> | |||||
| {{svg "octicon-organization" 16}} {{$.i18n.Tr "org.people"}} | |||||
| </a> | |||||
| {{if or ($.IsOrganizationMember) ($.IsOrganizationOwner)}} | |||||
| <a class="{{if $.PageIsOrgTeams}}active{{end}} item" href="{{$.OrgLink}}/teams"> | |||||
| {{svg "octicon-jersey" 16}} {{$.i18n.Tr "org.teams"}} | |||||
| </a> | |||||
| {{end}} | |||||
| {{if .IsOrganizationOwner}}<a class="right text grey item" href="{{.OrgLink}}/settings">{{svg "octicon-gear" 16}} {{$.i18n.Tr "org.settings"}}</a>{{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| @@ -0,0 +1,56 @@ | |||||
| <style> | |||||
| .organization-header{ | |||||
| margin-bottom: 0px !important; | |||||
| border-bottom:none !important | |||||
| } | |||||
| </style> | |||||
| {{template "base/head" .}} | |||||
| <div class="organization teams"> | |||||
| {{template "org/header_course" .}} | |||||
| {{template "org/navber_course" .}} | |||||
| <div class="ui container"> | |||||
| {{template "base/alert" .}} | |||||
| <div class="ui stackable grid"> | |||||
| <div class="ui sixteen wide computer column list"> | |||||
| <div class="ui two column grid"> | |||||
| {{range .Teams}} | |||||
| <div class="column"> | |||||
| <div class="ui top attached header"> | |||||
| <a class="text black" href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.Name}}</strong></a> | |||||
| <div class="ui right"> | |||||
| {{if .IsMember $.SignedUser.ID}} | |||||
| <form method="post" action="{{$.OrgLink}}/teams/{{.LowerName}}/action/leave"> | |||||
| {{$.CsrfTokenHtml}} | |||||
| <button type="submit" class="ui red small button" name="uid" value="{{$.SignedUser.ID}}">{{$.i18n.Tr "org.teams.leave"}}</button> | |||||
| </form> | |||||
| {{else if $.IsOrganizationOwner}} | |||||
| <form method="post" action="{{$.OrgLink}}/teams/{{.LowerName}}/action/join"> | |||||
| {{$.CsrfTokenHtml}} | |||||
| <button type="submit" class="ui blue small button" name="uid" value="{{$.SignedUser.ID}}">{{$.i18n.Tr "org.teams.join"}}</button> | |||||
| </form> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| <div class="ui attached segment members"> | |||||
| {{range .Members}} | |||||
| <a href="{{.HomeLink}}" title="{{.Name}}"> | |||||
| <img class="ui avatar image" src="{{.RelAvatarLink}}"> | |||||
| </a> | |||||
| {{end}} | |||||
| </div> | |||||
| <div class="ui bottom attached header"> | |||||
| <p class="team-meta">{{.NumMembers}} {{$.i18n.Tr "org.lower_members"}} · {{.NumRepos}} {{$.i18n.Tr "org.lower_repositories"}}</p> | |||||
| </div> | |||||
| </div> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,295 @@ | |||||
| {{template "base/head" .}} | |||||
| <style> | |||||
| .repository.file.list #repo-desc { | |||||
| font-size: 1.0em; | |||||
| margin-bottom: 1.0rem; | |||||
| } | |||||
| #contributorInfo > a:nth-child(n+26){ | |||||
| display:none; | |||||
| } | |||||
| #contributorInfo > a{ | |||||
| width: 2.0em; | |||||
| float: left; | |||||
| margin: .25em; | |||||
| } | |||||
| .edit-link{ | |||||
| vertical-align: top; | |||||
| display: inline-block; | |||||
| overflow: hidden; | |||||
| word-break: keep-all; | |||||
| white-space: nowrap; | |||||
| text-overflow: ellipsis; | |||||
| width: 16.5em; | |||||
| } | |||||
| #contributorInfo > a.circular{ | |||||
| height: 2.0em; | |||||
| padding: 0; | |||||
| overflow: hidden; | |||||
| letter-spacing:1.0em; | |||||
| text-indent: 0.6em; | |||||
| line-height: 2.0em; | |||||
| text-transform:capitalize; | |||||
| color: #FFF; | |||||
| } | |||||
| #contributorInfo > a.circular:nth-child(9n+1){ | |||||
| background-color: #4ccdec; | |||||
| } | |||||
| #contributorInfo > a.circular:nth-child(9n+2){ | |||||
| background-color: #e0b265; | |||||
| } | |||||
| #contributorInfo > a.circular:nth-child(9n+3){ | |||||
| background-color: #d884b7; | |||||
| } | |||||
| #contributorInfo > a.circular:nth-child(9n+4){ | |||||
| background-color: #8c6bdc; | |||||
| } | |||||
| #contributorInfo > a.circular:nth-child(9n+5){ | |||||
| background-color: #3cb99f; | |||||
| } | |||||
| #contributorInfo > a.circular:nth-child(9n+6){ | |||||
| background-color: #6995b9; | |||||
| } | |||||
| #contributorInfo > a.circular:nth-child(9n+7){ | |||||
| background-color: #ab91a7; | |||||
| } | |||||
| #contributorInfo > a.circular:nth-child(9n+8){ | |||||
| background-color: #bfd0aa; | |||||
| } | |||||
| .vue_menu { | |||||
| cursor: auto; | |||||
| position: absolute; | |||||
| outline: none; | |||||
| margin: 0em; | |||||
| padding: 0em 0em; | |||||
| background: #fff; | |||||
| font-size: 1em; | |||||
| text-shadow: none; | |||||
| text-align: left; | |||||
| /* -webkit-box-shadow: 0px 2px 3px 0px rgb(34 36 38 / 15%); */ | |||||
| box-shadow: 0px 2px 3px 0px rgba(34, 36, 38, 0.15); | |||||
| border: 1px solid rgba(34,36,38,0.15); | |||||
| border-radius: 0.28571429rem; | |||||
| -webkit-transition: opacity 0.1s ease; | |||||
| transition: opacity 0.1s ease; | |||||
| z-index: 11; | |||||
| will-change: transform, opacity; | |||||
| -webkit-animation-iteration-count: 1; | |||||
| animation-iteration-count: 1; | |||||
| -webkit-animation-duration: 300ms; | |||||
| animation-duration: 300ms; | |||||
| -webkit-animation-timing-function: ease; | |||||
| animation-timing-function: ease; | |||||
| -webkit-animation-fill-mode: both; | |||||
| animation-fill-mode: both; | |||||
| } | |||||
| .repo-topic{ | |||||
| background-color: rgba(179, 219, 219, 0.4) !important; | |||||
| color: #0366D6 !important; | |||||
| font-weight: 200 !important; | |||||
| } | |||||
| </style> | |||||
| <div class="repository file list"> | |||||
| {{template "repo/header" .}} | |||||
| <div class="ui container"> | |||||
| {{template "base/alert" .}} | |||||
| <div class="hide" id="validate_prompt"> | |||||
| <span id="count_prompt">{{.i18n.Tr "repo.topic.count_prompt"}}</span> | |||||
| <span id="format_prompt">{{.i18n.Tr "repo.topic.format_prompt"}}</span> | |||||
| </div> | |||||
| {{if .Repository.IsArchived}} | |||||
| <div class="ui warning message"> | |||||
| {{.i18n.Tr "repo.archive.title"}} | |||||
| </div> | |||||
| {{end}} | |||||
| <div> | |||||
| <span>{{.i18n.Tr "repo.computing.Introduction"}}:</span> | |||||
| {{if .Repository.DescriptionHTML}} | |||||
| <span class="description" style="color: #8a8e99;">{{.Repository.DescriptionHTML}}</span> | |||||
| {{else}} | |||||
| <span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span> | |||||
| {{end}} | |||||
| <!-- <span style="color: #8a8e99;">生课程的教学,在全国范围形成一批开放共享的教学材料。这类材料的风格、设想既不同于教材,也不同于IEEE CSs中对知识体系的描述,而是以一定的知识内容为背景,重在教师个人在教学实践中的心得,包括对某些内容独到的理解和课堂上的处理,等等。</span> --> | |||||
| </div> | |||||
| <div class="ui" id="repo-topics"> | |||||
| <div id="repo-topics1" style="display: inline-block;margin: 0.5rem 0;"> | |||||
| {{range .Topics}} | |||||
| <a class="ui repo-topic small label topic" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=">{{.Name}}</a> | |||||
| {{end}} | |||||
| </div> | |||||
| <a style="margin-left: 0.5rem;" id="manage_topic"> | |||||
| {{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<i style="cursor: pointer;" class="plus square outline icon"></i>{{end}} | |||||
| {{.i18n.Tr "repo.issues.new.add_labels_title"}} | |||||
| </a> | |||||
| <div id="topic_edit" class="vue_menu" style="display:none;"> | |||||
| <div id="topic_edit1"> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="ui mobile reversed stackable grid" style="margin-top: -1.5rem;"> | |||||
| {{ $n := len .TreeNames}} | |||||
| {{ $l := Subtract $n 1}} | |||||
| <!-- If home page, show new PR. If not, show breadcrumb --> | |||||
| <div class="ui ten wide tablet twelve wide computer column text right" style="margin-top: 1rem;"> | |||||
| <div class="right fitted item" id="file-buttons"> | |||||
| <div class="ui tiny blue buttons"> | |||||
| {{if .Repository.CanEnableEditor}} | |||||
| {{if .CanAddFile}} | |||||
| <a href="{{.RepoLink}}/_new/{{EscapePound .BranchName}}/{{EscapePound .TreePath}}" class="ui button"> | |||||
| {{.i18n.Tr "repo.editor.new_file"}} | |||||
| </a> | |||||
| {{end}} | |||||
| {{if .CanUploadFile}} | |||||
| <a href="{{.RepoLink}}/_upload/{{EscapePound .BranchName}}/{{EscapePound .TreePath}}" class="ui button"> | |||||
| {{.i18n.Tr "repo.editor.upload_file"}} | |||||
| </a> | |||||
| {{end}} | |||||
| {{end}} | |||||
| {{if and (ne $n 0) (not .IsViewFile) (not .IsBlame) }} | |||||
| <a href="{{.RepoLink}}/commits/{{EscapePound .BranchNameSubURL}}/{{EscapePound .TreePath}}" class="ui button"> | |||||
| {{.i18n.Tr "repo.file_history"}} | |||||
| </a> | |||||
| {{end}} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="ui container"> | |||||
| <div class="ui mobile reversed stackable grid"> | |||||
| <div class="ui ten wide tablet twelve wide computer column"> | |||||
| {{if .IsViewFile}} | |||||
| {{template "repo/view_file" .}} | |||||
| {{else if .IsBlame}} | |||||
| {{template "repo/blame" .}} | |||||
| {{else}} | |||||
| <table id="repo-files-table" class="ui single line table"> | |||||
| <thead> | |||||
| <tr class="commit-list"> | |||||
| <th colspan="2"> | |||||
| {{if .LatestCommitUser}} | |||||
| <img class="ui avatar image img-12" src="{{.LatestCommitUser.RelAvatarLink}}" /> | |||||
| {{if .LatestCommitUser.FullName}} | |||||
| <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommitUser.FullName}}</strong></a> | |||||
| {{else}} | |||||
| <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a> | |||||
| {{end}} | |||||
| {{else}} | |||||
| {{if .LatestCommit.Author}} | |||||
| <img class="ui avatar image img-12" src="{{AvatarLink .LatestCommit.Author.Email}}" /> | |||||
| <strong>{{.LatestCommit.Author.Name}}</strong> | |||||
| {{end}} | |||||
| {{end}} | |||||
| {{if .LatestCommit.Author}} | |||||
| <span style="margin: 0 0.5rem;color: #767676">{{TimeSince .LatestCommit.Author.When $.Lang}}</span> | |||||
| {{end}} | |||||
| {{ $commitLink:= printf "%s/commit/%s" .RepoLink .LatestCommit.ID }} | |||||
| <span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{RenderCommitMessageLinkSubject .LatestCommit.Message $.RepoLink $commitLink $.Repository.ComposeMetas}}</span> | |||||
| {{if IsMultilineCommitMessage .LatestCommit.Message}} | |||||
| <button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button> | |||||
| <pre class="commit-body" style="display: none;">{{RenderCommitBody .LatestCommit.Message $.RepoLink $.Repository.ComposeMetas}}</pre> | |||||
| {{end}} | |||||
| </span> | |||||
| </th> | |||||
| </tr> | |||||
| </thead> | |||||
| <tbody> | |||||
| {{if .HasParentPath}} | |||||
| <tr class="has-parent"> | |||||
| <td colspan="3">{{svg "octicon-mail-reply" 16}}<a href="{{EscapePound .BranchLink}}{{.ParentPath}}">..</a></td> | |||||
| </tr> | |||||
| {{end}} | |||||
| {{range $item := .Files}} | |||||
| {{$entry := index $item 0}} | |||||
| {{$commit := index $item 1}} | |||||
| <tr> | |||||
| {{if $entry.IsSubModule}} | |||||
| <td> | |||||
| <span class="truncate"> | |||||
| {{svg "octicon-inbox" 16}} | |||||
| {{$refURL := $commit.RefURL AppUrl $.Repository.FullName}} | |||||
| {{if $refURL}} | |||||
| <a href="{{$refURL}}">{{$entry.Name}}</a> @ <a href="{{$refURL}}/commit/{{$commit.RefID}}">{{ShortSha $commit.RefID}}</a> | |||||
| {{else}} | |||||
| {{$entry.Name}} @ {{ShortSha $commit.RefID}} | |||||
| {{end}} | |||||
| </span> | |||||
| </td> | |||||
| {{else}} | |||||
| <td class="name thirteen wide"> | |||||
| <span class="truncate"> | |||||
| {{if $entry.IsDir}} | |||||
| {{$subJumpablePathName := $entry.GetSubJumpablePathName}} | |||||
| {{$subJumpablePath := SubJumpablePath $subJumpablePathName}} | |||||
| {{svg "octicon-file-directory" 16}} | |||||
| <a href="{{EscapePound $.TreeLink}}/{{EscapePound $subJumpablePathName}}" title="{{$subJumpablePathName}}"> | |||||
| {{if eq (len $subJumpablePath) 2}} | |||||
| <span class="jumpable-path">{{index $subJumpablePath 0}}</span>{{index $subJumpablePath 1}} | |||||
| {{else}} | |||||
| {{index $subJumpablePath 0}} | |||||
| {{end}} | |||||
| </a> | |||||
| {{else}} | |||||
| <i class="ri-file-pdf-line" style="font-size: 16px;margin-left: 3px;margin-right: 5px;vertical-align: text-top;color: #FA8C16;"></i> | |||||
| <a href="{{EscapePound $.TreeLink}}/{{EscapePound $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a> | |||||
| {{end}} | |||||
| </span> | |||||
| </td> | |||||
| {{end}} | |||||
| <td class="text right age one wide" style="text-align: right;">{{TimeSince $commit.Committer.When $.Lang}}</td> | |||||
| </tr> | |||||
| {{end}} | |||||
| </tbody> | |||||
| </table> | |||||
| {{if .ReadmeExist}} | |||||
| {{template "repo/view_file" .}} | |||||
| {{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| <!-- 贡献者框 --> | |||||
| <div class="ui six wide tablet four wide computer column"> | |||||
| <div style="border-radius: 5px;border: 1px solid rgba(225, 227, 230, 100);padding: 1rem;"> | |||||
| <h4 class="ui header" style="border-bottom: 1px solid rgba(225, 227, 230, 100);padding: 0.5rem 0;"> | |||||
| {{$lenCon := len .ContributorInfo}} | |||||
| {{if lt $lenCon 25 }} | |||||
| <strong>{{.i18n.Tr "home.contributors"}} ({{len .ContributorInfo}})</strong> | |||||
| {{else}} | |||||
| <strong>{{.i18n.Tr "home.contributors"}} ({{len .ContributorInfo}}+)</strong> | |||||
| {{end}} | |||||
| <div class="ui right"> | |||||
| <a class="membersmore text grey" href="{{.RepoLink}}/contributors?type={{if .IsViewBranch}}branch{{else}}tag{{end}}&name={{.BranchName}}">{{.i18n.Tr "repo.computing.all"}} {{svg "octicon-chevron-right" 16}}</a> | |||||
| </div> | |||||
| </h4> | |||||
| <div class="ui members" id="contributorInfo"> | |||||
| {{range .ContributorInfo}} | |||||
| {{if .UserInfo}} | |||||
| <a href="{{AppSubUrl}}/{{.UserInfo.Name}}"><img class="ui avatar image" src="{{.UserInfo.RelAvatarLink}}"></a> | |||||
| {{else if .Email}} | |||||
| <a href="mailto:{{.Email}}" class="circular ui button">{{.Email}}</a> | |||||
| {{end}} | |||||
| {{end}} | |||||
| </div> | |||||
| <div style="clear: both;"></div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,111 @@ | |||||
| <style> | |||||
| #course_label::after{ | |||||
| display: inline-block; | |||||
| vertical-align: top; | |||||
| margin: -.2em 0 0 .2em; | |||||
| content: '*'; | |||||
| color: #db2828; | |||||
| } | |||||
| </style> | |||||
| {{template "base/head" .}} | |||||
| <div class="repository new repo" style="margin-top: 40px;"> | |||||
| <div class="ui middle very relaxed page grid"> | |||||
| <div class="column"> | |||||
| <form class="ui form" action="{{.Link}}" method="post" id="create_repo_form"> | |||||
| {{.CsrfTokenHtml}} | |||||
| <h3 class="ui top attached header"> | |||||
| {{.i18n.Tr "new_course"}} | |||||
| </h3> | |||||
| <div class="ui attached segment"> | |||||
| {{template "base/alert" .}} | |||||
| <div class="inline required field {{if .Err_RepoName}}error{{end}}" > | |||||
| <label for="Alias">{{.i18n.Tr "form.courseAlias"}}</label> | |||||
| <input id="alias" name="alias" value="{{.alias}}" autofocus required> | |||||
| <span class="help">{{.i18n.Tr "form.reponame_dash_dot_error"}}</span> | |||||
| </div> | |||||
| <div class="inline required fields" style="margin-bottom: 0;"> | |||||
| <label id="course_label" style="text-align: right;width: 250px!important;word-wrap: break-word;">{{.i18n.Tr "form.courseAdress"}}</label> | |||||
| <div class="required field {{if .Err_Owner}}error{{end}}" style="padding: 0;"> | |||||
| <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.Name}} | |||||
| </div> | |||||
| </div> | |||||
| </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}}"> | |||||
| <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.course_Adress"}}:</label> | |||||
| <span style="flex: 1;"></span> | |||||
| </div> | |||||
| <div class="inline field" style="display: flex;align-items:center;"> | |||||
| <label style="margin: .035714em 1em 0 0;">{{.i18n.Tr "repo.model.manage.label"}}</label> | |||||
| <div class="ui multiple search selection dropdown" id="dropdown_container"> | |||||
| <input type="hidden" name="topics" value=""> | |||||
| <div class="default text" id="default_text">{{.i18n.Tr "repo.repo_label_helpe"}}</div> | |||||
| <div class="menu" id="course_label_item"> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="inline field {{if .Err_Description}}error{{end}}"> | |||||
| <label for="description">{{.i18n.Tr "course_desc"}}</label> | |||||
| <textarea id="description" name="description" maxlength="254">{{.description}}</textarea> | |||||
| </div> | |||||
| <div class="inline field"> | |||||
| <label></label> | |||||
| <button class="ui green button" id="submit_reponame"> | |||||
| {{.i18n.Tr "new_course"}} | |||||
| </button> | |||||
| <a class="ui button" href="javascript:history.go(-1)">{{.i18n.Tr "cancel"}}</a> | |||||
| </div> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| {{template "base/footer" .}} | |||||
| <script> | |||||
| $(document).ready(function(){ | |||||
| $('.ui.multiple.search.selection.dropdown') | |||||
| .dropdown({ | |||||
| allowAdditions: true, | |||||
| onChange: function(value, text, $selectedItem) { | |||||
| $('#course_label_item').empty() | |||||
| } | |||||
| }) | |||||
| let defaultText = document.getElementById("default_text").offsetHeight | |||||
| defaultText = defaultText>40 ? defaultText+12 :defaultText | |||||
| $("#dropdown_container").css("height",defaultText) | |||||
| $('input.search').bind('input propertychange', function (event) { | |||||
| $("#dropdown_container").removeAttr("style"); | |||||
| const query = $('input.search').val() | |||||
| if(!query){ | |||||
| $('#course_label_item').empty() | |||||
| }else{ | |||||
| $.get(`/api/v1/topics/search?q=${query}`,(data)=>{ | |||||
| console.log(data) | |||||
| if(data.topics.length!==0){ | |||||
| let html='' | |||||
| $('#course_label_item').empty() | |||||
| data.topics.forEach(element => { | |||||
| html += `<div class="item" data-value="${element.topic_name}">${element.topic_name}</div>` | |||||
| }); | |||||
| $('#course_label_item').append(html) | |||||
| } | |||||
| }) | |||||
| } | |||||
| }); | |||||
| }) | |||||
| console.log() | |||||
| </script> | |||||
| @@ -93,6 +93,7 @@ | |||||
| </span> | </span> | ||||
| </td> | </td> | ||||
| {{end}} | {{end}} | ||||
| <td class="message nine wide"> | <td class="message nine wide"> | ||||
| <span class="truncate"> | <span class="truncate"> | ||||
| <a href="{{$.RepoLink}}/commit/{{$commit.ID}}" title="{{$commit.Summary}}">{{$commit.Summary | RenderEmoji}}</a> | <a href="{{$.RepoLink}}/commit/{{$commit.ID}}" title="{{$commit.Summary}}">{{$commit.Summary | RenderEmoji}}</a> | ||||