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 | |||
| type RepositoryStatus int | |||
| type RepoBlockChainStatus int | |||
| type RepoType int | |||
| // all kinds of RepositoryStatus | |||
| const ( | |||
| @@ -153,6 +154,11 @@ const ( | |||
| RepoBlockChainFailed | |||
| ) | |||
| const ( | |||
| RepoNormal RepoType = iota | |||
| RepoCourse | |||
| ) | |||
| // Repository represents a git repository. | |||
| type Repository struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| @@ -166,7 +172,8 @@ type Repository struct { | |||
| OriginalServiceType api.GitServiceType `xorm:"index"` | |||
| OriginalURL string `xorm:"VARCHAR(2048)"` | |||
| DefaultBranch string | |||
| CreatorID int64 `xorm:"INDEX NOT NULL DEFAULT 0"` | |||
| Creator *User `xorm:"-"` | |||
| NumWatches int | |||
| NumStars int | |||
| NumForks int | |||
| @@ -175,11 +182,12 @@ type Repository struct { | |||
| NumOpenIssues int `xorm:"-"` | |||
| NumPulls 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"` | |||
| IsEmpty bool `xorm:"INDEX"` | |||
| @@ -566,6 +574,19 @@ func (repo *Repository) GetOwner() error { | |||
| 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 { | |||
| if err := repo.getOwner(e); err != nil { | |||
| return &User{ | |||
| @@ -1064,6 +1085,8 @@ type CreateRepoOptions struct { | |||
| IsMirror bool | |||
| AutoInit bool | |||
| Status RepositoryStatus | |||
| IsCourse bool | |||
| Topics []string | |||
| } | |||
| // GetRepoInitFile returns repository init files | |||
| @@ -1109,7 +1132,7 @@ func IsUsableRepoAlias(name string) error { | |||
| } | |||
| // 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) | |||
| if err = IsUsableRepoName(repo.Name); err != nil { | |||
| return err | |||
| @@ -1124,7 +1147,10 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||
| } else if has { | |||
| return ErrRepoAlreadyExist{u.Name, repo.Name} | |||
| } | |||
| isCourse := isCourse(opts) | |||
| if isCourse { | |||
| repo.CreatorID = doer.ID | |||
| } | |||
| if _, err = ctx.e.Insert(repo); err != nil { | |||
| 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}, | |||
| }) | |||
| } 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 { | |||
| 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 { | |||
| units = append(units, RepoUnit{ | |||
| RepoID: repo.ID, | |||
| @@ -1176,11 +1208,13 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||
| Config: &BlockChainConfig{EnableBlockChain: true}, | |||
| }) | |||
| } 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 { | |||
| units = append(units, RepoUnit{ | |||
| RepoID: repo.ID, | |||
| @@ -1250,6 +1284,14 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||
| 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 { | |||
| sess := x.Where("id > 0") | |||
| @@ -48,9 +48,12 @@ func (repos RepositoryList) loadAttributes(e Engine) error { | |||
| set := make(map[int64]struct{}) | |||
| repoIDs := make([]int64, len(repos)) | |||
| setCreator := make(map[int64]struct{}) | |||
| for i := range repos { | |||
| set[repos[i].OwnerID] = struct{}{} | |||
| repoIDs[i] = repos[i].ID | |||
| setCreator[repos[i].CreatorID] = struct{}{} | |||
| } | |||
| // Load owners. | |||
| @@ -61,8 +64,18 @@ func (repos RepositoryList) loadAttributes(e Engine) error { | |||
| Find(&users); err != nil { | |||
| 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 { | |||
| repos[i].Owner = users[repos[i].OwnerID] | |||
| repos[i].Creator = creators[repos[i].CreatorID] | |||
| } | |||
| // Load primary language. | |||
| @@ -174,6 +187,10 @@ type SearchRepoOptions struct { | |||
| // True -> include just has milestones | |||
| // False -> include just has no milestone | |||
| 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 | |||
| @@ -351,6 +368,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { | |||
| 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 { | |||
| cond = cond.And(accessibleRepositoryCondition(opts.Actor)) | |||
| } | |||
| @@ -42,7 +42,7 @@ type TagsDetail struct { | |||
| TagId int64 | |||
| TagName string | |||
| TagLimit int | |||
| RepoList []Repository | |||
| RepoList []*Repository | |||
| } | |||
| func GetTagByID(id int64) (*OfficialTag, error) { | |||
| @@ -146,8 +146,8 @@ func GetAllOfficialTagRepos(orgID int64, isOwner bool) ([]TagsDetail, error) { | |||
| 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" | |||
| 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 { | |||
| 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["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 | |||
| ctx.Data["Org"] = org | |||
| ctx.Data["IsCourse"] = ctx.Org.Organization.Name == setting.Course.OrgName | |||
| // Force redirection when username is actually a user. | |||
| if !org.IsOrganization() { | |||
| ctx.Redirect(setting.AppSubURL + "/" + org.Name) | |||
| @@ -402,6 +402,7 @@ func RepoAssignment() macaron.Handler { | |||
| } | |||
| ctx.Repo.Owner = owner | |||
| ctx.Data["Username"] = ctx.Repo.Owner.Name | |||
| ctx.Data["IsCourse"] = owner.Name == setting.Course.OrgName | |||
| // Get repository. | |||
| repo, err := models.GetRepositoryByName(owner.ID, repoName) | |||
| @@ -22,6 +22,10 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m | |||
| Limit: u.MaxRepoCreation, | |||
| } | |||
| } | |||
| var RepoType = models.RepoNormal | |||
| if opts.IsCourse { | |||
| RepoType = models.RepoCourse | |||
| } | |||
| repo := &models.Repository{ | |||
| OwnerID: u.ID, | |||
| @@ -38,10 +42,15 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m | |||
| CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, | |||
| Status: opts.Status, | |||
| IsEmpty: !opts.AutoInit, | |||
| RepoType: RepoType, | |||
| Topics: opts.Topics, | |||
| } | |||
| 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 | |||
| } | |||
| @@ -572,6 +572,11 @@ var ( | |||
| }{} | |||
| Warn_Notify_Mails []string | |||
| Course = struct { | |||
| OrgName string | |||
| TeamName string | |||
| }{} | |||
| ) | |||
| // DateLang transforms standard language locale name to corresponding value in datetime plugin. | |||
| @@ -1339,6 +1344,11 @@ func NewContext() { | |||
| sec = Cfg.Section("warn_mail") | |||
| 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() { | |||
| @@ -93,6 +93,7 @@ func NewFuncMap() []template.FuncMap { | |||
| "TimeSince": timeutil.TimeSince, | |||
| "TimeSinceUnix": timeutil.TimeSinceUnix, | |||
| "TimeSinceUnix1": timeutil.TimeSinceUnix1, | |||
| "TimeSinceUnixShort": timeutil.TimeSinceUnixShort, | |||
| "RawTimeSince": timeutil.RawTimeSince, | |||
| "FileSize": base.FileSize, | |||
| "PrettyNumber": base.PrettyNumber, | |||
| @@ -342,6 +343,7 @@ func NewTextFuncMap() []texttmpl.FuncMap { | |||
| "TimeSince": timeutil.TimeSince, | |||
| "TimeSinceUnix": timeutil.TimeSinceUnix, | |||
| "TimeSinceUnix1": timeutil.TimeSinceUnix1, | |||
| "TimeSinceUnixShort": timeutil.TimeSinceUnixShort, | |||
| "RawTimeSince": timeutil.RawTimeSince, | |||
| "DateFmtLong": func(t time.Time) string { | |||
| return t.Format(time.RFC1123Z) | |||
| @@ -165,5 +165,8 @@ func htmlTimeSinceUnix(then, now TimeStamp, lang string) template.HTML { | |||
| func TimeSinceUnix1(then TimeStamp) string { | |||
| format := time.Unix(int64(then), 0).Format("2006-01-02 15:04:05") | |||
| 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 | |||
| mirror = Mirror | |||
| new_repo = New Repository | |||
| new_course=Publish Course | |||
| course_desc = Course Description | |||
| new_migrate = New Migration | |||
| new_dataset = New Dataset | |||
| edit_dataset = Edit Dataset | |||
| @@ -219,6 +221,7 @@ show_only_public = Showing only public | |||
| issues.in_your_repos = In your repositories | |||
| contributors = Contributors | |||
| contributor = Contributor | |||
| page_title=Explore Better AI | |||
| 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_search_results = Search results for '%s' | |||
| code_last_indexed_at = Last indexed %s | |||
| save=save | |||
| cancel=cancel | |||
| save=Save | |||
| cancel=Cancel | |||
| [auth] | |||
| create_new_account = Register Account | |||
| @@ -349,8 +352,11 @@ modify = Update | |||
| [form] | |||
| UserName = Username | |||
| Alias = Repository name | |||
| courseAlias = Course Name | |||
| courseAdress = Course Path | |||
| RepoPath = Repository path | |||
| RepoAdress = Repository Adress | |||
| course_Adress = Course Address | |||
| Email = Email address | |||
| Password = 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. | |||
| 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. | |||
| 2fa_auth_required = Remote visit required two factors authentication. | |||
| org_name_been_taken = The organization name is already taken. | |||
| @@ -796,6 +803,7 @@ generate_from = Generate From | |||
| repo_desc = Description | |||
| repo_lang = Language | |||
| repo_gitignore_helper = Select .gitignore templates. | |||
| repo_label_helpe = Press Enter to complete | |||
| issue_labels = Issue Labels | |||
| issue_labels_helper = Select an issue label set. | |||
| license = License | |||
| @@ -804,6 +812,8 @@ readme = README | |||
| readme_helper = Select a README file template. | |||
| auto_init = Initialize Repository (Adds .gitignore, License and README) | |||
| create_repo = Create Repository | |||
| create_course = Publish Course | |||
| failed_to_create_course=Fail to publish course, please try again later. | |||
| default_branch = Default Branch | |||
| mirror_prune = Prune | |||
| 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. | |||
| repo_stat_inspect=ProjectAnalysis | |||
| all=All | |||
| computing.all = All | |||
| computing.Introduction=Introduction | |||
| computing.success=Join Success | |||
| modelarts.status=Status | |||
| modelarts.createtime=CreateTime | |||
| 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. | |||
| 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.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.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 | |||
| 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. | |||
| create_org = Create Organization | |||
| repo_updated = Updated | |||
| repo_released = Post | |||
| home = Home | |||
| people = People | |||
| teams = Teams | |||
| @@ -2050,6 +2070,14 @@ team_access_desc = Repository access | |||
| team_permission_desc = Permission | |||
| team_unit_desc = Allow Access to Repository Sections | |||
| 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_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. | |||
| @@ -2093,6 +2121,8 @@ members.remove = Remove | |||
| members.leave = Leave | |||
| members.invite_desc = Add a new member to %s: | |||
| members.invite_now = Invite Now | |||
| course_members.remove = Remove | |||
| course_members.leave = Leave | |||
| teams.join = Join | |||
| 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_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.join_teams=Join in | |||
| [admin] | |||
| dashboard = Dashboard | |||
| @@ -50,6 +50,8 @@ repository=项目 | |||
| organization=组织 | |||
| mirror=镜像 | |||
| new_repo=创建项目 | |||
| new_course=发布课程 | |||
| course_desc=课程描述 | |||
| new_dataset=创建数据集 | |||
| new_migrate=迁移外部项目 | |||
| edit_dataset = Edit Dataset | |||
| @@ -221,6 +223,7 @@ show_only_public=只显示公开的 | |||
| issues.in_your_repos=属于该用户项目的 | |||
| contributors=贡献者 | |||
| contributor=贡献者 | |||
| page_title=探索更好的AI | |||
| page_small_title=启智AI开发协作平台 | |||
| @@ -352,8 +355,11 @@ modify=更新 | |||
| UserName=用户名 | |||
| RepoName=项目路径 | |||
| Alias=项目名称 | |||
| courseAlias=课程名称 | |||
| courseAdress=课程路径 | |||
| RepoPath=项目路径 | |||
| RepoAdress=项目地址 | |||
| course_Adress = 课程地址 | |||
| Email=邮箱地址 | |||
| Password=密码 | |||
| Retype=重新输入密码 | |||
| @@ -395,6 +401,7 @@ lang_select_error=从列表中选出语言 | |||
| username_been_taken=用户名已被使用。 | |||
| repo_name_been_taken=项目名称或项目路径已被使用。 | |||
| course_name_been_taken=课程名称或地址已被使用。 | |||
| visit_rate_limit=远程访问达到速度限制。 | |||
| 2fa_auth_required=远程访问需要双重验证。 | |||
| org_name_been_taken=组织名称已被使用。 | |||
| @@ -800,6 +807,7 @@ generate_from=生成自 | |||
| repo_desc=项目描述 | |||
| repo_lang=项目语言 | |||
| repo_gitignore_helper=选择 .gitignore 模板。 | |||
| repo_label_helpe=输入完成后回车键完成标签确定。 | |||
| issue_labels=任务标签 | |||
| issue_labels_helper=选择一个任务标签集 | |||
| license=授权许可 | |||
| @@ -808,6 +816,8 @@ readme=自述 | |||
| readme_helper=选择自述文件模板。 | |||
| auto_init=初始化存储库 (添加. gitignore、许可证和自述文件) | |||
| create_repo=创建项目 | |||
| create_course=发布课程 | |||
| failed_to_create_course=发布课程失败,请稍后再试。 | |||
| default_branch=默认分支 | |||
| mirror_prune=修剪 | |||
| mirror_prune_desc=删除过时的远程跟踪引用 | |||
| @@ -873,6 +883,10 @@ generate_statistic_file_error=生成文件失败。 | |||
| repo_stat_inspect=项目分析 | |||
| all=所有 | |||
| computing.all=全部 | |||
| computing.Introduction=简介 | |||
| computing.success=加入成功 | |||
| modelarts.status=状态 | |||
| modelarts.createtime=创建时间 | |||
| modelarts.version_nums=版本数 | |||
| @@ -999,8 +1013,12 @@ archive.issue.nocomment=此项目已存档,您不能在此任务添加评论 | |||
| archive.pull.nocomment=此项目已存档,您不能在此合并请求添加评论。 | |||
| form.reach_limit_of_creation=你已经达到了您的 %d 项目的限制。 | |||
| form.reach_limit_of_course_creation=你已经达到了您的 %d 课程的限制。 | |||
| form.name_reserved=项目名称 '%s' 是被保留的。 | |||
| form.course_name_reserved=课程名称 '%s' 是被保留的。 | |||
| form.name_pattern_not_allowed=项目名称中不允许使用模式 "%s"。 | |||
| form.course_name_pattern_not_allowed=课程名称中不允许使用模式 "%s"。 | |||
| add_course_org_fail=加入组织失败,请稍后重试。 | |||
| need_auth=需要授权验证 | |||
| migrate_type=迁移类型 | |||
| @@ -2045,6 +2063,7 @@ org_full_name_holder=组织全名 | |||
| org_name_helper=组织名字应该简单明了。 | |||
| create_org=创建组织 | |||
| repo_updated=最后更新于 | |||
| repo_released=发布于 | |||
| home=组织主页 | |||
| people=组织成员 | |||
| teams=组织团队 | |||
| @@ -2061,6 +2080,14 @@ team_access_desc=项目权限 | |||
| team_permission_desc=权限 | |||
| team_unit_desc=允许访问项目单元 | |||
| 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_pattern_not_allowed=组织名称中不允许使用 "%s"。 | |||
| @@ -2104,6 +2131,8 @@ members.remove=移除成员 | |||
| members.leave=离开组织 | |||
| members.invite_desc=邀请新的用户加入 %s: | |||
| members.invite_now=立即邀请 | |||
| course_members.remove=移除 | |||
| course_members.leave=离开 | |||
| teams.join=加入团队 | |||
| 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_admin_permission_desc=该团队拥有 <strong>管理</strong> <strong>所有项目</strong>的权限:团队成员可以读取、克隆、推送以及添加其它项目协作者。 | |||
| teams.join_teams=加入该组织 | |||
| [admin] | |||
| dashboard=管理面板 | |||
| users=帐户管理 | |||
| @@ -7,11 +7,11 @@ package routers | |||
| import ( | |||
| "bytes" | |||
| "fmt" | |||
| "io/ioutil" | |||
| "net/http" | |||
| "strings" | |||
| "code.gitea.io/gitea/services/repository" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/base" | |||
| "code.gitea.io/gitea/modules/context" | |||
| @@ -133,6 +133,7 @@ type RepoSearchOptions struct { | |||
| Restricted bool | |||
| PageSize int | |||
| TplName base.TplName | |||
| Course util.OptionalBool | |||
| } | |||
| var ( | |||
| @@ -211,6 +212,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { | |||
| AllLimited: true, | |||
| TopicName: topic, | |||
| IncludeDescription: setting.UI.SearchRepoDescription, | |||
| Course: opts.Course, | |||
| }) | |||
| if err != nil { | |||
| ctx.ServerError("SearchRepository", err) | |||
| @@ -559,7 +561,7 @@ func NotFound(ctx *context.Context) { | |||
| func RecommendOrgFromPromote(ctx *context.Context) { | |||
| url := setting.RecommentRepoAddr + "organizations" | |||
| result, err := recommendFromPromote(url) | |||
| result, err := repository.RecommendFromPromote(url) | |||
| if err != nil { | |||
| ctx.ServerError("500", err) | |||
| return | |||
| @@ -586,63 +588,11 @@ func RecommendOrgFromPromote(ctx *context.Context) { | |||
| 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) { | |||
| url := setting.RecommentRepoAddr + "projects" | |||
| result, err := recommendFromPromote(url) | |||
| result, err := repository.GetRecommendRepoFromPromote("projects") | |||
| if err != nil { | |||
| 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 ( | |||
| "strings" | |||
| "code.gitea.io/gitea/services/repository" | |||
| "code.gitea.io/gitea/modules/util" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/base" | |||
| "code.gitea.io/gitea/modules/context" | |||
| @@ -14,7 +18,8 @@ import ( | |||
| ) | |||
| const ( | |||
| tplOrgHome base.TplName = "org/home" | |||
| tplOrgHome base.TplName = "org/home" | |||
| tplOrgCourseHome base.TplName = "org/home_courses" | |||
| ) | |||
| // Home show organization home page | |||
| @@ -59,10 +64,16 @@ func Home(ctx *context.Context) { | |||
| case "fewestforks": | |||
| orderBy = models.SearchOrderByForks | |||
| 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"), " ") | |||
| ctx.Data["Keyword"] = keyword | |||
| @@ -76,9 +87,18 @@ func Home(ctx *context.Context) { | |||
| count int64 | |||
| 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{ | |||
| ListOptions: models.ListOptions{ | |||
| PageSize: setting.UI.User.RepoPagingNum, | |||
| PageSize: pageSize, | |||
| Page: page, | |||
| }, | |||
| Keyword: keyword, | |||
| @@ -87,6 +107,7 @@ func Home(ctx *context.Context) { | |||
| Private: ctx.IsSigned, | |||
| Actor: ctx.User, | |||
| IncludeDescription: setting.UI.SearchRepoDescription, | |||
| Course: CourseOptional, | |||
| }) | |||
| if err != nil { | |||
| ctx.ServerError("SearchRepository", err) | |||
| @@ -132,11 +153,27 @@ func Home(ctx *context.Context) { | |||
| //find org tag info | |||
| 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 { | |||
| ctx.ServerError("GetAllOfficialTagRepos", err) | |||
| return | |||
| } | |||
| 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 ( | |||
| // 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 | |||
| @@ -64,8 +65,11 @@ func Members(ctx *context.Context) { | |||
| ctx.Data["MembersIsPublicMember"] = membersIsPublic | |||
| ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID) | |||
| 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 | |||
| @@ -10,6 +10,8 @@ import ( | |||
| "path" | |||
| "strings" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/auth" | |||
| "code.gitea.io/gitea/modules/base" | |||
| @@ -22,7 +24,8 @@ import ( | |||
| const ( | |||
| // 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 base.TplName = "org/team/new" | |||
| // tplTeamMembers template path for showing team members page | |||
| @@ -44,8 +47,12 @@ func Teams(ctx *context.Context) { | |||
| } | |||
| } | |||
| 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 | |||
| @@ -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 ( | |||
| tplRepoEMPTY base.TplName = "repo/empty" | |||
| tplRepoHome base.TplName = "repo/home" | |||
| tplCourseHome base.TplName = "repo/courseHome" | |||
| tplWatchers base.TplName = "repo/watchers" | |||
| tplForks base.TplName = "repo/forks" | |||
| tplMigrating base.TplName = "repo/migrating" | |||
| @@ -855,7 +856,12 @@ func renderCode(ctx *context.Context) { | |||
| ctx.Data["TreeLink"] = treeLink | |||
| ctx.Data["TreeNames"] = treeNames | |||
| 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 | |||
| @@ -708,6 +708,13 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| }, reqSignIn) | |||
| // ***** 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 ***** | |||
| m.Group("/repo", func() { | |||
| m.Get("/create", repo.Create) | |||
| @@ -5,12 +5,17 @@ | |||
| package repository | |||
| import ( | |||
| "fmt" | |||
| "io/ioutil" | |||
| "net/http" | |||
| "strings" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/notification" | |||
| repo_module "code.gitea.io/gitea/modules/repository" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| pull_service "code.gitea.io/gitea/services/pull" | |||
| "fmt" | |||
| ) | |||
| // 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 | |||
| } | |||
| 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"> | |||
| {{template "base/head_navbar" .}} | |||
| </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}} | |||
| {{/* | |||
| </div> | |||
| @@ -247,5 +249,7 @@ var _hmt = _hmt || []; | |||
| 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"> | |||
| {{template "base/head_navbar_fluid" .}} | |||
| </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"> | |||
| <marquee behavior="scroll" direction="left"> | |||
| {{.notice.Title}} | |||
| @@ -248,5 +248,7 @@ var _hmt = _hmt || []; | |||
| document.getElementById("notic_content").style.display='none' | |||
| } | |||
| } | |||
| isShowNotice(); | |||
| if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||
| isShowNotice(); | |||
| } | |||
| </script> | |||
| @@ -205,7 +205,7 @@ var _hmt = _hmt || []; | |||
| <div class="ui top secondary stackable main menu following bar dark"> | |||
| {{template "base/head_navbar" .}} | |||
| </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"> | |||
| <marquee behavior="scroll" direction="left"> | |||
| {{.notice.Title}} | |||
| @@ -252,5 +252,7 @@ var _hmt = _hmt || []; | |||
| document.getElementById("notic_content").style.display='none' | |||
| } | |||
| } | |||
| isShowNotice(); | |||
| if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||
| isShowNotice(); | |||
| } | |||
| </script> | |||
| @@ -201,7 +201,7 @@ var _hmt = _hmt || []; | |||
| <div class="ui top secondary stackable main menu following bar dark"> | |||
| {{template "base/head_navbar_pro" .}} | |||
| </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"> | |||
| <marquee behavior="scroll" direction="left"> | |||
| {{.notice.Title}} | |||
| @@ -249,5 +249,7 @@ var _hmt = _hmt || []; | |||
| document.getElementById("notic_content").style.display='none' | |||
| } | |||
| } | |||
| isShowNotice(); | |||
| if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||
| isShowNotice(); | |||
| } | |||
| </script> | |||
| @@ -26,7 +26,7 @@ | |||
| <div class="item"> | |||
| <div class="ui header"> | |||
| <a class="name" href="{{.Repo.Link}}/datasets?type=0"> | |||
| {{.Repo.OwnerName}} / {{.Title}} | |||
| {{.Repo.OwnerName}} / {{.Repo.Alias}} | |||
| </a> | |||
| <div class="ui right metas"> | |||
| {{if .Task}} | |||
| @@ -53,4 +53,4 @@ | |||
| </div> | |||
| {{end}} | |||
| </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}}"> | |||
| <span class="text thin grey"><a href="{{.HomeLink}}">{{.DisplayName}}</a></span> | |||
| {{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> | |||
| <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> | |||
| </td> | |||
| {{end}} | |||
| <td class="message nine wide"> | |||
| <span class="truncate"> | |||
| <a href="{{$.RepoLink}}/commit/{{$commit.ID}}" title="{{$commit.Summary}}">{{$commit.Summary | RenderEmoji}}</a> | |||