Browse Source

解决冲突。

Signed-off-by: zouap <zouap@pcl.ac.cn>
tags/v1.22.5.1^2
zouap 3 years ago
parent
commit
67652d88bf
14 changed files with 1479 additions and 801 deletions
  1. +6
    -0
      modules/auth/repo_form.go
  2. +28
    -0
      modules/repofiles/temp_repo.go
  3. +207
    -0
      modules/repofiles/update.go
  4. +6
    -4
      options/locale/locale_en-US.ini
  5. +4
    -2
      options/locale/locale_zh-CN.ini
  6. +101
    -0
      routers/repo/editor.go
  7. +5
    -0
      routers/repo/view.go
  8. +32
    -0
      routers/response/response.go
  9. +1
    -0
      routers/routes/routes.go
  10. +2
    -1
      templates/repo/cloudbrain/models/dir_list.tmpl
  11. +244
    -199
      templates/repo/home.tmpl
  12. +172
    -81
      templates/repo/view_list.tmpl
  13. +153
    -0
      web_src/js/features/contexmenu.js
  14. +518
    -514
      web_src/js/index.js

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

@@ -740,3 +740,9 @@ type CreateCourseForm struct {
func (f *CreateCourseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}

// RenameRepoFileForm form for renaming repository file
type RenameRepoFileForm struct {
TreePath string `binding:"Required;MaxSize(500)"`
LastCommit string
}

+ 28
- 0
modules/repofiles/temp_repo.go View File

@@ -109,6 +109,34 @@ func (t *TemporaryUploadRepository) LsFiles(filenames ...string) ([]string, erro
return filelist, nil
}

// LsFilesStage list all files with stage format in index for the given paths
// if the given path is directory ,then return all files under it
// if the given path is file ,then return the file
func (t *TemporaryUploadRepository) LsFilesStage(paths ...string) ([]string, error) {
stdOut := new(bytes.Buffer)
stdErr := new(bytes.Buffer)

cmdArgs := []string{"ls-files", "-z", "-s", "--"}
for _, arg := range paths {
if arg != "" {
cmdArgs = append(cmdArgs, arg)
}
}

if err := git.NewCommand(cmdArgs...).RunInDirPipeline(t.basePath, stdOut, stdErr); err != nil {
log.Error("Unable to run git ls-files for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String())
err = fmt.Errorf("Unable to run git ls-files for temporary repo of: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
return nil, err
}

filelist := make([]string, 0)
for _, line := range bytes.Split(stdOut.Bytes(), []byte{'\000'}) {
filelist = append(filelist, string(line))
}

return filelist, nil
}

// RemoveFilesFromIndex removes the given files from the index
func (t *TemporaryUploadRepository) RemoveFilesFromIndex(filenames ...string) error {
stdOut := new(bytes.Buffer)


+ 207
- 0
modules/repofiles/update.go View File

@@ -756,3 +756,210 @@ func createCommitRepoActions(repo *models.Repository, gitRepo *git.Repository, o
}
return actions, nil
}

// RenameRepoFileOptions
type RenameRepoFileOptions struct {
LastCommitID string
BranchName string
TreePath string
FromTreePath string
Message string
Author *IdentityOptions
Committer *IdentityOptions
}

// RenameRepoFile rename file in the given repository
func RenameRepoFile(repo *models.Repository, doer *models.User, opts *RenameRepoFileOptions) error {

// Branch must exist for this operation
if _, err := repo_module.GetBranch(repo, opts.BranchName); err != nil {
return err
}

//make sure user can commit to the given branch
if err := checkBranchProtection(doer, repo, opts.BranchName, opts.TreePath); err != nil {
return err
}

// Check that the path given in opts.treePath is valid (not a git path)
treePath := CleanUploadFileName(opts.TreePath)
if treePath == "" {
return models.ErrFilenameInvalid{
Path: opts.TreePath,
}
}
// If there is a fromTreePath (we are copying it), also clean it up
fromTreePath := CleanUploadFileName(opts.FromTreePath)
if fromTreePath == "" && opts.FromTreePath != "" {
return models.ErrFilenameInvalid{
Path: opts.FromTreePath,
}
}

author, committer := GetAuthorAndCommitterUsers(opts.Author, opts.Committer, doer)

t, err := NewTemporaryUploadRepository(repo)
if err != nil {
log.Error("%v", err)
}
defer t.Close()
if err := t.Clone(opts.BranchName); err != nil {
return err
}
if err := t.SetDefaultIndex(); err != nil {
return err
}

// Get the commit of the original branch
commit, err := t.GetBranchCommit(opts.BranchName)
if err != nil {
return err // Couldn't get a commit for the branch
}

lastCommitID, err := t.gitRepo.ConvertToSHA1(opts.LastCommitID)
if err != nil {
return fmt.Errorf("DeleteRepoFile: Invalid last commit ID: %v", err)
}
opts.LastCommitID = lastCommitID.String()

if opts.LastCommitID == "" {
// When updating a file, a lastCommitID needs to be given to make sure other commits
// haven't been made. We throw an error if one wasn't provided.
return models.ErrSHAOrCommitIDNotProvided{}
}

//if fromTreePath not exist,return error
_, err = commit.GetTreeEntryByPath(fromTreePath)
if err != nil {
return err
}

// If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw
// an error.
if commit.ID.String() != opts.LastCommitID {
if changed, err := commit.FileChangedSinceCommit(fromTreePath, opts.LastCommitID); err != nil {
return err
} else if changed {
return models.ErrCommitIDDoesNotMatch{
GivenCommitID: opts.LastCommitID,
CurrentCommitID: opts.LastCommitID,
}
}
}

//if treePath has been exist,return error
_, err = commit.GetTreeEntryByPath(treePath)
if err == nil || !git.IsErrNotExist(err) {
// Means the file has been exist in new path
return models.ErrFilePathInvalid{
Message: fmt.Sprintf("a file exists where you’re trying to create a subdirectory [path: %s]", treePath),
Path: treePath,
Name: treePath,
Type: git.EntryModeBlob,
}
}

//move and add files to index
if err = moveAndAddFiles(fromTreePath, treePath, t); err != nil {
return err
}

// Now write the tree
treeHash, err := t.WriteTree()
if err != nil {
return err
}

// Now commit the tree
message := strings.TrimSpace(opts.Message)
commitHash, err := t.CommitTree(author, committer, treeHash, message)
if err != nil {
return err
}

// Then push this tree to NewBranch
if err := t.Push(doer, commitHash, opts.BranchName); err != nil {
log.Error("%T %v", err, err)
return err
}

return nil
}

func checkBranchProtection(doer *models.User, repo *models.Repository, branchName, treePath string) error {
//make sure user can commit to the given branch
protectedBranch, err := repo.GetBranchProtection(branchName)
if err != nil {
return err
}
if protectedBranch != nil {
if !protectedBranch.CanUserPush(doer.ID) {
return models.ErrUserCannotCommit{
UserName: doer.LowerName,
}
}
if protectedBranch.RequireSignedCommits {
_, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), branchName)
if err != nil {
if !models.IsErrWontSign(err) {
return err
}
return models.ErrUserCannotCommit{
UserName: doer.LowerName,
}
}
}
patterns := protectedBranch.GetProtectedFilePatterns()
for _, pat := range patterns {
if pat.Match(strings.ToLower(treePath)) {
return models.ErrFilePathProtected{
Path: treePath,
}
}
}
}
return nil
}

func moveAndAddFiles(oldTreePath, newTreePath string, t *TemporaryUploadRepository) error {
array, err := t.LsFilesStage(oldTreePath)
if err != nil {
return err
}
if len(array) == 0 {
return git.ErrNotExist{RelPath: oldTreePath}
}
stdOut := new(bytes.Buffer)
stdErr := new(bytes.Buffer)
stdIn := new(bytes.Buffer)
//write all files in stage format to the stdin,
//for each file,remove old tree path and add new tree path
//see the update-index help document at https://git-scm.com/docs/git-update-index
//especially see the content of "USING --INDEX-INFO"
for _, v := range array {
if v == "" {
continue
}
//example for v(mode SHA-1 stage file)
//100755 d294c88235ac05d3dece028d8a65590f28ec46ac 0 custom/conf/app.ini
v = strings.ReplaceAll(v, "0\t", "")
tmpArray := strings.Split(v, " ")
oldPath := tmpArray[2]
newPath := newTreePath + strings.TrimPrefix(oldPath, oldTreePath)
// mode 0 means remove file
stdIn.WriteString("0 0000000000000000000000000000000000000000\t")
stdIn.WriteString(oldPath)
stdIn.WriteByte('\000')
stdIn.WriteString(tmpArray[0] + " ")
stdIn.WriteString(tmpArray[1] + "\t")
stdIn.WriteString(newPath)
stdIn.WriteByte('\000')
}

if err := git.NewCommand("update-index", "--replace", "-z", "--index-info").RunInDirFullPipeline(t.basePath, stdOut, stdErr, stdIn); err != nil {
log.Error("Unable to update-index for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String())
return fmt.Errorf("Unable to update-index for temporary repo: %s Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String())
}

return nil
}

+ 6
- 4
options/locale/locale_en-US.ini View File

@@ -263,7 +263,7 @@ search_issue=Issue
search_pr=Pull Request
search_user=User
search_org=Organization
search_finded=Find
search_finded=Find
search_related=related
search_maybe=maybe
search_ge=
@@ -276,7 +276,7 @@ use_plt__fuction = To use the AI collaboration functions provided by this platfo
provide_resoure = Computing resources of CPU/GPU/NPU are provided freely for various types of AI tasks.
activity = Activity
no_events = There are no events related
or_t = or
or_t = or

[explore]
repos = Repositories
@@ -1325,6 +1325,8 @@ editor.require_signed_commit = Branch requires a signed commit
editor.repo_too_large = Repository can not exceed %d MB
editor.repo_file_invalid = Upload files are invalid
editor.upload_file_too_much = Can not upload more than %d files at a time
editor.rename = rename "%s" to %s"
editor.file_changed_while_renaming=The version of the file or folder to be renamed has changed. Please refresh the page and try again


commits.desc = Browse source code change history.
@@ -1389,7 +1391,7 @@ issues.add_milestone_at = `added this to the <b>%s</b> milestone %s`
issues.change_milestone_at = `modified the milestone from <b>%s</b> to <b>%s</b> %s`
issues.remove_milestone_at = `removed this from the <b>%s</b> milestone %s`

issues.add_branch_at=`added this to the <b>%s</b> branch %s`
issues.add_branch_at=`added this to the <b>%s</b> branch %s`
issues.add_tag_at =`added this to the <b>%s</b> tag %s`
issues.change_branch_tag_at= `modified the branch/tag from <b>%s</b> to <b>%s</b> %s`
issues.remove_branch_at=`removed this from the <b>%s</b> branch %s`
@@ -3041,4 +3043,4 @@ SNN4IMAGENET = SNN4IMAGENET
BRAINSCORE = BRAINSCORE
TRAIN = TRAIN
INFERENCE = INFERENCE
BENCHMARK = BENCHMARK
BENCHMARK = BENCHMARK

+ 4
- 2
options/locale/locale_zh-CN.ini View File

@@ -271,7 +271,7 @@ search_maybe=约为
search_ge=个

wecome_AI_plt=欢迎来到启智AI协作平台!
explore_AI = 探索更好的AI,来这里发现更有意思的
explore_AI = 探索更好的AI,来这里发现更有意思的
datasets = 数据集
repositories = 项目
use_plt__fuction = 使用本平台提供的AI协作功能,如:托管代码、共享数据、调试算法或训练模型,请先
@@ -279,7 +279,7 @@ provide_resoure = 平台目前免费提供CPU、GPU、NPU的算力资源,可
create_pro = 创建项目
activity = 活动
no_events = 还没有与您相关的活动
or_t = 或
or_t = 或


[explore]
@@ -1338,6 +1338,8 @@ editor.require_signed_commit=分支需要签名提交
editor.repo_too_large = 代码仓总大小不能超过%dMB
editor.repo_file_invalid = 提交的文件非法
editor.upload_file_too_much = 不能同时提交超过%d个文件
editor.rename = 重命名"%s"为"%s"
editor.file_changed_while_renaming=待重命名的文件或文件夹版本已发生变化,请您刷新页面后重试

commits.desc=浏览代码修改历史
commits.commits=次代码提交


+ 101
- 0
routers/repo/editor.go View File

@@ -5,10 +5,12 @@
package repo

import (
"code.gitea.io/gitea/routers/response"
repo_service "code.gitea.io/gitea/services/repository"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path"
"path/filepath"
"strings"
@@ -795,3 +797,102 @@ func GetClosestParentWithFiles(treePath string, commit *git.Commit) string {
}
return treePath
}

// RenameFilePost response for editing file
func RenameFilePost(ctx *context.Context, form auth.RenameRepoFileForm) {
renameFilePost(ctx, form)
}

func renameFilePost(ctx *context.Context, form auth.RenameRepoFileForm) {
if form.TreePath == "" || form.LastCommit == "" {
ctx.JSON(http.StatusOK, response.ServerError("param error"))
return
}
if form.TreePath == ctx.Repo.TreePath {
ctx.JSON(http.StatusOK, response.Success())
return
}

canCommit := renderCommitRights(ctx)
branchName := ctx.Repo.BranchName
if ctx.HasError() {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Flash.ErrorMsg))
return
}

// Cannot commit to a an existing branch if user doesn't have rights
if branchName == ctx.Repo.BranchName && !canCommit {
ctx.Data["Err_NewBranchName"] = true
ctx.Data["commit_choice"] = frmCommitChoiceNewBranch
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.cannot_commit_to_protected_branch", branchName)))
return
}

message := ctx.Tr("repo.editor.rename", ctx.Repo.TreePath, form.TreePath)

if err := repofiles.RenameRepoFile(ctx.Repo.Repository, ctx.User, &repofiles.RenameRepoFileOptions{
LastCommitID: form.LastCommit,
BranchName: branchName,
FromTreePath: ctx.Repo.TreePath,
TreePath: form.TreePath,
Message: message,
}); err != nil {
// This is where we handle all the errors thrown by repofiles.CreateOrUpdateRepoFile
if git.IsErrNotExist(err) {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.file_editing_no_longer_exists", ctx.Repo.TreePath)))
} else if models.IsErrLFSFileLocked(err) {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.file_editing_no_longer_exists", ctx.Tr("repo.editor.upload_file_is_locked", err.(models.ErrLFSFileLocked).Path, err.(models.ErrLFSFileLocked).UserName))))
} else if models.IsErrFilenameInvalid(err) {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath)))
} else if models.IsErrFilePathInvalid(err) {
if fileErr, ok := err.(models.ErrFilePathInvalid); ok {
switch fileErr.Type {
case git.EntryModeSymlink:
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path)))
case git.EntryModeTree:
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path)))
case git.EntryModeBlob:
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path)))
default:
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
}
} else {
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
}
} else if models.IsErrRepoFileAlreadyExists(err) {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.file_already_exists", form.TreePath)))
} else if git.IsErrBranchNotExist(err) {
// For when a user adds/updates a file to a branch that no longer exists
if branchErr, ok := err.(git.ErrBranchNotExist); ok {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name)))
} else {
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
}
} else if models.IsErrBranchAlreadyExists(err) {
// For when a user specifies a new branch that already exists
ctx.Data["Err_NewBranchName"] = true
if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName)))
} else {
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
ctx.Error(500, err.Error())
}
} else if models.IsErrCommitIDDoesNotMatch(err) {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.file_changed_while_renaming")))
} else if git.IsErrPushOutOfDate(err) {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.file_changed_while_renaming")))
} else if git.IsErrPushRejected(err) {
errPushRej := err.(*git.ErrPushRejected)
if len(errPushRej.Message) == 0 {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.push_rejected_no_message")))
} else {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(errPushRej.Message))))
}
} else {
ctx.JSON(http.StatusOK, response.ServerError(ctx.Tr("repo.editor.fail_to_update_file", form.TreePath, utils.SanitizeFlashErrorString(err.Error()))))
}
return
}
ctx.JSON(http.StatusOK, response.Success())

}

+ 5
- 0
routers/repo/view.go View File

@@ -608,6 +608,11 @@ func getContributorInfo(contributorInfos []*ContributorInfo, email string) *Cont

// Home render repository home page
func Home(ctx *context.Context) {
if ctx.Repo.CanEnableEditor() {
ctx.Data["CanEditFile"] = true
} else {
ctx.Data["CanEditFile"] = false
}
if len(ctx.Repo.Units) > 0 {
//get repo contributors info
contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath(), ctx.Repo.BranchName)


+ 32
- 0
routers/response/response.go View File

@@ -0,0 +1,32 @@
package response

const (
RESPONSE_CODE_SUCCESS = 0
RESPONSE_MSG_SUCCESS = "ok"
RESPONSE_CODE_ERROR_DEFAULT = 99
)

type AiforgeResponse struct {
Code int
Msg string
Data interface{}
}

func Success() *AiforgeResponse {
return &AiforgeResponse{Code: RESPONSE_CODE_SUCCESS, Msg: RESPONSE_MSG_SUCCESS}
}

func Error(code int, msg string) *AiforgeResponse {
return &AiforgeResponse{Code: code, Msg: msg}
}

func ServerError(msg string) *AiforgeResponse {
return &AiforgeResponse{Code: RESPONSE_CODE_ERROR_DEFAULT, Msg: msg}
}

func SuccessWithData(data interface{}) *AiforgeResponse {
return &AiforgeResponse{Code: RESPONSE_CODE_ERROR_DEFAULT, Msg: RESPONSE_MSG_SUCCESS, Data: data}
}
func ErrorWithData(code int, msg string, data interface{}) *AiforgeResponse {
return &AiforgeResponse{Code: code, Msg: msg, Data: data}
}

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

@@ -942,6 +942,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo("/_upload/*", repo.MustBeAbleToUpload).
Get(repo.UploadFile).
Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost)
m.Post("/_rename/*", bindIgnErr(auth.RenameRepoFileForm{}), repo.RenameFilePost)
}, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable)
m.Group("", func() {
m.Post("/upload-file", repo.UploadFileToServer)


+ 2
- 1
templates/repo/cloudbrain/models/dir_list.tmpl View File

@@ -20,8 +20,9 @@
<span class="time-since poping up">{{.ModTime}}</span>
</td>
</tr>

{{end}}
</tbody>
</table>

{{end}}
{{end}}

+ 244
- 199
templates/repo/home.tmpl View File

@@ -1,102 +1,114 @@
{{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;
top: 100%;
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;
width: 100% !important;

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




.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;
top: 100%;
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;
width: 100% !important;

-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;
}
</style>
<div class="repository file list">
{{template "repo/header" .}}
<div class="ui container">
<div class="ui negative message" style="display: none;">

</div>
{{template "base/alert" .}}

{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
<!-- <div class="ui repo-topic-edit grid form segment error" id="topic_edit" style="display:none">
<!-- <div class="ui repo-topic-edit grid form segment error" id="topic_edit" style="display:none">
<div class="fourteen wide column">
<div class="field">
<div class="ui fluid multiple search selection dropdown">
@@ -114,33 +126,33 @@
</div>
</div> -->

{{end}}
<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>
{{end}}
<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>
<div class="ui repo-description stackable grid">

{{if .RepoSearchEnabled}}
<div class="ui repo-search four wide column">
<form class="ui form ignore-dirty" action="{{.RepoLink}}/search" method="get">
<div class="field">
<div class="ui action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "repo.search.search_repo"}}">
<button class="ui icon button" type="submit">
<i class="search icon"></i>
</button>
</div>
<div class="ui repo-search four wide column">
<form class="ui form ignore-dirty" action="{{.RepoLink}}/search" method="get">
<div class="field">
<div class="ui action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "repo.search.search_repo"}}">
<button class="ui icon button" type="submit">
<i class="search icon"></i>
</button>
</div>
</form>
</div>
</div>
</form>
</div>
{{end}}
</div>

{{if .Repository.IsArchived}}
<div class="ui warning message">
{{.i18n.Tr "repo.archive.title"}}
</div>
<div class="ui warning message">
{{.i18n.Tr "repo.archive.title"}}
</div>
{{end}}
{{template "repo/sub_menu" .}}
<div class="ui stackable secondary menu mobile--margin-between-items mobile--no-negative-margins">
@@ -149,97 +161,122 @@
{{ $l := Subtract $n 1}}
<!-- If home page, show new PR. If not, show breadcrumb -->
{{if eq $n 0}}
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
<div class="fitted item">
<a href="{{.BaseRepo.Link}}/compare/{{.BaseRepo.DefaultBranch | EscapePound}}...{{if ne .Repository.Owner.Name .BaseRepo.Owner.Name}}{{.Repository.Owner.Name}}:{{end}}{{.BranchName | EscapePound}}">
<button id="new-pull-request" class="ui compact basic button">{{if .PullRequestCtx.Allowed}}{{.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{.i18n.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{if and .Repository.IsFork .PullRequestCtx.Allowed}}
{{if gt .FetchUpstreamCnt 0 }}
<a href="{{.Repository.Link}}/compare/{{.BranchName | EscapePound}}...{{.BaseRepo.Owner.Name}}:{{if .UpstreamSameBranchName}}{{.BranchName | EscapePound}}{{else}}{{.BaseRepo.DefaultBranch | EscapePound}}{{end}}">
<button id="new-pull-request" class="ui compact basic button" title="{{$.i18n.Tr (TrN $.i18n.Lang .FetchUpstreamCnt "repo.pulls.commits_count_1" "repo.pulls.commits_count_n") .FetchUpstreamCnt}}">{{.i18n.Tr "repo.pulls.fetch_upstream"}}</button>
</a>
{{else if lt .FetchUpstreamCnt 0}}
<a href="{{.Repository.Link}}/compare/{{.BranchName | EscapePound}}...{{.BaseRepo.Owner.Name}}:{{.BaseRepo.DefaultBranch | EscapePound}}">
<button id="new-pull-request" class="ui compact basic button" title="{{.i18n.Tr "repo.pulls.upstream_error"}}">{{.i18n.Tr "repo.pulls.fetch_upstream"}}</button>
</a>
{{else}}
<a href="{{.Repository.Link}}/compare/{{.BranchName | EscapePound}}...{{.BaseRepo.Owner.Name}}:{{if .UpstreamSameBranchName}}{{.BranchName | EscapePound}}{{else}}{{.BaseRepo.DefaultBranch | EscapePound}}{{end}}">
<button id="new-pull-request" class="ui compact basic button" title="{{.i18n.Tr "repo.pulls.upstream_up_to_date"}}">{{.i18n.Tr "repo.pulls.fetch_upstream"}}</button>
</a>
{{end}}
{{end}}
</div>
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
<div class="fitted item">
<a
href="{{.BaseRepo.Link}}/compare/{{.BaseRepo.DefaultBranch | EscapePound}}...{{if ne .Repository.Owner.Name .BaseRepo.Owner.Name}}{{.Repository.Owner.Name}}:{{end}}{{.BranchName | EscapePound}}">
<button id="new-pull-request"
class="ui compact basic button">{{if .PullRequestCtx.Allowed}}{{.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{.i18n.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{if and .Repository.IsFork .PullRequestCtx.Allowed}}
{{if gt .FetchUpstreamCnt 0 }}
<a
href="{{.Repository.Link}}/compare/{{.BranchName | EscapePound}}...{{.BaseRepo.Owner.Name}}:{{if .UpstreamSameBranchName}}{{.BranchName | EscapePound}}{{else}}{{.BaseRepo.DefaultBranch | EscapePound}}{{end}}">
<button id="new-pull-request" class="ui compact basic button"
title="{{$.i18n.Tr (TrN $.i18n.Lang .FetchUpstreamCnt "repo.pulls.commits_count_1" "repo.pulls.commits_count_n") .FetchUpstreamCnt}}">{{.i18n.Tr "repo.pulls.fetch_upstream"}}</button>
</a>
{{else if lt .FetchUpstreamCnt 0}}
<a
href="{{.Repository.Link}}/compare/{{.BranchName | EscapePound}}...{{.BaseRepo.Owner.Name}}:{{.BaseRepo.DefaultBranch | EscapePound}}">
<button id="new-pull-request" class="ui compact basic button"
title="{{.i18n.Tr "repo.pulls.upstream_error"}}">{{.i18n.Tr "repo.pulls.fetch_upstream"}}</button>
</a>
{{else}}
<a
href="{{.Repository.Link}}/compare/{{.BranchName | EscapePound}}...{{.BaseRepo.Owner.Name}}:{{if .UpstreamSameBranchName}}{{.BranchName | EscapePound}}{{else}}{{.BaseRepo.DefaultBranch | EscapePound}}{{end}}">
<button id="new-pull-request" class="ui compact basic button"
title="{{.i18n.Tr "repo.pulls.upstream_up_to_date"}}">{{.i18n.Tr "repo.pulls.fetch_upstream"}}</button>
</a>
{{end}}
{{end}}
</div>
{{end}}
{{else}}
<div class="fitted item"><span class="ui breadcrumb repo-path"><a class="section" href="{{.RepoLink}}/src/{{EscapePound .BranchNameSubURL}}" title="{{.Repository.Name}}">{{EllipsisString .Repository.Name 30}}</a>{{range $i, $v := .TreeNames}}<span class="divider">/</span>{{if eq $i $l}}<span class="active section" title="{{$v}}">{{EllipsisString $v 30}}</span>{{else}}{{ $p := index $.Paths $i}}<span class="section"><a href="{{EscapePound $.BranchLink}}/{{EscapePound $p}}" title="{{$v}}">{{EllipsisString $v 30}}</a></span>{{end}}{{end}}</span></div>
<div class="fitted item"><span class="ui breadcrumb repo-path"><a class="section"
href="{{.RepoLink}}/src/{{EscapePound .BranchNameSubURL}}"
title="{{.Repository.Name}}">{{EllipsisString .Repository.Name 30}}</a>{{range $i, $v := .TreeNames}}<span
class="divider">/</span>{{if eq $i $l}}<span class="active section"
title="{{$v}}">{{EllipsisString $v 30}}</span>{{else}}{{ $p := index $.Paths $i}}<span
class="section"><a href="{{EscapePound $.BranchLink}}/{{EscapePound $p}}"
title="{{$v}}">{{EllipsisString $v 30}}</a></span>{{end}}{{end}}</span></div>
{{end}}
<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}}
{{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>
<a href="{{.RepoLink}}/commits/{{EscapePound .BranchNameSubURL}}/{{EscapePound .TreePath}}"
class="ui button">
{{.i18n.Tr "repo.file_history"}}
</a>
{{end}}
</div>

</div>
<div class="fitted item">
{{if eq $n 0}}
{{if .Repository.IsTemplate}}
<div class="ui tiny blue buttons">
<a href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}" class="ui button">
{{.i18n.Tr "repo.use_template"}}
</a>
</div>
{{end}}
{{if .Repository.IsTemplate}}
<div class="ui tiny blue buttons">
<a href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}" class="ui button">
{{.i18n.Tr "repo.use_template"}}
</a>
</div>
{{end}}
{{end}}
</div>
<div class="fitted item">

<!-- Only show clone panel in repository home page -->
{{if eq $n 0}}
<div class="ui action tiny input" id="clone-panel">
{{if not $.DisableHTTP}}
<button class="ui basic clone button" id="repo-clone-https" data-link="{{.CloneLink.HTTPS}}">
{{if UseHTTPS}}HTTPS{{else}}HTTP{{end}}
</button>
{{end}}
{{if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
<button class="ui basic clone button" id="repo-clone-ssh" data-link="{{.CloneLink.SSH}}">
SSH
</button>
{{end}}
{{if not $.DisableHTTP}}
<input id="repo-clone-url" value="{{$.CloneLink.HTTPS}}" readonly>
{{else if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
<input id="repo-clone-url" value="{{$.CloneLink.SSH}}" readonly>
{{end}}
{{if or (not $.DisableHTTP) (and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH))}}
<button class="ui basic icon button poping up clipboard" id="clipboard-btn" data-original="{{.i18n.Tr "repo.copy_link"}}" data-success="{{.i18n.Tr "repo.copy_link_success"}}" data-error="{{.i18n.Tr "repo.copy_link_error"}}" data-content="{{.i18n.Tr "repo.copy_link"}}" data-variation="inverted tiny" data-clipboard-target="#repo-clone-url">
{{svg "octicon-clippy" 16}}
</button>
{{end}}
<div class="ui basic jump dropdown icon button poping up" data-content="{{.i18n.Tr "repo.download_archive"}}" data-variation="tiny inverted" data-position="top right">
<i class="download icon"></i>
<div class="menu">
<a class="item" href="{{$.RepoLink}}/archive/{{EscapePound $.BranchName}}.zip">{{svg "octicon-file-zip" 16}}&nbsp;ZIP</a>
<a class="item" href="{{$.RepoLink}}/archive/{{EscapePound $.BranchName}}.tar.gz">{{svg "octicon-file-zip" 16}}&nbsp;TAR.GZ</a>
</div>
<div class="ui action tiny input" id="clone-panel">
{{if not $.DisableHTTP}}
<button class="ui basic clone button" id="repo-clone-https" data-link="{{.CloneLink.HTTPS}}">
{{if UseHTTPS}}HTTPS{{else}}HTTP{{end}}
</button>
{{end}}
{{if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
<button class="ui basic clone button" id="repo-clone-ssh" data-link="{{.CloneLink.SSH}}">
SSH
</button>
{{end}}
{{if not $.DisableHTTP}}
<input id="repo-clone-url" value="{{$.CloneLink.HTTPS}}" readonly>
{{else if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
<input id="repo-clone-url" value="{{$.CloneLink.SSH}}" readonly>
{{end}}
{{if or (not $.DisableHTTP) (and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH))}}
<button class="ui basic icon button poping up clipboard" id="clipboard-btn"
data-original="{{.i18n.Tr "repo.copy_link"}}"
data-success="{{.i18n.Tr "repo.copy_link_success"}}"
data-error="{{.i18n.Tr "repo.copy_link_error"}}" data-content="{{.i18n.Tr "repo.copy_link"}}"
data-variation="inverted tiny" data-clipboard-target="#repo-clone-url">
{{svg "octicon-clippy" 16}}
</button>
{{end}}
<div class="ui basic jump dropdown icon button poping up"
data-content="{{.i18n.Tr "repo.download_archive"}}" data-variation="tiny inverted"
data-position="top right">
<i class="download icon"></i>
<div class="menu">
<a class="item"
href="{{$.RepoLink}}/archive/{{EscapePound $.BranchName}}.zip">{{svg "octicon-file-zip" 16}}&nbsp;ZIP</a>
<a class="item"
href="{{$.RepoLink}}/archive/{{EscapePound $.BranchName}}.tar.gz">{{svg "octicon-file-zip" 16}}&nbsp;TAR.GZ</a>
</div>
</div>
</div>
{{end}}
</div>
</div>
@@ -247,22 +284,24 @@
<div class="ui mobile reversed stackable grid">
<div class="ui ten wide tablet twelve wide computer column">
{{if .IsViewFile}}
{{template "repo/view_file" .}}
{{template "repo/view_file" .}}
{{else if .IsBlame}}
{{template "repo/blame" .}}
{{template "repo/blame" .}}
{{else}}
{{template "repo/view_list" .}}
{{template "repo/view_list" .}}
{{end}}
</div>
<div class="ui six wide tablet four wide computer column">
<div id="repo-desc" data-IsAdmin= "{{.Permission.IsAdmin}}" data-IsArchived="{{.Repository.IsArchived}}" >
<h4 id="about-desc" class="ui header">简介</h4>
<input type="hidden" id="edit-alias" value="{{.Repository.Alias}}">
<div id="repo-desc" data-IsAdmin="{{.Permission.IsAdmin}}"
data-IsArchived="{{.Repository.IsArchived}}">
<h4 id="about-desc" class="ui header">简介</h4>
<input type="hidden" id="edit-alias" value="{{.Repository.Alias}}">
<p>
{{if .Repository.DescriptionHTML}}
<span class="description" style="word-break:break-all">{{.Repository.DescriptionHTML}}</span>
<span class="description"
style="word-break:break-all">{{.Repository.DescriptionHTML}}</span>
{{else}}
<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span>
<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span>
{{end}}

</p>
@@ -274,7 +313,8 @@
{{if .Repository.Website}}
<p class="ui">
<i class="gray linkify icon"></i>
<a class="link edit-link" target="_blank" title="{{.Repository.Website}}" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
<a class="link edit-link" target="_blank" title="{{.Repository.Website}}"
href="{{.Repository.Website}}">{{.Repository.Website}}</a>
</p>

{{end}}
@@ -284,11 +324,13 @@

<div id="repo-topics1" style="flex: 1;">
{{range .Topics}}
<a class="ui repo-topic small label topic" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=">{{.Name}}</a>
<a class="ui repo-topic small label topic"
href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=">{{.Name}}</a>
{{end}}
</div>
<div>
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<i id="manage_topic" style="cursor: pointer;" class="plus icon"></i>{{end}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<i id="manage_topic"
style="cursor: pointer;" class="plus icon"></i>{{end}}
</div>
<div id="topic_edit" class="vue_menu" style="display:none">
<div id="topic_edit1">
@@ -302,7 +344,7 @@
<p class="ui">
<i class="grey code icon"></i>
{{range .LanguageStats}}
{{.Language}}
{{.Language}}
{{end}}
</p>

@@ -311,7 +353,7 @@
{{if .LICENSE}}
<p class="ui">
<i class="grey clone icon"></i>
{{.LICENSE}}
{{.LICENSE}}
</p>

{{end}}
@@ -326,19 +368,22 @@
{{else}}
<strong>贡献者 ({{len .ContributorInfo}}+)</strong>
{{end}}
<div class="ui right">
<!-- <a class="membersmore text grey" href="{{.RepoLink}}/contributors">全部 {{svg "octicon-chevron-right" 16}}</a> -->
<a class="membersmore text grey" href="{{.RepoLink}}/contributors?type={{if .IsViewBranch}}branch{{else}}tag{{end}}&name={{.BranchName}}">全部 {{svg "octicon-chevron-right" 16}}</a>
<a class="membersmore text grey"
href="{{.RepoLink}}/contributors?type={{if .IsViewBranch}}branch{{else}}tag{{end}}&name={{.BranchName}}">全部
{{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}}
{{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>
@@ -357,4 +402,4 @@
// });
// });
</script>
{{template "base/footer" .}}
{{template "base/footer" .}}

+ 172
- 81
templates/repo/view_list.tmpl View File

@@ -1,109 +1,200 @@
<table id="repo-files-table" class="ui single line table">
<style>
.context-menu {
z-index: 99;
position: absolute;
padding: 0;
border-radius: 4px;
border: 1px solid #e3e9ed;
-webkit-box-shadow: none;
box-shadow: none;
background: #fff;
display: none !important;
}

.context-menu.active {
display: block !important;
}

.context-menu-operation {
padding: 5px !important;
line-height: 1.78 !important;
}

.context-menu-icon {
float: left !important;
margin: 0px 5px 0px 0px !important;
}
</style>
<div id="mask">
<div id="loadingPage">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</div>
<table id="repo-files-table" class="ui single line table can-context-menu" data-can-editfile="{{.CanEditFile}}">
{{.CsrfTokenHtml}}
<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}}
<img class="ui avatar image img-12" src="{{.LatestCommitUser.RelAvatarLink}}" />
{{if .LatestCommitUser.FullName}}
<a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
{{else}}
{{if .LatestCommit.Author}}
<img class="ui avatar image img-12" src="{{AvatarLink .LatestCommit.Author.Email}}" />
<strong>{{.LatestCommit.Author.Name}}</strong>
{{end}}
<a
href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>
{{end}}
<a rel="nofollow" class="ui sha label {{if .LatestCommit.Signature}} isSigned {{if .LatestCommitVerification.Verified }} isVerified{{if eq .LatestCommitVerification.TrustStatus "trusted"}}{{else if eq .LatestCommitVerification.TrustStatus "untrusted"}}Untrusted{{else}}Unmatched{{end}}{{else if .LatestCommitVerification.Warning}} isWarning{{end}}{{end}}" href="{{.RepoLink}}/commit/{{.LatestCommit.ID}}">
{{else}}
{{if .LatestCommit.Author}}
<img class="ui avatar image img-12" src="{{AvatarLink .LatestCommit.Author.Email}}" />
<strong>{{.LatestCommit.Author.Name}}</strong>
{{end}}
{{end}}
<a rel="nofollow"
class="ui sha label {{if .LatestCommit.Signature}} isSigned {{if .LatestCommitVerification.Verified }} isVerified{{if eq .LatestCommitVerification.TrustStatus "trusted"}}{{else if eq .LatestCommitVerification.TrustStatus "untrusted"}}Untrusted{{else}}Unmatched{{end}}{{else if .LatestCommitVerification.Warning}} isWarning{{end}}{{end}}"
href="{{.RepoLink}}/commit/{{.LatestCommit.ID}}">
<span class="shortsha">{{ShortSha .LatestCommit.ID.String}}</span>
{{if .LatestCommit.Signature}}
<div class="ui detail icon button">
{{if .LatestCommitVerification.Verified}}
<div title="{{if eq .LatestCommitVerification.TrustStatus "trusted"}}{{else if eq .LatestCommitVerification.TrustStatus "untrusted"}}{{.i18n.Tr "repo.commits.signed_by_untrusted_user"}}: {{else}}{{.i18n.Tr "repo.commits.signed_by_untrusted_user_unmatched"}}: {{end}}{{.LatestCommitVerification.Reason}}">
{{if ne .LatestCommitVerification.SigningUser.ID 0}}
<i class="lock icon"></i>
<img class="ui signature avatar image" src="{{.LatestCommitVerification.SigningUser.RelAvatarLink}}" />
{{else}}
<i title="{{.LatestCommitVerification.Reason}}" class="icons">
<i class="lock icon"></i>
<i class="tiny inverted cog icon centerlock"></i>
</i>
<img class="ui signature avatar image" src="{{AvatarLink .LatestCommitVerification.SigningEmail}}" />
{{end}}
</div>
<div class="ui detail icon button">
{{if .LatestCommitVerification.Verified}}
<div
title="{{if eq .LatestCommitVerification.TrustStatus "trusted"}}{{else if eq .LatestCommitVerification.TrustStatus "untrusted"}}{{.i18n.Tr "repo.commits.signed_by_untrusted_user"}}: {{else}}{{.i18n.Tr "repo.commits.signed_by_untrusted_user_unmatched"}}: {{end}}{{.LatestCommitVerification.Reason}}">
{{if ne .LatestCommitVerification.SigningUser.ID 0}}
<i class="lock icon"></i>
<img class="ui signature avatar image"
src="{{.LatestCommitVerification.SigningUser.RelAvatarLink}}" />
{{else}}
<i title="{{$.i18n.Tr .LatestCommitVerification.Reason}}" class="unlock icon"></i>
<i title="{{.LatestCommitVerification.Reason}}" class="icons">
<i class="lock icon"></i>
<i class="tiny inverted cog icon centerlock"></i>
</i>
<img class="ui signature avatar image"
src="{{AvatarLink .LatestCommitVerification.SigningEmail}}" />
{{end}}
</div>
{{else}}
<i title="{{$.i18n.Tr .LatestCommitVerification.Reason}}" class="unlock icon"></i>
{{end}}
</div>
{{end}}
</a>
{{template "repo/commit_status" .LatestCommitStatus}}
{{ $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 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>
<th class="text grey right age">{{if .LatestCommit.Author}}{{TimeSince .LatestCommit.Author.When $.Lang}}{{end}}</th>
<th class="text grey right age">
{{if .LatestCommit.Author}}{{TimeSince .LatestCommit.Author.When $.Lang}}{{end}}</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>
<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 four 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}}
{{svg (printf "octicon-%s" (EntryIcon $entry)) 16}}
<a href="{{EscapePound $.TreeLink}}/{{EscapePound $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a>
{{end}}
</span>
</td>
{{end}}
<td class="message nine wide">
<span class="truncate">
<a href="{{$.RepoLink}}/commit/{{$commit.ID}}" title="{{$commit.Summary}}">{{$commit.Summary | RenderEmoji}}</a>
</span>
</td>
<td class="text right age three wide">{{TimeSince $commit.Committer.When $.Lang}}</td>
</tr>
{{$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 four wide modified-contextmenu">
<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}}
{{svg (printf "octicon-%s" (EntryIcon $entry)) 16}}
<a href="{{EscapePound $.TreeLink}}/{{EscapePound $entry.Name}}"
title="{{$entry.Name}}">{{$entry.Name}}</a>
{{end}}
</span>
</td>
{{end}}

<td class="message nine wide">
<span class="truncate">
<a href="{{$.RepoLink}}/commit/{{$commit.ID}}"
title="{{$commit.Summary}}">{{$commit.Summary | RenderEmoji}}</a>
</span>
</td>
<td class="text right age three wide">{{TimeSince $commit.Committer.When $.Lang}}</td>
</tr>
<tr style="display: none !important;" class="context-menu-one">
<td colspan="12">
<div class="ui column form" method="POST">
<div class="two fields" style="margin: 0;">
<div class="five wide field">
<input class="ui input" name="new_filename" type="text" value="">
</div>
<div class="five wide field">
<button class="ui blue button popup-save" type="button"
data-postBasePath="{{$.RepoLink}}/_rename/{{EscapePound $.BranchName}}{{if $.TreePath}}/{{EscapePound $.TreePath}}{{end}}/{{$entry.Name}}"
data-commit="{{$.LatestCommit.ID}}"
data-treepath="{{if $.TreePath}}{{EscapePound $.TreePath}}/{{end}}">保存</button>
<button class="ui basic button popup-close" type="button">取消</button>
</div>
</div>
</div>
</td>
</tr>
{{end}}
</tbody>
</table>
{{if .ReadmeExist}}
{{template "repo/view_file" .}}
{{template "repo/view_file" .}}
{{end}}

<!-- 确认模态框 -->
<div id="deletemodel">
<div class="ui basic modal context-menu-delete">
<div class="ui icon header">
<i class="trash icon"></i> {{.i18n.Tr "cloudbrain.delete_task"}}
</div>

<div class="content">
<p>{{.i18n.Tr "cloudbrain.task_delete_confirm"}}</p>
</div>
<div class="actions">
<div class="ui red basic inverted cancel button">
<i class="remove icon"></i> {{.i18n.Tr "cloudbrain.operate_cancel"}}
</div>
<div class="ui green basic inverted ok button">
<i class="checkmark icon"></i> {{.i18n.Tr "cloudbrain.operate_confirm"}}
</div>
</div>
</div>
</div>

+ 153
- 0
web_src/js/features/contexmenu.js View File

@@ -0,0 +1,153 @@

export default async function initContextMenu() {
$('.popup-close').on('click', function (e) {
$(this).parents('tr').prev().css('display', 'table-row')
$(this).closest('tr').css('cssText', 'display:none !important')
})
function contextMenu() {
let canContextMenu = $('.ui.single.line.table.can-context-menu').data('can-editfile')
if (canContextMenu) {
$('.name.four.wide').on('contextmenu', function (e) {
let ev = window.event || e;
ev.preventDefault();
menu.show(e)
})
} else {
return
}
}
contextMenu()
const menu = new Menu({
data: [
{
label: '新标签打开',
icon: "file outline icon context-menu-icon",
active: (e, a) => {
window.open(a.currentTarget.getElementsByTagName("a")[0].getAttribute('href'))
}
},
{
label: '重命名',
icon: "edit icon context-menu-icon",
active: (e, a) => {
document.querySelectorAll(".context-menu-one").forEach((ele) => {
if (ele.style.display === 'table-row') {

ele.style.display = 'none'
ele.previousElementSibling.style.display = 'table-row'
}
})
if (a.currentTarget.parentNode.nextElementSibling) {
a.currentTarget.parentNode.style.setProperty('display', 'none', 'important')
a.currentTarget.parentNode.nextElementSibling.style.display = 'table-row'
a.currentTarget.parentNode.nextElementSibling.getElementsByTagName("input")[0].setAttribute("value", a.currentTarget.getElementsByTagName("a")[0].getAttribute('title'))

}
let btn = a.currentTarget.parentNode.nextElementSibling.getElementsByTagName("button")[0]
btn.addEventListener('click', function (e) {
let postUrl = btn.getAttribute('data-postbasepath')
let last_commit = btn.getAttribute('data-commit')
let tree_path = btn.getAttribute('data-treepath') + e.target.parentNode.previousElementSibling.getElementsByTagName("input")[0].value
let csrf = $("input[name='_csrf']").val()
$.ajax({
url: postUrl,
type: "POST",
contentType: "application/x-www-form-urlencoded",
data: { last_commit: last_commit, tree_path: tree_path, _csrf: csrf },
success: function (res) {
console.log("--------------")
console.log(res.Code)
console.log(res.Message)
if (res.Code === 0) {
document.getElementById("mask").style.display = "block"
location.reload()
}
else {
$('.ui.negative.message').text(res.Msg).show().delay(10000).fadeOut();
}
}

})

})
},
},
// {
// label: '删除',
// icon: "trash icon context-menu-icon",
// active: (e, a) => {
// console.dir(a)
// $('.context-menu-delete.modal')
// .modal({
// onApprove() {
// }
// })
// .modal('show');
// }
// },

]
})
}
class Menu {
constructor(param) {
this.target = document.createElement('div')
this.target.classList.add("ui", "menu", "compact", "vertical", "context-menu")
this.data = param.data
this.active = false
this.clickZ = this.click.bind(this)
this.closeZ = this.close2.bind(this)
document.addEventListener('click', this.closeZ)
for (let i = 0; i < this.data.length; i++) {
let div = document.createElement('a')
div.classList.add('item', 'context-menu-operation')
if (this.data[i].disabled) {
div.classList.add('disabled')
}
div.dataset.index = i.toString()
div.innerHTML = `<i class="${this.data[i].icon}"></i>${this.data[i].label}`
div.addEventListener('click', this.clickZ)
this.target.append(div)
}
document.body.append(this.target)
}
click(e) {
let index = parseInt(e.target.dataset.index)
if (this.data[index].active) {
this.data[index].active(e, this.acEvent)
}
this.close()
}
show(e) {
this.active = true
this.nodeList = this.target.querySelectorAll('.item')
for (let i = 0; i < this.data.length; i++) {
if (this.data[i].beforeDisabled) {
let t = this.data[i].beforeDisabled(e)
this.data[i].disabled = t
if (t) {
this.nodeList[i].classList.add('disabled')
} else {
this.nodeList[i].classList.remove('disabled')
}
}
}
this.acEvent = e
this.target.style.top = `${e.pageY}px`
this.target.style.left = `${e.pageX}px`
this.target.style.minWidth = '90px'
this.target.classList.add('active')
}

close() {
this.active = false
this.target.classList.remove('active')
}

close2(e) {
if (!this.target.contains(e.target) && this.active) {
this.active = false
this.target.classList.remove('active')
}
}
}

+ 518
- 514
web_src/js/index.js
File diff suppressed because it is too large
View File


Loading…
Cancel
Save