| @@ -740,3 +740,9 @@ type CreateCourseForm struct { | |||||
| func (f *CreateCourseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | func (f *CreateCourseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | ||||
| return validate(errs, ctx.Data, f, ctx.Locale) | 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 | |||||
| } | |||||
| @@ -109,6 +109,34 @@ func (t *TemporaryUploadRepository) LsFiles(filenames ...string) ([]string, erro | |||||
| return filelist, nil | 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 | // RemoveFilesFromIndex removes the given files from the index | ||||
| func (t *TemporaryUploadRepository) RemoveFilesFromIndex(filenames ...string) error { | func (t *TemporaryUploadRepository) RemoveFilesFromIndex(filenames ...string) error { | ||||
| stdOut := new(bytes.Buffer) | stdOut := new(bytes.Buffer) | ||||
| @@ -756,3 +756,210 @@ func createCommitRepoActions(repo *models.Repository, gitRepo *git.Repository, o | |||||
| } | } | ||||
| return actions, nil | 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 | |||||
| } | |||||
| @@ -263,7 +263,7 @@ search_issue=Issue | |||||
| search_pr=Pull Request | search_pr=Pull Request | ||||
| search_user=User | search_user=User | ||||
| search_org=Organization | search_org=Organization | ||||
| search_finded=Find | |||||
| search_finded=Find | |||||
| search_related=related | search_related=related | ||||
| search_maybe=maybe | search_maybe=maybe | ||||
| search_ge= | 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. | provide_resoure = Computing resources of CPU/GPU/NPU are provided freely for various types of AI tasks. | ||||
| activity = Activity | activity = Activity | ||||
| no_events = There are no events related | no_events = There are no events related | ||||
| or_t = or | |||||
| or_t = or | |||||
| [explore] | [explore] | ||||
| repos = Repositories | 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_too_large = Repository can not exceed %d MB | ||||
| editor.repo_file_invalid = Upload files are invalid | editor.repo_file_invalid = Upload files are invalid | ||||
| editor.upload_file_too_much = Can not upload more than %d files at a time | 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. | 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.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.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.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.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` | issues.remove_branch_at=`removed this from the <b>%s</b> branch %s` | ||||
| @@ -3041,4 +3043,4 @@ SNN4IMAGENET = SNN4IMAGENET | |||||
| BRAINSCORE = BRAINSCORE | BRAINSCORE = BRAINSCORE | ||||
| TRAIN = TRAIN | TRAIN = TRAIN | ||||
| INFERENCE = INFERENCE | INFERENCE = INFERENCE | ||||
| BENCHMARK = BENCHMARK | |||||
| BENCHMARK = BENCHMARK | |||||
| @@ -271,7 +271,7 @@ search_maybe=约为 | |||||
| search_ge=个 | search_ge=个 | ||||
| wecome_AI_plt=欢迎来到启智AI协作平台! | wecome_AI_plt=欢迎来到启智AI协作平台! | ||||
| explore_AI = 探索更好的AI,来这里发现更有意思的 | |||||
| explore_AI = 探索更好的AI,来这里发现更有意思的 | |||||
| datasets = 数据集 | datasets = 数据集 | ||||
| repositories = 项目 | repositories = 项目 | ||||
| use_plt__fuction = 使用本平台提供的AI协作功能,如:托管代码、共享数据、调试算法或训练模型,请先 | use_plt__fuction = 使用本平台提供的AI协作功能,如:托管代码、共享数据、调试算法或训练模型,请先 | ||||
| @@ -279,7 +279,7 @@ provide_resoure = 平台目前免费提供CPU、GPU、NPU的算力资源,可 | |||||
| create_pro = 创建项目 | create_pro = 创建项目 | ||||
| activity = 活动 | activity = 活动 | ||||
| no_events = 还没有与您相关的活动 | no_events = 还没有与您相关的活动 | ||||
| or_t = 或 | |||||
| or_t = 或 | |||||
| [explore] | [explore] | ||||
| @@ -1338,6 +1338,8 @@ editor.require_signed_commit=分支需要签名提交 | |||||
| editor.repo_too_large = 代码仓总大小不能超过%dMB | editor.repo_too_large = 代码仓总大小不能超过%dMB | ||||
| editor.repo_file_invalid = 提交的文件非法 | editor.repo_file_invalid = 提交的文件非法 | ||||
| editor.upload_file_too_much = 不能同时提交超过%d个文件 | editor.upload_file_too_much = 不能同时提交超过%d个文件 | ||||
| editor.rename = 重命名"%s"为"%s" | |||||
| editor.file_changed_while_renaming=待重命名的文件或文件夹版本已发生变化,请您刷新页面后重试 | |||||
| commits.desc=浏览代码修改历史 | commits.desc=浏览代码修改历史 | ||||
| commits.commits=次代码提交 | commits.commits=次代码提交 | ||||
| @@ -5,10 +5,12 @@ | |||||
| package repo | package repo | ||||
| import ( | import ( | ||||
| "code.gitea.io/gitea/routers/response" | |||||
| repo_service "code.gitea.io/gitea/services/repository" | repo_service "code.gitea.io/gitea/services/repository" | ||||
| "encoding/json" | "encoding/json" | ||||
| "fmt" | "fmt" | ||||
| "io/ioutil" | "io/ioutil" | ||||
| "net/http" | |||||
| "path" | "path" | ||||
| "path/filepath" | "path/filepath" | ||||
| "strings" | "strings" | ||||
| @@ -795,3 +797,102 @@ func GetClosestParentWithFiles(treePath string, commit *git.Commit) string { | |||||
| } | } | ||||
| return treePath | 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()) | |||||
| } | |||||
| @@ -608,6 +608,11 @@ func getContributorInfo(contributorInfos []*ContributorInfo, email string) *Cont | |||||
| // Home render repository home page | // Home render repository home page | ||||
| func Home(ctx *context.Context) { | func Home(ctx *context.Context) { | ||||
| if ctx.Repo.CanEnableEditor() { | |||||
| ctx.Data["CanEditFile"] = true | |||||
| } else { | |||||
| ctx.Data["CanEditFile"] = false | |||||
| } | |||||
| if len(ctx.Repo.Units) > 0 { | if len(ctx.Repo.Units) > 0 { | ||||
| //get repo contributors info | //get repo contributors info | ||||
| contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath(), ctx.Repo.BranchName) | contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath(), ctx.Repo.BranchName) | ||||
| @@ -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} | |||||
| } | |||||
| @@ -942,6 +942,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Combo("/_upload/*", repo.MustBeAbleToUpload). | m.Combo("/_upload/*", repo.MustBeAbleToUpload). | ||||
| Get(repo.UploadFile). | Get(repo.UploadFile). | ||||
| Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost) | Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost) | ||||
| m.Post("/_rename/*", bindIgnErr(auth.RenameRepoFileForm{}), repo.RenameFilePost) | |||||
| }, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable) | }, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable) | ||||
| m.Group("", func() { | m.Group("", func() { | ||||
| m.Post("/upload-file", repo.UploadFileToServer) | m.Post("/upload-file", repo.UploadFileToServer) | ||||
| @@ -20,8 +20,9 @@ | |||||
| <span class="time-since poping up">{{.ModTime}}</span> | <span class="time-since poping up">{{.ModTime}}</span> | ||||
| </td> | </td> | ||||
| </tr> | </tr> | ||||
| {{end}} | {{end}} | ||||
| </tbody> | </tbody> | ||||
| </table> | </table> | ||||
| {{end}} | |||||
| {{end}} | |||||
| @@ -1,102 +1,114 @@ | |||||
| {{template "base/head" .}} | {{template "base/head" .}} | ||||
| <style> | <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> | </style> | ||||
| <div class="repository file list"> | <div class="repository file list"> | ||||
| {{template "repo/header" .}} | {{template "repo/header" .}} | ||||
| <div class="ui container"> | <div class="ui container"> | ||||
| <div class="ui negative message" style="display: none;"> | |||||
| </div> | |||||
| {{template "base/alert" .}} | {{template "base/alert" .}} | ||||
| {{if and .Permission.IsAdmin (not .Repository.IsArchived)}} | {{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="fourteen wide column"> | ||||
| <div class="field"> | <div class="field"> | ||||
| <div class="ui fluid multiple search selection dropdown"> | <div class="ui fluid multiple search selection dropdown"> | ||||
| @@ -114,33 +126,33 @@ | |||||
| </div> | </div> | ||||
| </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"> | <div class="ui repo-description stackable grid"> | ||||
| {{if .RepoSearchEnabled}} | {{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> | </div> | ||||
| </form> | |||||
| </div> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| {{if .Repository.IsArchived}} | {{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}} | {{end}} | ||||
| {{template "repo/sub_menu" .}} | {{template "repo/sub_menu" .}} | ||||
| <div class="ui stackable secondary menu mobile--margin-between-items mobile--no-negative-margins"> | <div class="ui stackable secondary menu mobile--margin-between-items mobile--no-negative-margins"> | ||||
| @@ -149,97 +161,122 @@ | |||||
| {{ $l := Subtract $n 1}} | {{ $l := Subtract $n 1}} | ||||
| <!-- If home page, show new PR. If not, show breadcrumb --> | <!-- If home page, show new PR. If not, show breadcrumb --> | ||||
| {{if eq $n 0}} | {{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}} | {{end}} | ||||
| </div> | |||||
| {{end}} | |||||
| {{else}} | {{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}} | {{end}} | ||||
| <div class="right fitted item" id="file-buttons"> | <div class="right fitted item" id="file-buttons"> | ||||
| <div class="ui tiny blue buttons"> | <div class="ui tiny blue buttons"> | ||||
| {{if .Repository.CanEnableEditor}} | {{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}} | {{end}} | ||||
| {{if and (ne $n 0) (not .IsViewFile) (not .IsBlame) }} | {{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}} | {{end}} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div class="fitted item"> | <div class="fitted item"> | ||||
| {{if eq $n 0}} | {{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}} | {{end}} | ||||
| </div> | </div> | ||||
| <div class="fitted item"> | <div class="fitted item"> | ||||
| <!-- Only show clone panel in repository home page --> | <!-- Only show clone panel in repository home page --> | ||||
| {{if eq $n 0}} | {{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}} ZIP</a> | |||||
| <a class="item" href="{{$.RepoLink}}/archive/{{EscapePound $.BranchName}}.tar.gz">{{svg "octicon-file-zip" 16}} 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}} ZIP</a> | |||||
| <a class="item" | |||||
| href="{{$.RepoLink}}/archive/{{EscapePound $.BranchName}}.tar.gz">{{svg "octicon-file-zip" 16}} TAR.GZ</a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | |||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -247,22 +284,24 @@ | |||||
| <div class="ui mobile reversed stackable grid"> | <div class="ui mobile reversed stackable grid"> | ||||
| <div class="ui ten wide tablet twelve wide computer column"> | <div class="ui ten wide tablet twelve wide computer column"> | ||||
| {{if .IsViewFile}} | {{if .IsViewFile}} | ||||
| {{template "repo/view_file" .}} | |||||
| {{template "repo/view_file" .}} | |||||
| {{else if .IsBlame}} | {{else if .IsBlame}} | ||||
| {{template "repo/blame" .}} | |||||
| {{template "repo/blame" .}} | |||||
| {{else}} | {{else}} | ||||
| {{template "repo/view_list" .}} | |||||
| {{template "repo/view_list" .}} | |||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| <div class="ui six wide tablet four wide computer column"> | <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> | <p> | ||||
| {{if .Repository.DescriptionHTML}} | {{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}} | {{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}} | {{end}} | ||||
| </p> | </p> | ||||
| @@ -274,7 +313,8 @@ | |||||
| {{if .Repository.Website}} | {{if .Repository.Website}} | ||||
| <p class="ui"> | <p class="ui"> | ||||
| <i class="gray linkify icon"></i> | <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> | </p> | ||||
| {{end}} | {{end}} | ||||
| @@ -284,11 +324,13 @@ | |||||
| <div id="repo-topics1" style="flex: 1;"> | <div id="repo-topics1" style="flex: 1;"> | ||||
| {{range .Topics}} | {{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}} | {{end}} | ||||
| </div> | </div> | ||||
| <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> | ||||
| <div id="topic_edit" class="vue_menu" style="display:none"> | <div id="topic_edit" class="vue_menu" style="display:none"> | ||||
| <div id="topic_edit1"> | <div id="topic_edit1"> | ||||
| @@ -302,7 +344,7 @@ | |||||
| <p class="ui"> | <p class="ui"> | ||||
| <i class="grey code icon"></i> | <i class="grey code icon"></i> | ||||
| {{range .LanguageStats}} | {{range .LanguageStats}} | ||||
| {{.Language}} | |||||
| {{.Language}} | |||||
| {{end}} | {{end}} | ||||
| </p> | </p> | ||||
| @@ -311,7 +353,7 @@ | |||||
| {{if .LICENSE}} | {{if .LICENSE}} | ||||
| <p class="ui"> | <p class="ui"> | ||||
| <i class="grey clone icon"></i> | <i class="grey clone icon"></i> | ||||
| {{.LICENSE}} | |||||
| {{.LICENSE}} | |||||
| </p> | </p> | ||||
| {{end}} | {{end}} | ||||
| @@ -326,19 +368,22 @@ | |||||
| {{else}} | {{else}} | ||||
| <strong>贡献者 ({{len .ContributorInfo}}+)</strong> | <strong>贡献者 ({{len .ContributorInfo}}+)</strong> | ||||
| {{end}} | {{end}} | ||||
| <div class="ui right"> | <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">全部 {{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> | </div> | ||||
| </h4> | </h4> | ||||
| <div class="ui members" id="contributorInfo"> | <div class="ui members" id="contributorInfo"> | ||||
| {{range .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}} | {{end}} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -357,4 +402,4 @@ | |||||
| // }); | // }); | ||||
| // }); | // }); | ||||
| </script> | </script> | ||||
| {{template "base/footer" .}} | |||||
| {{template "base/footer" .}} | |||||
| @@ -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> | <thead> | ||||
| <tr class="commit-list"> | <tr class="commit-list"> | ||||
| <th colspan="2"> | <th colspan="2"> | ||||
| {{if .LatestCommitUser}} | {{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}} | {{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}} | {{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> | <span class="shortsha">{{ShortSha .LatestCommit.ID.String}}</span> | ||||
| {{if .LatestCommit.Signature}} | {{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}} | {{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}} | {{end}} | ||||
| </div> | </div> | ||||
| {{else}} | |||||
| <i title="{{$.i18n.Tr .LatestCommitVerification.Reason}}" class="unlock icon"></i> | |||||
| {{end}} | |||||
| </div> | |||||
| {{end}} | {{end}} | ||||
| </a> | </a> | ||||
| {{template "repo/commit_status" .LatestCommitStatus}} | {{template "repo/commit_status" .LatestCommitStatus}} | ||||
| {{ $commitLink:= printf "%s/commit/%s" .RepoLink .LatestCommit.ID }} | {{ $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> | </span> | ||||
| </th> | </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> | </tr> | ||||
| </thead> | </thead> | ||||
| <tbody> | <tbody> | ||||
| {{if .HasParentPath}} | {{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}} | {{end}} | ||||
| {{range $item := .Files}} | {{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}} | {{end}} | ||||
| </tbody> | </tbody> | ||||
| </table> | </table> | ||||
| {{if .ReadmeExist}} | {{if .ReadmeExist}} | ||||
| {{template "repo/view_file" .}} | |||||
| {{template "repo/view_file" .}} | |||||
| {{end}} | {{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> | |||||
| @@ -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') | |||||
| } | |||||
| } | |||||
| } | |||||