#1425 computing

Merged
lewis merged 83 commits from computing into V20220125 2 years ago
  1. +65
    -23
      models/repo.go
  2. +21
    -0
      models/repo_list.go
  3. +3
    -3
      models/repo_tag.go
  4. +12
    -0
      modules/auth/repo_form.go
  5. +1
    -1
      modules/context/context.go
  6. +2
    -0
      modules/context/org.go
  7. +1
    -0
      modules/context/repo.go
  8. +10
    -1
      modules/repository/create.go
  9. +10
    -0
      modules/setting/setting.go
  10. +2
    -0
      modules/templates/helper.go
  11. +4
    -1
      modules/timeutil/since.go
  12. +33
    -2
      options/locale/locale_en-US.ini
  13. +33
    -0
      options/locale/locale_zh-CN.ini
  14. +8
    -58
      routers/home.go
  15. +43
    -6
      routers/org/home.go
  16. +7
    -3
      routers/org/members.go
  17. +9
    -2
      routers/org/teams.go
  18. +196
    -0
      routers/repo/course.go
  19. +7
    -1
      routers/repo/view.go
  20. +7
    -0
      routers/routes/routes.go
  21. +87
    -1
      services/repository/repository.go
  22. +14
    -10
      templates/base/head.tmpl
  23. +209
    -0
      templates/base/head_course.tmpl
  24. +4
    -2
      templates/base/head_fluid.tmpl
  25. +4
    -2
      templates/base/head_home.tmpl
  26. +4
    -2
      templates/base/head_pro.tmpl
  27. +2
    -2
      templates/explore/dataset_list.tmpl
  28. +137
    -0
      templates/org/course_list.tmpl
  29. +49
    -11
      templates/org/header.tmpl
  30. +32
    -0
      templates/org/header_course.tmpl
  31. +454
    -0
      templates/org/home_courses.tmpl
  32. +130
    -0
      templates/org/member/course_members.tmpl
  33. +28
    -0
      templates/org/navber_course.tmpl
  34. +56
    -0
      templates/org/team/courseTeams.tmpl
  35. +295
    -0
      templates/repo/courseHome.tmpl
  36. +111
    -0
      templates/repo/createCourse.tmpl
  37. +1
    -0
      templates/repo/view_list.tmpl

+ 65
- 23
models/repo.go View File

@@ -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")



+ 21
- 0
models/repo_list.go View File

@@ -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))
}


+ 3
- 3
models/repo_tag.go View File

@@ -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 {


+ 12
- 0
modules/auth/repo_form.go View File

@@ -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)
}

+ 1
- 1
modules/context/context.go View File

@@ -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) + `">`)


+ 2
- 0
modules/context/org.go View File

@@ -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)


+ 1
- 0
modules/context/repo.go View File

@@ -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)


+ 10
- 1
modules/repository/create.go View File

@@ -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
}



+ 10
- 0
modules/setting/setting.go View File

@@ -568,6 +568,11 @@ var (
}{}

Warn_Notify_Mails []string

Course = struct {
OrgName string
TeamName string
}{}
)

// DateLang transforms standard language locale name to corresponding value in datetime plugin.
@@ -1331,6 +1336,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() {


+ 2
- 0
modules/templates/helper.go View File

@@ -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)


+ 4
- 1
modules/timeutil/since.go View File

@@ -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
}

+ 33
- 2
options/locale/locale_en-US.ini View File

@@ -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
@@ -266,8 +269,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
@@ -347,8 +350,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
@@ -390,6 +396,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.
@@ -794,6 +801,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
@@ -802,6 +810,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
@@ -865,6 +875,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
@@ -978,8 +993,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
@@ -2024,6 +2043,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
@@ -2040,6 +2060,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.
@@ -2083,6 +2111,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
@@ -2125,6 +2155,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


+ 33
- 0
options/locale/locale_zh-CN.ini View File

@@ -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=版本数
@@ -991,8 +1005,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=迁移类型
@@ -2037,6 +2055,7 @@ org_full_name_holder=组织全名
org_name_helper=组织名字应该简单明了。
create_org=创建组织
repo_updated=最后更新于
repo_released=发布于
home=组织主页
people=组织成员
teams=组织团队
@@ -2053,6 +2072,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"。
@@ -2096,6 +2123,8 @@ members.remove=移除成员
members.leave=离开组织
members.invite_desc=邀请新的用户加入 %s:
members.invite_now=立即邀请
course_members.remove=移除
course_members.leave=离开

teams.join=加入团队
teams.leave=离开团队
@@ -2139,6 +2168,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=帐户管理


+ 8
- 58
routers/home.go View File

@@ -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)
}

+ 43
- 6
routers/org/home.go View File

@@ -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)
}

}

+ 7
- 3
routers/org/members.go View File

@@ -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


+ 9
- 2
routers/org/teams.go View File

@@ -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


+ 196
- 0
routers/repo/course.go View File

@@ -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)
}
}

+ 7
- 1
routers/repo/view.go View File

@@ -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


+ 7
- 0
routers/routes/routes.go View File

@@ -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)


+ 87
- 1
services/repository/repository.go View File

@@ -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
}

+ 14
- 10
templates/base/head.tmpl View File

@@ -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>

+ 209
- 0
templates/base/head_course.tmpl View File

@@ -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>
*/}}


+ 4
- 2
templates/base/head_fluid.tmpl View File

@@ -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>

+ 4
- 2
templates/base/head_home.tmpl View File

@@ -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>

+ 4
- 2
templates/base/head_pro.tmpl View File

@@ -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>

+ 2
- 2
templates/explore/dataset_list.tmpl View File

@@ -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>

+ 137
- 0
templates/org/course_list.tmpl View File

@@ -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"}}&nbsp;: &nbsp; {{TimeSinceUnixShort .CreatedUnix}}
<!-- </span> -->
</div>
</div>
</div>
{{end}}
</div>
</div>

+ 49
- 11
templates/org/header.tmpl View File

@@ -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>


+ 32
- 0
templates/org/header_course.tmpl View File

@@ -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>

+ 454
- 0
templates/org/home_courses.tmpl View File

@@ -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> &nbsp;{{.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> &nbsp;{{.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>

+ 130
- 0
templates/org/member/course_members.tmpl View File

@@ -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>

+ 28
- 0
templates/org/navber_course.tmpl View File

@@ -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}}&nbsp;{{$.i18n.Tr "org.home"}}
</a>
{{end}}
<a class="{{if $.PageIsOrgMembers}}active{{end}} item" href="{{$.OrgLink}}/members">
{{svg "octicon-organization" 16}}&nbsp;{{$.i18n.Tr "org.people"}}
</a>
{{if or ($.IsOrganizationMember) ($.IsOrganizationOwner)}}
<a class="{{if $.PageIsOrgTeams}}active{{end}} item" href="{{$.OrgLink}}/teams">
{{svg "octicon-jersey" 16}}&nbsp;{{$.i18n.Tr "org.teams"}}
</a>
{{end}}
{{if .IsOrganizationOwner}}<a class="right text grey item" href="{{.OrgLink}}/settings">{{svg "octicon-gear" 16}} &nbsp;{{$.i18n.Tr "org.settings"}}</a>{{end}}
</div>

</div>
</div>

+ 56
- 0
templates/org/team/courseTeams.tmpl View File

@@ -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" .}}

+ 295
- 0
templates/repo/courseHome.tmpl View File

@@ -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" .}}

+ 111
- 0
templates/repo/createCourse.tmpl View File

@@ -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>

+ 1
- 0
templates/repo/view_list.tmpl View File

@@ -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>


Loading…
Cancel
Save