| @@ -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 | |||
| } | |||
| @@ -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) | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| @@ -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=次代码提交 | |||
| @@ -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()) | |||
| } | |||
| @@ -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) | |||
| @@ -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). | |||
| 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) | |||
| @@ -20,8 +20,9 @@ | |||
| <span class="time-since poping up">{{.ModTime}}</span> | |||
| </td> | |||
| </tr> | |||
| {{end}} | |||
| </tbody> | |||
| </table> | |||
| {{end}} | |||
| {{end}} | |||
| @@ -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}} 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> | |||
| {{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" .}} | |||
| @@ -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> | |||
| @@ -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') | |||
| } | |||
| } | |||
| } | |||