Collaborators have write access as default, and can be changed via repository collaboration settings page to change between read, write and admin.tags/v1.21.12.1
| @@ -190,6 +190,8 @@ func runWeb(ctx *cli.Context) { | |||
| bindIgnErr := binding.BindIgnErr | |||
| // FIXME: not all routes need go through same middlewares. | |||
| // Especially some AJAX requests, we can reduce middleware number to improve performance. | |||
| // Routers. | |||
| m.Get("/", ignSignIn, routers.Home) | |||
| m.Get("/explore", ignSignIn, routers.Explore) | |||
| @@ -400,7 +402,11 @@ func runWeb(ctx *cli.Context) { | |||
| m.Group("/settings", func() { | |||
| m.Combo("").Get(repo.Settings). | |||
| Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost) | |||
| m.Combo("/collaboration").Get(repo.Collaboration).Post(repo.CollaborationPost) | |||
| m.Group("/collaboration", func() { | |||
| m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost) | |||
| m.Post("/access_mode", repo.ChangeCollaborationAccessMode) | |||
| m.Post("/delete", repo.DeleteCollaboration) | |||
| }) | |||
| m.Group("/hooks", func() { | |||
| m.Get("", repo.Webhooks) | |||
| @@ -221,8 +221,6 @@ still_own_repo = Your account still has ownership over at least one repository, | |||
| still_has_org = Your account still has membership in at least one organization, you have to leave or delete your memberships first. | |||
| org_still_own_repo = This organization still has ownership of repositories, you must delete or transfer them first. | |||
| still_own_user = This authentication is still in use by at least one user, please remove them from the authentication and try again. | |||
| target_branch_not_exist = Target branch does not exist. | |||
| [user] | |||
| @@ -615,6 +613,9 @@ settings.transfer_succeed = Repository ownership has been transferred successful | |||
| settings.confirm_delete = Confirm Deletion | |||
| settings.add_collaborator = Add New Collaborator | |||
| settings.add_collaborator_success = New collaborator has been added. | |||
| settings.delete_collaborator = Delete | |||
| settings.collaborator_deletion = Collaborator Deletion | |||
| settings.collaborator_deletion_desc = This user will no longer have collaboration access to this repository after deletion. Do you want to continue? | |||
| settings.remove_collaborator_success = Collaborator has been removed. | |||
| settings.search_user_placeholder = Search user... | |||
| settings.org_not_allowed_to_be_collaborator = Organization is not allowed to be added as a collaborator. | |||
| @@ -949,6 +950,7 @@ auths.update = Update Authentication Setting | |||
| auths.delete = Delete This Authentication | |||
| auths.delete_auth_title = Authentication Deletion | |||
| auths.delete_auth_desc = This authentication is going to be deleted, do you want to continue? | |||
| auths.still_in_used = This authentication is still used by some users, please delete or convert these users to another login type first. | |||
| auths.deletion_success = Authentication has been deleted successfully! | |||
| config.server_config = Server Configuration | |||
| @@ -13,11 +13,11 @@ import ( | |||
| type AccessMode int | |||
| const ( | |||
| ACCESS_MODE_NONE AccessMode = iota | |||
| ACCESS_MODE_READ | |||
| ACCESS_MODE_WRITE | |||
| ACCESS_MODE_ADMIN | |||
| ACCESS_MODE_OWNER | |||
| ACCESS_MODE_NONE AccessMode = iota // 0 | |||
| ACCESS_MODE_READ // 1 | |||
| ACCESS_MODE_WRITE // 2 | |||
| ACCESS_MODE_ADMIN // 3 | |||
| ACCESS_MODE_OWNER // 4 | |||
| ) | |||
| // Access represents the highest access level of a user to the repository. The only access type | |||
| @@ -151,15 +151,14 @@ func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode | |||
| return nil | |||
| } | |||
| // FIXME: should be able to have read-only access. | |||
| // Give all collaborators write access. | |||
| // refreshCollaboratorAccesses retrieves repository collaborations with their access modes. | |||
| func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error { | |||
| collaborators, err := repo.getCollaborators(e) | |||
| collaborations, err := repo.getCollaborations(e) | |||
| if err != nil { | |||
| return fmt.Errorf("getCollaborators: %v", err) | |||
| return fmt.Errorf("getCollaborations: %v", err) | |||
| } | |||
| for _, c := range collaborators { | |||
| accessMap[c.Id] = ACCESS_MODE_WRITE | |||
| for _, c := range collaborations { | |||
| accessMap[c.UserID] = c.Mode | |||
| } | |||
| return nil | |||
| } | |||
| @@ -121,7 +121,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err | |||
| return nil, err | |||
| } | |||
| // Compose comment action, could be plain comment, close or reopen issue. | |||
| // Compose comment action, could be plain comment, close or reopen issue/pull request. | |||
| // This object will be used to notify watchers in the end of function. | |||
| act := &Action{ | |||
| ActUserID: opts.Doer.Id, | |||
| @@ -179,6 +179,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| case COMMENT_TYPE_CLOSE: | |||
| act.OpType = ACTION_CLOSE_ISSUE | |||
| if opts.Issue.IsPull { | |||
| @@ -330,7 +330,6 @@ func (repo *Repository) RepoRelLink() string { | |||
| return "/" + repo.MustOwner().Name + "/" + repo.Name | |||
| } | |||
| func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { | |||
| return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) | |||
| } | |||
| @@ -1797,105 +1796,6 @@ func CheckRepoStats() { | |||
| // ***** END: Repository.NumForks ***** | |||
| } | |||
| // _________ .__ .__ ___. __ .__ | |||
| // \_ ___ \ ____ | | | | _____ \_ |__ ________________ _/ |_|__| ____ ____ | |||
| // / \ \/ / _ \| | | | \__ \ | __ \ / _ \_ __ \__ \\ __\ |/ _ \ / \ | |||
| // \ \___( <_> ) |_| |__/ __ \| \_\ ( <_> ) | \// __ \| | | ( <_> ) | \ | |||
| // \______ /\____/|____/____(____ /___ /\____/|__| (____ /__| |__|\____/|___| / | |||
| // \/ \/ \/ \/ \/ | |||
| // A Collaboration is a relation between an individual and a repository | |||
| type Collaboration struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` | |||
| UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` | |||
| Created time.Time `xorm:"CREATED"` | |||
| } | |||
| // Add collaborator and accompanying access | |||
| func (repo *Repository) AddCollaborator(u *User) error { | |||
| collaboration := &Collaboration{ | |||
| RepoID: repo.ID, | |||
| UserID: u.Id, | |||
| } | |||
| has, err := x.Get(collaboration) | |||
| if err != nil { | |||
| return err | |||
| } else if has { | |||
| return nil | |||
| } | |||
| if err = repo.GetOwner(); err != nil { | |||
| return fmt.Errorf("GetOwner: %v", err) | |||
| } | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if _, err = sess.InsertOne(collaboration); err != nil { | |||
| return err | |||
| } | |||
| if repo.Owner.IsOrganization() { | |||
| err = repo.recalculateTeamAccesses(sess, 0) | |||
| } else { | |||
| err = repo.recalculateAccesses(sess) | |||
| } | |||
| if err != nil { | |||
| return fmt.Errorf("recalculateAccesses 'team=%v': %v", repo.Owner.IsOrganization(), err) | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| func (repo *Repository) getCollaborators(e Engine) ([]*User, error) { | |||
| collaborations := make([]*Collaboration, 0) | |||
| if err := e.Find(&collaborations, &Collaboration{RepoID: repo.ID}); err != nil { | |||
| return nil, err | |||
| } | |||
| users := make([]*User, len(collaborations)) | |||
| for i, c := range collaborations { | |||
| user, err := getUserByID(e, c.UserID) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| users[i] = user | |||
| } | |||
| return users, nil | |||
| } | |||
| // GetCollaborators returns the collaborators for a repository | |||
| func (repo *Repository) GetCollaborators() ([]*User, error) { | |||
| return repo.getCollaborators(x) | |||
| } | |||
| // Delete collaborator and accompanying access | |||
| func (repo *Repository) DeleteCollaborator(u *User) (err error) { | |||
| collaboration := &Collaboration{ | |||
| RepoID: repo.ID, | |||
| UserID: u.Id, | |||
| } | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if has, err := sess.Delete(collaboration); err != nil || has == 0 { | |||
| return err | |||
| } else if err = repo.recalculateAccesses(sess); err != nil { | |||
| return err | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| // __ __ __ .__ | |||
| // / \ / \_____ _/ |_ ____ | |__ | |||
| // \ \/\/ /\__ \\ __\/ ___\| | \ | |||
| @@ -0,0 +1,161 @@ | |||
| // Copyright 2016 The Gogs Authors. All rights reserved. | |||
| // Use of this source code is governed by a MIT-style | |||
| // license that can be found in the LICENSE file. | |||
| package models | |||
| import ( | |||
| "fmt" | |||
| "time" | |||
| ) | |||
| // Collaboration represent the relation between an individual and a repository. | |||
| type Collaboration struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` | |||
| UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` | |||
| Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"` | |||
| Created time.Time `xorm:"CREATED"` | |||
| } | |||
| func (c *Collaboration) ModeName() string { | |||
| switch c.Mode { | |||
| case ACCESS_MODE_READ: | |||
| return "Read" | |||
| case ACCESS_MODE_WRITE: | |||
| return "Write" | |||
| case ACCESS_MODE_ADMIN: | |||
| return "Admin" | |||
| } | |||
| return "Undefined" | |||
| } | |||
| // AddCollaborator adds new collaboration relation between an individual and a repository. | |||
| func (repo *Repository) AddCollaborator(u *User) error { | |||
| collaboration := &Collaboration{ | |||
| RepoID: repo.ID, | |||
| UserID: u.Id, | |||
| } | |||
| has, err := x.Get(collaboration) | |||
| if err != nil { | |||
| return err | |||
| } else if has { | |||
| return nil | |||
| } | |||
| collaboration.Mode = ACCESS_MODE_WRITE | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if _, err = sess.InsertOne(collaboration); err != nil { | |||
| return err | |||
| } | |||
| if repo.Owner.IsOrganization() { | |||
| err = repo.recalculateTeamAccesses(sess, 0) | |||
| } else { | |||
| err = repo.recalculateAccesses(sess) | |||
| } | |||
| if err != nil { | |||
| return fmt.Errorf("recalculateAccesses 'team=%v': %v", repo.Owner.IsOrganization(), err) | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| func (repo *Repository) getCollaborations(e Engine) ([]*Collaboration, error) { | |||
| collaborations := make([]*Collaboration, 0) | |||
| return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repo.ID}) | |||
| } | |||
| // Collaborator represents a user with collaboration details. | |||
| type Collaborator struct { | |||
| *User | |||
| Collaboration *Collaboration | |||
| } | |||
| func (repo *Repository) getCollaborators(e Engine) ([]*Collaborator, error) { | |||
| collaborations, err := repo.getCollaborations(e) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("getCollaborations: %v", err) | |||
| } | |||
| collaborators := make([]*Collaborator, len(collaborations)) | |||
| for i, c := range collaborations { | |||
| user, err := getUserByID(e, c.UserID) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| collaborators[i] = &Collaborator{ | |||
| User: user, | |||
| Collaboration: c, | |||
| } | |||
| } | |||
| return collaborators, nil | |||
| } | |||
| // GetCollaborators returns the collaborators for a repository | |||
| func (repo *Repository) GetCollaborators() ([]*Collaborator, error) { | |||
| return repo.getCollaborators(x) | |||
| } | |||
| // ChangeCollaborationAccessMode sets new access mode for the collaboration. | |||
| func (repo *Repository) ChangeCollaborationAccessMode(uid int64, mode AccessMode) error { | |||
| // Discard invalid input | |||
| if mode <= ACCESS_MODE_NONE || mode > ACCESS_MODE_OWNER { | |||
| return nil | |||
| } | |||
| collaboration := &Collaboration{ | |||
| RepoID: repo.ID, | |||
| UserID: uid, | |||
| } | |||
| has, err := x.Get(collaboration) | |||
| if err != nil { | |||
| return fmt.Errorf("get collaboration: %v", err) | |||
| } else if !has { | |||
| return nil | |||
| } | |||
| collaboration.Mode = mode | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if _, err = sess.Id(collaboration.ID).AllCols().Update(collaboration); err != nil { | |||
| return fmt.Errorf("update collaboration: %v", err) | |||
| } else if _, err = sess.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil { | |||
| return fmt.Errorf("update access table: %v", err) | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| // DeleteCollaboration removes collaboration relation between the user and repository. | |||
| func (repo *Repository) DeleteCollaboration(uid int64) (err error) { | |||
| collaboration := &Collaboration{ | |||
| RepoID: repo.ID, | |||
| UserID: uid, | |||
| } | |||
| sess := x.NewSession() | |||
| defer sessionRelease(sess) | |||
| if err = sess.Begin(); err != nil { | |||
| return err | |||
| } | |||
| if has, err := sess.Delete(collaboration); err != nil || has == 0 { | |||
| return err | |||
| } else if err = repo.recalculateAccesses(sess); err != nil { | |||
| return err | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| @@ -20,15 +20,6 @@ | |||
| "outputPathIsOutsideProject": 0, | |||
| "outputPathIsSetByUser": 0 | |||
| }, | |||
| "\/css\/gogs.min.css": { | |||
| "fileType": 16, | |||
| "ignore": 1, | |||
| "ignoreWasSetByUser": 0, | |||
| "inputAbbreviatedPath": "\/css\/gogs.min.css", | |||
| "outputAbbreviatedPath": "No Output Path", | |||
| "outputPathIsOutsideProject": 0, | |||
| "outputPathIsSetByUser": 0 | |||
| }, | |||
| "\/css\/semantic-2.1.8.min.css": { | |||
| "fileType": 16, | |||
| "ignore": 0, | |||
| @@ -5,7 +5,7 @@ | |||
| background-size: contain; | |||
| } | |||
| body { | |||
| font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif, '微软雅黑'; | |||
| font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important; | |||
| background-color: #fff; | |||
| overflow-y: scroll; | |||
| } | |||
| @@ -104,6 +104,9 @@ code.wrap { | |||
| .ui.container.fluid.padded { | |||
| padding: 0 10px 0 10px; | |||
| } | |||
| .ui.form .ui.button { | |||
| font-weight: normal; | |||
| } | |||
| .ui .text.red { | |||
| color: #d95c5c !important; | |||
| } | |||
| @@ -234,6 +237,10 @@ code.wrap { | |||
| .ui.status.buttons .octicon { | |||
| margin-right: 4px; | |||
| } | |||
| .ui.inline.delete-button { | |||
| padding: 8px 15px; | |||
| font-weight: normal; | |||
| } | |||
| .overflow.menu .items { | |||
| max-height: 300px; | |||
| overflow-y: auto; | |||
| @@ -1984,10 +1991,11 @@ footer .container .links > *:first-child { | |||
| .repository.settings.collaboration .collaborator.list { | |||
| padding: 0; | |||
| } | |||
| .repository.settings.collaboration .collaborator.list .item { | |||
| padding: 10px 20px; | |||
| .repository.settings.collaboration .collaborator.list > .item { | |||
| margin: 0; | |||
| line-height: 2em; | |||
| } | |||
| .repository.settings.collaboration .collaborator.list .item:not(:last-child) { | |||
| .repository.settings.collaboration .collaborator.list > .item:not(:last-child) { | |||
| border-bottom: 1px solid #DDD; | |||
| } | |||
| .repository.settings.collaboration #repo-collab-form #search-user-box .results { | |||
| @@ -458,6 +458,20 @@ function initRepository() { | |||
| } | |||
| } | |||
| function initRepositoryCollaboration(){ | |||
| console.log('initRepositoryCollaboration'); | |||
| // Change collaborator access mode | |||
| $('.access-mode.menu .item').click(function(){ | |||
| var $menu = $(this).parent(); | |||
| $.post($menu.data('url'), { | |||
| "_csrf": csrf, | |||
| "uid": $menu.data('uid'), | |||
| "mode": $(this).data('value') | |||
| }) | |||
| }); | |||
| } | |||
| function initWiki() { | |||
| if ($('.repository.wiki').length == 0) { | |||
| return; | |||
| @@ -964,7 +978,8 @@ $(document).ready(function () { | |||
| initAdmin(); | |||
| var routes = { | |||
| 'div.user.settings': initUserSettings | |||
| 'div.user.settings': initUserSettings, | |||
| 'div.repository.settings.collaboration': initRepositoryCollaboration | |||
| }; | |||
| var selector; | |||
| @@ -1,7 +1,7 @@ | |||
| @footer-margin: 40px; | |||
| body { | |||
| font-family: 'Helvetica Neue',Arial,Helvetica,sans-serif,'微软雅黑'; | |||
| font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important; | |||
| background-color: #fff; | |||
| overflow-y: scroll; | |||
| } | |||
| @@ -109,6 +109,12 @@ pre, code { | |||
| } | |||
| } | |||
| &.form { | |||
| .ui.button { | |||
| font-weight: normal; | |||
| } | |||
| } | |||
| .text { | |||
| &.red { | |||
| color: #d95c5c !important; | |||
| @@ -260,6 +266,11 @@ pre, code { | |||
| margin-right: 4px; | |||
| } | |||
| } | |||
| &.inline.delete-button { | |||
| padding: 8px 15px; | |||
| font-weight: normal; | |||
| } | |||
| } | |||
| .overflow.menu { | |||
| @@ -1026,8 +1026,9 @@ | |||
| .collaborator.list { | |||
| padding: 0; | |||
| .item { | |||
| padding: 10px 20px; | |||
| >.item { | |||
| margin: 0; | |||
| line-height: 2em; | |||
| &:not(:last-child) { | |||
| border-bottom: 1px solid #DDD; | |||
| @@ -5,6 +5,8 @@ | |||
| package admin | |||
| import ( | |||
| "fmt" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-xorm/core" | |||
| @@ -218,11 +220,13 @@ func DeleteAuthSource(ctx *middleware.Context) { | |||
| if err = models.DeleteSource(source); err != nil { | |||
| switch err { | |||
| case models.ErrAuthenticationUserUsed: | |||
| ctx.Flash.Error("form.still_own_user") | |||
| ctx.Redirect(setting.AppSubUrl + "/admin/auths/" + ctx.Params(":authid")) | |||
| ctx.Flash.Error(ctx.Tr("admin.auths.still_in_used")) | |||
| default: | |||
| ctx.Handle(500, "DeleteSource", err) | |||
| ctx.Flash.Error(fmt.Sprintf("DeleteSource: %v", err)) | |||
| } | |||
| ctx.JSON(200, map[string]interface{}{ | |||
| "redirect": setting.AppSubUrl + "/admin/auths/" + ctx.Params(":authid"), | |||
| }) | |||
| return | |||
| } | |||
| log.Trace("Authentication deleted by admin(%s): %d", ctx.User.Name, source.ID) | |||
| @@ -257,30 +257,13 @@ func Collaboration(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.settings") | |||
| ctx.Data["PageIsSettingsCollaboration"] = true | |||
| // Delete collaborator. | |||
| remove := strings.ToLower(ctx.Query("remove")) | |||
| if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName { | |||
| u, err := models.GetUserByName(remove) | |||
| if err != nil { | |||
| ctx.Handle(500, "GetUserByName", err) | |||
| return | |||
| } | |||
| if err := ctx.Repo.Repository.DeleteCollaborator(u); err != nil { | |||
| ctx.Handle(500, "DeleteCollaborator", err) | |||
| return | |||
| } | |||
| ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") | |||
| return | |||
| } | |||
| users, err := ctx.Repo.Repository.GetCollaborators() | |||
| if err != nil { | |||
| ctx.Handle(500, "GetCollaborators", err) | |||
| return | |||
| } | |||
| ctx.Data["Collaborators"] = users | |||
| ctx.HTML(200, COLLABORATION) | |||
| } | |||
| @@ -332,6 +315,26 @@ func CollaborationPost(ctx *middleware.Context) { | |||
| ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path) | |||
| } | |||
| func ChangeCollaborationAccessMode(ctx *middleware.Context) { | |||
| if err := ctx.Repo.Repository.ChangeCollaborationAccessMode( | |||
| ctx.QueryInt64("uid"), | |||
| models.AccessMode(ctx.QueryInt("mode"))); err != nil { | |||
| log.Error(4, "ChangeCollaborationAccessMode: %v", err) | |||
| } | |||
| } | |||
| func DeleteCollaboration(ctx *middleware.Context) { | |||
| if err := ctx.Repo.Repository.DeleteCollaboration(ctx.QueryInt64("id")); err != nil { | |||
| ctx.Flash.Error("DeleteCollaboration: " + err.Error()) | |||
| } else { | |||
| ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) | |||
| } | |||
| ctx.JSON(200, map[string]interface{}{ | |||
| "redirect": ctx.Repo.RepoLink + "/settings/collaboration", | |||
| }) | |||
| } | |||
| func parseOwnerAndRepo(ctx *middleware.Context) (*models.User, *models.Repository) { | |||
| owner, err := models.GetUserByName(ctx.Params(":username")) | |||
| if err != nil { | |||
| @@ -11,14 +11,30 @@ | |||
| </h4> | |||
| <div class="ui attached segment collaborator list"> | |||
| {{range .Collaborators}} | |||
| <div class="item"> | |||
| {{if not (eq .Id $.Owner.Id)}} | |||
| <a href="{{$.RepoLink}}/settings/collaboration?remove={{.Name}}" class="ui right text red"><i class="fa fa-times"></i></a> | |||
| {{end}} | |||
| <a href="{{AppSubUrl}}/{{.Name}}"> | |||
| <img class="ui avatar image" src="{{.AvatarLink}}"> | |||
| {{.DisplayName}} | |||
| </a> | |||
| <div class="item ui grid"> | |||
| <div class="ui five wide column"> | |||
| <a href="{{AppSubUrl}}/{{.Name}}"> | |||
| <img class="ui avatar image" src="{{.AvatarLink}}"> | |||
| {{.DisplayName}} | |||
| </a> | |||
| </div> | |||
| <div class="ui eight wide column"> | |||
| <span class="octicon octicon-shield"></span> | |||
| <div class="ui inline dropdown"> | |||
| <div class="text">{{.Collaboration.ModeName}}</div> | |||
| <i class="dropdown icon"></i> | |||
| <div class="access-mode menu" data-url="{{$.Link}}/access_mode" data-uid="{{.Id}}"> | |||
| <div class="item" data-text="Admin" data-value="3">Admin</div> | |||
| <div class="item" data-text="Write" data-value="2">Write</div> | |||
| <div class="item" data-text="Read" data-value="1">Read</div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="ui two wide column"> | |||
| <button class="ui red tiny button inline text-thin delete-button" data-url="{{$.Link}}/delete" data-id="{{.Id}}"> | |||
| {{$.i18n.Tr "repo.settings.delete_collaborator"}} | |||
| </button> | |||
| </div> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| @@ -40,4 +56,15 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="ui small basic delete modal"> | |||
| <div class="ui icon header"> | |||
| <i class="trash icon"></i> | |||
| {{.i18n.Tr "repo.settings.collaborator_deletion"}} | |||
| </div> | |||
| <div class="content"> | |||
| <p>{{.i18n.Tr "repo.settings.collaborator_deletion_desc"}}</p> | |||
| </div> | |||
| {{template "base/delete_modal_actions" .}} | |||
| </div> | |||
| {{template "base/footer" .}} | |||