Jelajahi Sumber

user/settings: complete repositories panel (#4312)

Unknwon 8 tahun lalu
induk
melakukan
66c1e6b0e8

+ 2 - 2
cmd/web.go

@@ -255,9 +255,9 @@ func runWeb(ctx *cli.Context) error {
 			m.Get("", user.SettingsOrganizations)
 			m.Post("/leave", user.SettingsLeaveOrganization)
 		})
-		m.Group("/repos", func() {
+		m.Group("/repositories", func() {
 			m.Get("", user.SettingsRepos)
-			m.Post("/delete", user.SettingsDeleteRepo)
+			m.Post("/leave", user.SettingsLeaveRepo)
 		})
 		m.Route("/delete", "GET,POST", user.SettingsDelete)
 	}, reqSignIn, func(ctx *context.Context) {

+ 7 - 1
conf/locale/locale_en-US.ini

@@ -258,6 +258,7 @@ ssh_keys = SSH Keys
 social = Social Accounts
 applications = Applications
 orgs = Organizations
+repos = Repositories
 delete = Delete Account
 uid = Uid
 
@@ -343,9 +344,14 @@ access_token_deletion_desc = Delete this personal access token will remove all r
 delete_token_success = Personal access token has been removed successfully! Don't forget to update your application as well.
 
 orgs.none = You are not a member of any organizations.
-orgs.leave_title = Leave an organization
+orgs.leave_title = Leave organization
 orgs.leave_desc = You will lose access to all repositories and teams after you left the organization. Do you want to continue?
 
+repos.leave = Leave
+repos.leave_title = Leave repository
+repos.leave_desc = You will lose access to the repository after you left. Do you want to continue?
+repos.leave_success = You have left repository '%s' successfully!
+
 delete_account = Delete Your Account
 delete_prompt = The operation will delete your account permanently, and <strong>CANNOT</strong> be undone!
 confirm_delete_account = Confirm Deletion

+ 1 - 1
gogs.go

@@ -16,7 +16,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.10.25.0322"
+const APP_VER = "0.10.26.0323"
 
 func init() {
 	setting.AppVer = APP_VER

+ 33 - 0
models/repo.go

@@ -228,6 +228,21 @@ func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
 	}
 }
 
+func (repo *Repository) loadAttributes(e Engine) (err error) {
+	if repo.Owner == nil {
+		repo.Owner, err = getUserByID(e, repo.OwnerID)
+		if err != nil {
+			return fmt.Errorf("getUserByID [%d]: %v", repo.OwnerID, err)
+		}
+	}
+
+	return nil
+}
+
+func (repo *Repository) LoadAttributes() error {
+	return repo.loadAttributes(x)
+}
+
 // MustOwner always returns a valid *User object to avoid
 // conceptually impossible error handling.
 // It creates a fake object that contains error deftail
@@ -1559,6 +1574,24 @@ func GetRecentUpdatedRepositories(page, pageSize int) (repos []*Repository, err
 		Where("is_private=?", false).Limit(pageSize).Desc("updated_unix").Find(&repos)
 }
 
+// GetUserAndCollaborativeRepositories returns list of repositories the user owns and collaborates.
+func GetUserAndCollaborativeRepositories(userID int64) ([]*Repository, error) {
+	repos := make([]*Repository, 0, 10)
+	if err := x.Alias("repo").
+		Join("INNER", "collaboration", "collaboration.repo_id = repo.id").
+		Where("collaboration.user_id = ?", userID).
+		Find(&repos); err != nil {
+		return nil, fmt.Errorf("select collaborative repositories: %v", err)
+	}
+
+	ownRepos := make([]*Repository, 0, 10)
+	if err := x.Where("owner_id = ?", userID).Find(&ownRepos); err != nil {
+		return nil, fmt.Errorf("select own repositories: %v", err)
+	}
+
+	return append(repos, ownRepos...), nil
+}
+
 func getRepositoryCount(e Engine, u *User) (int64, error) {
 	return x.Count(&Repository{OwnerID: u.ID})
 }

+ 25 - 7
models/repo_collaboration.go

@@ -7,6 +7,8 @@ package models
 import (
 	"fmt"
 
+	log "gopkg.in/clog.v1"
+
 	api "github.com/gogits/go-gogs-client"
 )
 
@@ -31,14 +33,22 @@ func (c *Collaboration) ModeI18nKey() string {
 	}
 }
 
-//IsCollaborator returns true if the user is a collaborator
-func (repo *Repository) IsCollaborator(uid int64) (bool, error) {
+// IsCollaborator returns true if the user is a collaborator of the repository.
+func IsCollaborator(repoID, userID int64) bool {
 	collaboration := &Collaboration{
-		RepoID: repo.ID,
-		UserID: uid,
+		RepoID: repoID,
+		UserID: userID,
 	}
+	has, err := x.Get(collaboration)
+	if err != nil {
+		log.Error(2, "get collaboration [repo_id: %d, user_id: %d]: %v", repoID, userID, err)
+		return false
+	}
+	return has
+}
 
-	return x.Get(collaboration)
+func (repo *Repository) IsCollaborator(userID int64) bool {
+	return IsCollaborator(repo.ID, userID)
 }
 
 // AddCollaborator adds new collaboration to a repository with default access mode.
@@ -186,10 +196,14 @@ func (repo *Repository) ChangeCollaborationAccessMode(userID int64, mode AccessM
 }
 
 // DeleteCollaboration removes collaboration relation between the user and repository.
-func (repo *Repository) DeleteCollaboration(uid int64) (err error) {
+func DeleteCollaboration(repo *Repository, userID int64) (err error) {
+	if !IsCollaborator(repo.ID, userID) {
+		return nil
+	}
+
 	collaboration := &Collaboration{
 		RepoID: repo.ID,
-		UserID: uid,
+		UserID: userID,
 	}
 
 	sess := x.NewSession()
@@ -206,3 +220,7 @@ func (repo *Repository) DeleteCollaboration(uid int64) (err error) {
 
 	return sess.Commit()
 }
+
+func (repo *Repository) DeleteCollaboration(userID int64) error {
+	return DeleteCollaboration(repo, userID)
+}

File diff ditekan karena terlalu besar
+ 0 - 0
modules/bindata/bindata.go


+ 2 - 2
public/config.codekit

@@ -1,6 +1,6 @@
 {
 "CodeKitInfo": "This is a CodeKit 2.x project configuration file. It is designed to sync project settings across multiple machines. MODIFYING THE CONTENTS OF THIS FILE IS A POOR LIFE DECISION. If you do so, you will likely cause CodeKit to crash. This file is not useful unless accompanied by the project that created it in CodeKit 2. This file is not backwards-compatible with CodeKit 1.x. For more information, see: http:\/\/incident57.com\/codekit",
-"creatorBuild": "19115",
+"creatorBuild": "19127",
 "files": {
 	"\/css\/github.min.css": {
 		"fileType": 16,
@@ -66,7 +66,7 @@
 		"fileType": 32768,
 		"ignore": 0,
 		"ignoreWasSetByUser": 0,
-		"initialSize": 4048,
+		"initialSize": 514087,
 		"inputAbbreviatedPath": "\/img\/avatar_default.png",
 		"outputAbbreviatedPath": "\/img\/avatar_default.png",
 		"outputPathIsOutsideProject": 0,

+ 13 - 3
public/css/gogs.css

@@ -2841,16 +2841,26 @@ footer .ui.language .menu {
 .user.settings .email.list .item:not(:first-child) .button {
   margin-top: -10px;
 }
-.user.settings .orgs.non-empty {
+.user.settings.organizations .orgs.non-empty {
   padding: 0;
 }
-.user.settings .orgs .item {
+.user.settings.organizations .orgs .item {
   padding: 10px;
 }
-.user.settings .orgs .item .button {
+.user.settings.organizations .orgs .item .button {
   margin-top: 5px;
   margin-right: 8px;
 }
+.user.settings.repositories .repos {
+  padding: 0;
+}
+.user.settings.repositories .repos .item {
+  padding: 15px;
+  height: 46px;
+}
+.user.settings.repositories .repos .item .button {
+  margin-top: -5px;
+}
 .user.profile .ui.card .username {
   display: block;
 }

+ 1 - 1
public/js/gogs.js

@@ -1308,7 +1308,7 @@ $(document).ready(function () {
                 $.post($this.data('url'), {
                     "_csrf": csrf,
                     "id": $this.data("id")
-                }).done(function (data) {
+                }).success(function (data) {
                     window.location.href = data.redirect;
                 });
             }

+ 11 - 1
public/less/_user.less

@@ -19,7 +19,7 @@
 				}
 			}
 		}
-		.orgs {
+		&.organizations .orgs {
 			&.non-empty {
 				padding: 0;
 			}
@@ -31,6 +31,16 @@
 				}
 			}
 		}
+		&.repositories .repos {
+			padding: 0;
+			.item {
+				padding: 15px;
+				height: 46px;
+				.button {
+					margin-top: -5px;
+				}
+			}
+		}
 	}
 
 	&.profile {

+ 1 - 7
routers/api/v1/repo/collaborators.go

@@ -67,13 +67,7 @@ func IsCollaborator(ctx *context.APIContext) {
 		return
 	}
 
-	is, err := ctx.Repo.Repository.IsCollaborator(collaborator.ID)
-	if err != nil {
-		ctx.Error(500, "IsCollaboration", err)
-		return
-	}
-
-	if !is {
+	if !ctx.Repo.Repository.IsCollaborator(collaborator.ID) {
 		ctx.Status(404)
 	} else {
 		ctx.Status(204)

+ 18 - 41
routers/user/setting.go

@@ -7,11 +7,9 @@ package user
 import (
 	"fmt"
 	"io/ioutil"
-	"net/url"
 	"strings"
 
 	"github.com/Unknwon/com"
-	"github.com/Unknwon/paginater"
 	log "gopkg.in/clog.v1"
 
 	"github.com/gogits/gogs/models"
@@ -32,7 +30,7 @@ const (
 	SETTINGS_SOCIAL        base.TplName = "user/settings/social"
 	SETTINGS_APPLICATIONS  base.TplName = "user/settings/applications"
 	SETTINGS_ORGANIZATIONS base.TplName = "user/settings/organizations"
-	SETTINGS_REPOS         base.TplName = "user/settings/repos"
+	SETTINGS_REPOSITORIES  base.TplName = "user/settings/repositories"
 	SETTINGS_DELETE        base.TplName = "user/settings/delete"
 	NOTIFICATION           base.TplName = "user/notification"
 	SECURITY               base.TplName = "user/security"
@@ -446,6 +444,9 @@ func SettingsLeaveOrganization(ctx *context.Context) {
 	err := models.RemoveOrgUser(ctx.QueryInt64("id"), ctx.User.ID)
 	if models.IsErrLastOrgOwner(err) {
 		ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
+	} else {
+		ctx.Handle(500, "RemoveOrgUser", err)
+		return
 	}
 
 	ctx.JSON(200, map[string]interface{}{
@@ -454,63 +455,39 @@ func SettingsLeaveOrganization(ctx *context.Context) {
 }
 
 func SettingsRepos(ctx *context.Context) {
-
-	ctx.Data["Title"] = ctx.Tr("admin.repositories")
+	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsSettingsRepositories"] = true
 
-	keyword := ctx.Query("q")
-	page := ctx.QueryInt("page")
-	if page <= 0 {
-		page = 1
-	}
-
-	repos, count, err := models.SearchRepositoryByName(&models.SearchRepoOptions{
-		Keyword:  keyword,
-		UserID:   ctx.User.ID,
-		OrderBy:  "lower_name",
-		Page:     page,
-		PageSize: setting.UI.Admin.RepoPagingNum,
-	})
+	repos, err := models.GetUserAndCollaborativeRepositories(ctx.User.ID)
 	if err != nil {
-		ctx.Handle(500, "SearchRepositoryByName", err)
+		ctx.Handle(500, "GetUserAndCollaborativeRepositories", err)
 		return
 	}
 	if err = models.RepositoryList(repos).LoadAttributes(); err != nil {
 		ctx.Handle(500, "LoadAttributes", err)
 		return
 	}
-
-	ctx.Data["Keyword"] = keyword
-	ctx.Data["Total"] = count
-	ctx.Data["Page"] = paginater.New(int(count), setting.UI.Admin.RepoPagingNum, page, 5)
 	ctx.Data["Repos"] = repos
-	ctx.HTML(200, SETTINGS_REPOS)
+
+	ctx.HTML(200, SETTINGS_REPOSITORIES)
 }
 
-func SettingsDeleteRepo(ctx *context.Context) {
+func SettingsLeaveRepo(ctx *context.Context) {
 	repo, err := models.GetRepositoryByID(ctx.QueryInt64("id"))
 	if err != nil {
-		ctx.Handle(500, "GetRepositoryByID", err)
+		ctx.NotFoundOrServerError("GetRepositoryByID", errors.IsRepoNotExist, err)
 		return
 	}
-	// make sure the user owns the repository or is an admin before allowing them to delete it
-	if repo.OwnerID == ctx.User.ID || ctx.User.IsAdmin {
-		if err := models.DeleteRepository(repo.MustOwner().ID, repo.ID); err != nil {
-			ctx.Handle(500, "DeleteRepository", err)
-			return
-		}
-		log.Trace("Repository deleted: %s/%s", repo.MustOwner().Name, repo.Name)
 
-		ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
-		ctx.JSON(200, map[string]interface{}{
-			"redirect": setting.AppSubUrl + "/user/settings/repos?page=" + ctx.Query("page") + "&q=" + url.QueryEscape(ctx.Query("q")),
-		})
-	} else {
-		// logged in user doesn't have rights to delete this repository
-		err := errors.New("You do not have rights to delete repository '" + repo.FullName() + "'")
-		ctx.Handle(403, "SettingsDeleteRepo", err)
+	if err = repo.DeleteCollaboration(ctx.User.ID); err != nil {
+		ctx.Handle(500, "DeleteCollaboration", err)
+		return
 	}
 
+	ctx.Flash.Success(ctx.Tr("settings.repos.leave_success", repo.FullName()))
+	ctx.JSON(200, map[string]interface{}{
+		"redirect": setting.AppSubUrl + "/user/settings/repositories",
+	})
 }
 
 func SettingsDelete(ctx *context.Context) {

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.10.25.0322
+0.10.26.0323

+ 2 - 2
templates/user/settings/navbar.tmpl

@@ -22,8 +22,8 @@
 		<a class="{{if .PageIsSettingsOrganizations}}active{{end}} item" href="{{AppSubUrl}}/user/settings/organizations">
 			{{.i18n.Tr "settings.orgs"}}
 		</a>
-		<a class="{{if .PageIsSettingsRepositories}}active{{end}} item" href="{{AppSubUrl}}/user/settings/repos">
-			{{.i18n.Tr "admin.repositories"}} 
+		<a class="{{if .PageIsSettingsRepositories}}active{{end}} item" href="{{AppSubUrl}}/user/settings/repositories">
+			{{.i18n.Tr "settings.repos"}}
 		</a>
 		<a class="{{if .PageIsSettingsDelete}}active{{end}} item" href="{{AppSubUrl}}/user/settings/delete">
 			{{.i18n.Tr "settings.delete"}}

+ 0 - 49
templates/user/settings/repos.tmpl

@@ -1,49 +0,0 @@
-{{template "base/head" .}}
-<div class="user">
-	<div class="ui container">
-		<div class="ui grid">
-			{{template "user/settings/navbar" .}}
-			<div class="twelve wide column content">
-				{{template "base/alert" .}}
-				<h4 class="ui top attached header">
-					{{.i18n.Tr "admin.repositories"}} ({{.i18n.Tr "admin.total" .Total}})
-				</h4>
-				<div class="ui attached segment">
-					{{template "admin/base/search" .}}
-				</div>
-
-				{{range .Repos}}
-				<div class="ui attached segment repos">
-				<div class="ui list">
-						<div class="item">
-							<a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">
-								<span class="octicon octicon-repo text light grey"></span> 
-								{{.Owner.Name}}/{{.Name}}
-							</a> 
-							<span class="ui text light grey">{{.Size | FileSize}}</span>
-							{{if .IsPrivate}}
-							<div class="right floated content">
-								<a class="ui red tiny button inline text-thin delete-button" href="" data-url="{{$.Link}}/leave?page={{$.Page.Current}}" data-id="{{.ID}}">{{$.i18n.Tr "settings.leave"}}</a>
-							</div>
-							{{end}}
-						</div>
-					</div>
-					</div>
-				{{end}}
-
-				{{template "admin/base/page" .}}
-			</div>
-		</div>
-	</div>
-</div>
-
-<div class="ui small basic leave modal">
-	<div class="ui icon header">
-		{{.i18n.Tr "teams.leave"}}
-	</div>
-	<div class="content">
-		<p>{{.i18n.Tr "teams.leave_desc"}}</p>
-	</div>
-	{{template "base/delete_modal_actions" .}}
-</div>
-{{template "base/footer" .}}

+ 54 - 0
templates/user/settings/repositories.tmpl

@@ -0,0 +1,54 @@
+{{template "base/head" .}}
+<div class="user settings repositories">
+	<div class="ui container">
+		<div class="ui grid">
+			{{template "user/settings/navbar" .}}
+			<div class="twelve wide column content">
+				{{template "base/alert" .}}
+				<h4 class="ui top attached header">
+					{{.i18n.Tr "settings.repos"}}
+				</h4>
+
+				<div class="ui attached segment repos">
+					<div class="ui middle aligned divided list">
+						{{range .Repos}}
+							<div class="item">
+								<span class="text light grey">
+									{{if .IsPrivate}}
+										<span class="text gold"><i class="octicon octicon-lock"></i></span>
+									{{else if .IsFork}}
+										<i class="octicon octicon-repo-forked"></i>
+									{{else if .IsMirror}}
+										<i class="octicon octicon-repo-clone"></i>
+									{{else}}
+										<i class="octicon octicon-repo"></i>
+									{{end}}
+								</span>
+								<a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">
+									{{.Owner.Name}}/{{.Name}}
+								</a> 
+								<span class="ui text light grey">{{.Size | FileSize}}</span>
+								{{if not (eq .OwnerID $.SignedUserID)}}
+								<div class="right floated content">
+									<a class="ui red tiny button inline text-thin delete-button" href="" data-url="{{$.Link}}/leave" data-id="{{.ID}}">{{$.i18n.Tr "settings.repos.leave"}}</a>
+								</div>
+								{{end}}
+							</div>
+						{{end}}
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
+
+<div class="ui small basic delete modal">
+	<div class="ui icon header">
+		{{.i18n.Tr "settings.repos.leave_title"}}
+	</div>
+	<div class="content">
+		<p>{{.i18n.Tr "settings.repos.leave_desc"}}</p>
+	</div>
+	{{template "base/delete_modal_actions" .}}
+</div>
+{{template "base/footer" .}}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini