Link to previous blames in file blame page (#16259)
Adds a link to each blame hunk, to view the blame of an earlier version of the file, similar to GitHub. Also refactors the blame render from fmtstring based to template based. * Fix blame bottom line and add blame prior button * Jump to previous parent commit from the commit. * Fix previous commit link * Fix previous blame link * Fix the given file not exist in the previous commit. * Fix blameRow struct not export * fix theming issues, rename template var * remove unused LastCommit fetch * fix location of blame-hunk divider * rewrite previous commit checks * remove duplicate commit lookup its already resolved and stored in ctx.Repo.Commit! * split out blamePart processing into function Co-authored-by: rogerluo410 <rogerluo410@gmail.com>
This commit is contained in:
		
							parent
							
								
									59c58553ba
								
							
						
					
					
						commit
						9c6aeb47f7
					
				
					 6 changed files with 166 additions and 101 deletions
				
			
		|  | @ -809,6 +809,7 @@ delete_preexisting_label = Delete | ||||||
| delete_preexisting = Delete pre-existing files | delete_preexisting = Delete pre-existing files | ||||||
| delete_preexisting_content = Delete files in %s | delete_preexisting_content = Delete files in %s | ||||||
| delete_preexisting_success = Deleted unadopted files in %s | delete_preexisting_success = Deleted unadopted files in %s | ||||||
|  | blame_prior = View blame prior to this change | ||||||
| 
 | 
 | ||||||
| transfer.accept = Accept Transfer | transfer.accept = Accept Transfer | ||||||
| transfer.accept_desc =  Transfer to "%s" | transfer.accept_desc =  Transfer to "%s" | ||||||
|  |  | ||||||
|  | @ -5,7 +5,6 @@ | ||||||
| package repo | package repo | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" |  | ||||||
| 	"container/list" | 	"container/list" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"html" | 	"html" | ||||||
|  | @ -18,7 +17,6 @@ import ( | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/highlight" | 	"code.gitea.io/gitea/modules/highlight" | ||||||
| 	"code.gitea.io/gitea/modules/log" |  | ||||||
| 	"code.gitea.io/gitea/modules/templates" | 	"code.gitea.io/gitea/modules/templates" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| ) | ) | ||||||
|  | @ -27,6 +25,20 @@ const ( | ||||||
| 	tplBlame base.TplName = "repo/home" | 	tplBlame base.TplName = "repo/home" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | type blameRow struct { | ||||||
|  | 	RowNumber      int | ||||||
|  | 	Avatar         gotemplate.HTML | ||||||
|  | 	RepoLink       string | ||||||
|  | 	PartSha        string | ||||||
|  | 	PreviousSha    string | ||||||
|  | 	PreviousShaURL string | ||||||
|  | 	IsFirstCommit  bool | ||||||
|  | 	CommitURL      string | ||||||
|  | 	CommitMessage  string | ||||||
|  | 	CommitSince    gotemplate.HTML | ||||||
|  | 	Code           gotemplate.HTML | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // RefBlame render blame page
 | // RefBlame render blame page
 | ||||||
| func RefBlame(ctx *context.Context) { | func RefBlame(ctx *context.Context) { | ||||||
| 	fileName := ctx.Repo.TreePath | 	fileName := ctx.Repo.TreePath | ||||||
|  | @ -39,19 +51,6 @@ func RefBlame(ctx *context.Context) { | ||||||
| 	repoName := ctx.Repo.Repository.Name | 	repoName := ctx.Repo.Repository.Name | ||||||
| 	commitID := ctx.Repo.CommitID | 	commitID := ctx.Repo.CommitID | ||||||
| 
 | 
 | ||||||
| 	commit, err := ctx.Repo.GitRepo.GetCommit(commitID) |  | ||||||
| 	if err != nil { |  | ||||||
| 		if git.IsErrNotExist(err) { |  | ||||||
| 			ctx.NotFound("Repo.GitRepo.GetCommit", err) |  | ||||||
| 		} else { |  | ||||||
| 			ctx.ServerError("Repo.GitRepo.GetCommit", err) |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if len(commitID) != 40 { |  | ||||||
| 		commitID = commit.ID.String() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() | 	branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL() | ||||||
| 	treeLink := branchLink | 	treeLink := branchLink | ||||||
| 	rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() | 	rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() | ||||||
|  | @ -74,25 +73,6 @@ func RefBlame(ctx *context.Context) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Show latest commit info of repository in table header,
 |  | ||||||
| 	// or of directory if not in root directory.
 |  | ||||||
| 	latestCommit := ctx.Repo.Commit |  | ||||||
| 	if len(ctx.Repo.TreePath) > 0 { |  | ||||||
| 		latestCommit, err = ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath) |  | ||||||
| 		if err != nil { |  | ||||||
| 			ctx.ServerError("GetCommitByPath", err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	ctx.Data["LatestCommit"] = latestCommit |  | ||||||
| 	ctx.Data["LatestCommitVerification"] = models.ParseCommitWithSignature(latestCommit) |  | ||||||
| 	ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit) |  | ||||||
| 
 |  | ||||||
| 	statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), models.ListOptions{}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Error("GetLatestCommitStatus: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Get current entry user currently looking at.
 | 	// Get current entry user currently looking at.
 | ||||||
| 	entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) | 	entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -102,9 +82,6 @@ func RefBlame(ctx *context.Context) { | ||||||
| 
 | 
 | ||||||
| 	blob := entry.Blob() | 	blob := entry.Blob() | ||||||
| 
 | 
 | ||||||
| 	ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses) |  | ||||||
| 	ctx.Data["LatestCommitStatuses"] = statuses |  | ||||||
| 
 |  | ||||||
| 	ctx.Data["Paths"] = paths | 	ctx.Data["Paths"] = paths | ||||||
| 	ctx.Data["TreeLink"] = treeLink | 	ctx.Data["TreeLink"] = treeLink | ||||||
| 	ctx.Data["TreeNames"] = treeNames | 	ctx.Data["TreeNames"] = treeNames | ||||||
|  | @ -145,8 +122,33 @@ func RefBlame(ctx *context.Context) { | ||||||
| 		blameParts = append(blameParts, *blamePart) | 		blameParts = append(blameParts, *blamePart) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Get Topics of this repo
 | ||||||
|  | 	renderRepoTopics(ctx) | ||||||
|  | 	if ctx.Written() { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	commitNames, previousCommits := processBlameParts(ctx, blameParts) | ||||||
|  | 	if ctx.Written() { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	renderBlame(ctx, blameParts, commitNames, previousCommits) | ||||||
|  | 
 | ||||||
|  | 	ctx.HTML(http.StatusOK, tplBlame) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) (map[string]models.UserCommit, map[string]string) { | ||||||
|  | 	// store commit data by SHA to look up avatar info etc
 | ||||||
| 	commitNames := make(map[string]models.UserCommit) | 	commitNames := make(map[string]models.UserCommit) | ||||||
|  | 	// previousCommits contains links from SHA to parent SHA,
 | ||||||
|  | 	// if parent also contains the current TreePath.
 | ||||||
|  | 	previousCommits := make(map[string]string) | ||||||
|  | 	// and as blameParts can reference the same commits multiple
 | ||||||
|  | 	// times, we cache the lookup work locally
 | ||||||
| 	commits := list.New() | 	commits := list.New() | ||||||
|  | 	commitCache := map[string]*git.Commit{} | ||||||
|  | 	commitCache[ctx.Repo.Commit.ID.String()] = ctx.Repo.Commit | ||||||
| 
 | 
 | ||||||
| 	for _, part := range blameParts { | 	for _, part := range blameParts { | ||||||
| 		sha := part.Sha | 		sha := part.Sha | ||||||
|  | @ -154,14 +156,38 @@ func RefBlame(ctx *context.Context) { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		commit, err := ctx.Repo.GitRepo.GetCommit(sha) | 		// find the blamePart commit, to look up parent & email address for avatars
 | ||||||
|  | 		commit, ok := commitCache[sha] | ||||||
|  | 		var err error | ||||||
|  | 		if !ok { | ||||||
|  | 			commit, err = ctx.Repo.GitRepo.GetCommit(sha) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				if git.IsErrNotExist(err) { | 				if git.IsErrNotExist(err) { | ||||||
| 					ctx.NotFound("Repo.GitRepo.GetCommit", err) | 					ctx.NotFound("Repo.GitRepo.GetCommit", err) | ||||||
| 				} else { | 				} else { | ||||||
| 					ctx.ServerError("Repo.GitRepo.GetCommit", err) | 					ctx.ServerError("Repo.GitRepo.GetCommit", err) | ||||||
| 				} | 				} | ||||||
| 			return | 				return nil, nil | ||||||
|  | 			} | ||||||
|  | 			commitCache[sha] = commit | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// find parent commit
 | ||||||
|  | 		if commit.ParentCount() > 0 { | ||||||
|  | 			psha := commit.Parents[0] | ||||||
|  | 			previousCommit, ok := commitCache[psha.String()] | ||||||
|  | 			if !ok { | ||||||
|  | 				previousCommit, _ = commit.Parent(0) | ||||||
|  | 				if previousCommit != nil { | ||||||
|  | 					commitCache[psha.String()] = previousCommit | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			// only store parent commit ONCE, if it has the file
 | ||||||
|  | 			if previousCommit != nil { | ||||||
|  | 				if haz1, _ := previousCommit.HasFile(ctx.Repo.TreePath); haz1 { | ||||||
|  | 					previousCommits[commit.ID.String()] = previousCommit.ID.String() | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		commits.PushBack(commit) | 		commits.PushBack(commit) | ||||||
|  | @ -169,46 +195,39 @@ func RefBlame(ctx *context.Context) { | ||||||
| 		commitNames[commit.ID.String()] = models.UserCommit{} | 		commitNames[commit.ID.String()] = models.UserCommit{} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// populate commit email addresses to later look up avatars.
 | ||||||
| 	commits = models.ValidateCommitsWithEmails(commits) | 	commits = models.ValidateCommitsWithEmails(commits) | ||||||
| 
 |  | ||||||
| 	for e := commits.Front(); e != nil; e = e.Next() { | 	for e := commits.Front(); e != nil; e = e.Next() { | ||||||
| 		c := e.Value.(models.UserCommit) | 		c := e.Value.(models.UserCommit) | ||||||
| 
 |  | ||||||
| 		commitNames[c.ID.String()] = c | 		commitNames[c.ID.String()] = c | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Get Topics of this repo
 | 	return commitNames, previousCommits | ||||||
| 	renderRepoTopics(ctx) |  | ||||||
| 	if ctx.Written() { |  | ||||||
| 		return |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 	renderBlame(ctx, blameParts, commitNames) | func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]models.UserCommit, previousCommits map[string]string) { | ||||||
| 
 |  | ||||||
| 	ctx.HTML(http.StatusOK, tplBlame) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]models.UserCommit) { |  | ||||||
| 	repoLink := ctx.Repo.RepoLink | 	repoLink := ctx.Repo.RepoLink | ||||||
| 
 | 
 | ||||||
| 	var lines = make([]string, 0) | 	var lines = make([]string, 0) | ||||||
| 
 | 	rows := make([]*blameRow, 0) | ||||||
| 	var commitInfo bytes.Buffer |  | ||||||
| 	var lineNumbers bytes.Buffer |  | ||||||
| 	var codeLines bytes.Buffer |  | ||||||
| 
 | 
 | ||||||
| 	var i = 0 | 	var i = 0 | ||||||
| 	for pi, part := range blameParts { | 	var commitCnt = 0 | ||||||
|  | 	for _, part := range blameParts { | ||||||
| 		for index, line := range part.Lines { | 		for index, line := range part.Lines { | ||||||
| 			i++ | 			i++ | ||||||
| 			lines = append(lines, line) | 			lines = append(lines, line) | ||||||
| 
 | 
 | ||||||
| 			var attr = "" | 			br := &blameRow{ | ||||||
| 			if len(part.Lines)-1 == index && len(blameParts)-1 != pi { | 				RowNumber: i, | ||||||
| 				attr = " bottom-line" |  | ||||||
| 			} | 			} | ||||||
|  | 
 | ||||||
| 			commit := commitNames[part.Sha] | 			commit := commitNames[part.Sha] | ||||||
|  | 			previousSha := previousCommits[part.Sha] | ||||||
| 			if index == 0 { | 			if index == 0 { | ||||||
|  | 				// Count commit number
 | ||||||
|  | 				commitCnt++ | ||||||
|  | 
 | ||||||
| 				// User avatar image
 | 				// User avatar image
 | ||||||
| 				commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Data["Lang"].(string)) | 				commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Data["Lang"].(string)) | ||||||
| 
 | 
 | ||||||
|  | @ -219,16 +238,14 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m | ||||||
| 					avatar = string(templates.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "mr-3")) | 					avatar = string(templates.AvatarByEmail(commit.Author.Email, commit.Author.Name, 18, "mr-3")) | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				commitInfo.WriteString(fmt.Sprintf(`<div class="blame-info%s"><div class="blame-data"><div class="blame-avatar">%s</div><div class="blame-message"><a href="%s/commit/%s" title="%[5]s">%[5]s</a></div><div class="blame-time">%s</div></div></div>`, attr, avatar, repoLink, part.Sha, html.EscapeString(commit.CommitMessage), commitSince)) | 				br.Avatar = gotemplate.HTML(avatar) | ||||||
| 			} else { | 				br.RepoLink = repoLink | ||||||
| 				commitInfo.WriteString(fmt.Sprintf(`<div class="blame-info%s">​</div>`, attr)) | 				br.PartSha = part.Sha | ||||||
| 			} | 				br.PreviousSha = previousSha | ||||||
| 
 | 				br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, previousSha, ctx.Repo.TreePath) | ||||||
| 			//Line number
 | 				br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, part.Sha) | ||||||
| 			if len(part.Lines)-1 == index && len(blameParts)-1 != pi { | 				br.CommitMessage = html.EscapeString(commit.CommitMessage) | ||||||
| 				lineNumbers.WriteString(fmt.Sprintf(`<span id="L%d" data-line-number="%d" class="bottom-line"></span>`, i, i)) | 				br.CommitSince = commitSince | ||||||
| 			} else { |  | ||||||
| 				lineNumbers.WriteString(fmt.Sprintf(`<span id="L%d" data-line-number="%d"></span>`, i, i)) |  | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if i != len(lines)-1 { | 			if i != len(lines)-1 { | ||||||
|  | @ -236,16 +253,12 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m | ||||||
| 			} | 			} | ||||||
| 			fileName := fmt.Sprintf("%v", ctx.Data["FileName"]) | 			fileName := fmt.Sprintf("%v", ctx.Data["FileName"]) | ||||||
| 			line = highlight.Code(fileName, line) | 			line = highlight.Code(fileName, line) | ||||||
| 			line = `<code class="code-inner">` + line + `</code>` | 
 | ||||||
| 			if len(part.Lines)-1 == index && len(blameParts)-1 != pi { | 			br.Code = gotemplate.HTML(line) | ||||||
| 				codeLines.WriteString(fmt.Sprintf(`<li class="L%d bottom-line" rel="L%d">%s</li>`, i, i, line)) | 			rows = append(rows, br) | ||||||
| 			} else { |  | ||||||
| 				codeLines.WriteString(fmt.Sprintf(`<li class="L%d" rel="L%d">%s</li>`, i, i, line)) |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ctx.Data["BlameContent"] = gotemplate.HTML(codeLines.String()) | 	ctx.Data["BlameRows"] = rows | ||||||
| 	ctx.Data["BlameCommitInfo"] = gotemplate.HTML(commitInfo.String()) | 	ctx.Data["CommitCnt"] = commitCnt | ||||||
| 	ctx.Data["BlameLineNums"] = gotemplate.HTML(lineNumbers.String()) |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -23,11 +23,40 @@ | ||||||
| 		<div class="file-view code-view"> | 		<div class="file-view code-view"> | ||||||
| 			<table> | 			<table> | ||||||
| 				<tbody> | 				<tbody> | ||||||
| 					<tr> | 					{{range $row := .BlameRows}} | ||||||
| 						<td class="lines-commit">{{.BlameCommitInfo}}</td> | 						<tr class="{{if and (gt $.CommitCnt 1) ($row.CommitMessage)}}top-line-blame{{end}}"> | ||||||
| 						<td class="lines-num">{{.BlameLineNums}}</td> | 							<td class="lines-commit"> | ||||||
| 						<td class="lines-code"><code class="chroma"><ol class="linenums">{{.BlameContent}}</ol></code></td> | 								<div class="blame-info"> | ||||||
|  | 									<div class="blame-data"> | ||||||
|  | 										<div class="blame-avatar"> | ||||||
|  | 											{{$row.Avatar}} | ||||||
|  | 										</div> | ||||||
|  | 										<div class="blame-message"> | ||||||
|  | 											<a href="{{$row.CommitURL}}" title="{{$row.CommitMessage}}"> | ||||||
|  | 												{{$row.CommitMessage}} | ||||||
|  | 											</a> | ||||||
|  | 										</div> | ||||||
|  | 										<div class="blame-time"> | ||||||
|  | 											{{$row.CommitSince}} | ||||||
|  | 										</div> | ||||||
|  | 									</div> | ||||||
|  | 								</div> | ||||||
|  | 							</td> | ||||||
|  | 							<td class="lines-blame-btn"> | ||||||
|  | 								{{if $row.PreviousSha}} | ||||||
|  | 									<a href="{{$row.PreviousShaURL}}" class="poping up" data-content='{{$.i18n.Tr "repo.blame_prior"}}' data-variation="tiny inverted"> | ||||||
|  | 										{{svg "octicon-versions"}} | ||||||
|  | 									</a> | ||||||
|  | 								{{end}} | ||||||
|  | 							</td> | ||||||
|  | 							<td class="lines-num"> | ||||||
|  | 								<span id="L{{$row.RowNumber}}" data-line-number="{{$row.RowNumber}}"></span> | ||||||
|  | 							</td> | ||||||
|  | 							<td rel="L{{$row.RowNumber}}" rel="L{{$row.RowNumber}}" class="lines-code blame-code chroma"> | ||||||
|  | 								<code class="code-inner pl-3">{{$row.Code}}</code> | ||||||
|  | 							</td> | ||||||
| 						</tr> | 						</tr> | ||||||
|  | 					{{end}} | ||||||
| 				</tbody> | 				</tbody> | ||||||
| 			</table> | 			</table> | ||||||
| 		</div> | 		</div> | ||||||
|  |  | ||||||
|  | @ -2283,20 +2283,24 @@ function initCodeView() { | ||||||
|       const $select = $(this); |       const $select = $(this); | ||||||
|       let $list; |       let $list; | ||||||
|       if ($('div.blame').length) { |       if ($('div.blame').length) { | ||||||
|         $list = $('.code-view td.lines-code li'); |         $list = $('.code-view td.lines-code.blame-code'); | ||||||
|       } else { |       } else { | ||||||
|         $list = $('.code-view td.lines-code'); |         $list = $('.code-view td.lines-code'); | ||||||
|       } |       } | ||||||
|       selectRange($list, $list.filter(`[rel=${$select.attr('id')}]`), (e.shiftKey ? $list.filter('.active').eq(0) : null)); |       selectRange($list, $list.filter(`[rel=${$select.attr('id')}]`), (e.shiftKey ? $list.filter('.active').eq(0) : null)); | ||||||
|       deSelect(); |       deSelect(); | ||||||
|  | 
 | ||||||
|  |       // show code view menu marker (don't show in blame page)
 | ||||||
|  |       if ($('div.blame').length === 0) { | ||||||
|         showLineButton(); |         showLineButton(); | ||||||
|  |       } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     $(window).on('hashchange', () => { |     $(window).on('hashchange', () => { | ||||||
|       let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/); |       let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/); | ||||||
|       let $list; |       let $list; | ||||||
|       if ($('div.blame').length) { |       if ($('div.blame').length) { | ||||||
|         $list = $('.code-view td.lines-code li'); |         $list = $('.code-view td.lines-code.blame-code'); | ||||||
|       } else { |       } else { | ||||||
|         $list = $('.code-view td.lines-code'); |         $list = $('.code-view td.lines-code'); | ||||||
|       } |       } | ||||||
|  | @ -2304,7 +2308,12 @@ function initCodeView() { | ||||||
|       if (m) { |       if (m) { | ||||||
|         $first = $list.filter(`[rel=${m[1]}]`); |         $first = $list.filter(`[rel=${m[1]}]`); | ||||||
|         selectRange($list, $first, $list.filter(`[rel=${m[2]}]`)); |         selectRange($list, $first, $list.filter(`[rel=${m[2]}]`)); | ||||||
|  | 
 | ||||||
|  |         // show code view menu marker (don't show in blame page)
 | ||||||
|  |         if ($('div.blame').length === 0) { | ||||||
|           showLineButton(); |           showLineButton(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         $('html, body').scrollTop($first.offset().top - 200); |         $('html, body').scrollTop($first.offset().top - 200); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|  | @ -2312,7 +2321,12 @@ function initCodeView() { | ||||||
|       if (m) { |       if (m) { | ||||||
|         $first = $list.filter(`[rel=L${m[2]}]`); |         $first = $list.filter(`[rel=L${m[2]}]`); | ||||||
|         selectRange($list, $first); |         selectRange($list, $first); | ||||||
|  | 
 | ||||||
|  |         // show code view menu marker (don't show in blame page)
 | ||||||
|  |         if ($('div.blame').length === 0) { | ||||||
|           showLineButton(); |           showLineButton(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         $('html, body').scrollTop($first.offset().top - 200); |         $('html, body').scrollTop($first.offset().top - 200); | ||||||
|       } |       } | ||||||
|     }).trigger('hashchange'); |     }).trigger('hashchange'); | ||||||
|  | @ -2911,7 +2925,6 @@ function selectRange($list, $select, $from) { | ||||||
|       } else { |       } else { | ||||||
|         $issue.attr('href', `${$issue.attr('href')}%23L${a}-L${b}`); |         $issue.attr('href', `${$issue.attr('href')}%23L${a}-L${b}`); | ||||||
|       } |       } | ||||||
| 
 |  | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -106,6 +106,7 @@ | ||||||
|   --color-markup-code-block: #00000010; |   --color-markup-code-block: #00000010; | ||||||
|   --color-button: #ffffff; |   --color-button: #ffffff; | ||||||
|   --color-code-bg: #ffffff; |   --color-code-bg: #ffffff; | ||||||
|  |   --color-code-sidebar-bg: #f5f5f5; | ||||||
|   --color-shadow: #00000030; |   --color-shadow: #00000030; | ||||||
|   --color-secondary-bg: #f4f4f4; |   --color-secondary-bg: #f4f4f4; | ||||||
|   --color-expand-button: #d8efff; |   --color-expand-button: #d8efff; | ||||||
|  | @ -1442,6 +1443,14 @@ a.ui.label:hover { | ||||||
|   margin-right: 0; |   margin-right: 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .lines-blame-btn { | ||||||
|  |   padding-left: 10px; | ||||||
|  |   padding-right: 10px; | ||||||
|  |   text-align: right !important; | ||||||
|  |   background-color: var(--color-code-sidebar-bg); | ||||||
|  |   width: 2%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .lines-num { | .lines-num { | ||||||
|   padding-left: 10px; |   padding-left: 10px; | ||||||
|   padding-right: 10px; |   padding-right: 10px; | ||||||
|  | @ -1507,7 +1516,7 @@ a.ui.label:hover { | ||||||
| 
 | 
 | ||||||
| .blame .lines-num { | .blame .lines-num { | ||||||
|   padding: 0 !important; |   padding: 0 !important; | ||||||
|   background-color: #f5f5f5; |   background-color: var(--color-code-sidebar-bg); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .blame .lines-code { | .blame .lines-code { | ||||||
|  | @ -1532,7 +1541,7 @@ a.ui.label:hover { | ||||||
|   vertical-align: top; |   vertical-align: top; | ||||||
|   color: #999999; |   color: #999999; | ||||||
|   padding: 0 !important; |   padding: 0 !important; | ||||||
|   background: #f5f5f5; |   background: var(--color-code-sidebar-bg); | ||||||
|   width: 1%; |   width: 1%; | ||||||
|   -moz-user-select: none; |   -moz-user-select: none; | ||||||
|   -ms-user-select: none; |   -ms-user-select: none; | ||||||
|  | @ -1574,6 +1583,10 @@ a.ui.label:hover { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .top-line-blame { | ||||||
|  |   border-top: 1px solid var(--color-secondary); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .lines-code, | .lines-code, | ||||||
| .lines-commit { | .lines-commit { | ||||||
|   .bottom-line { |   .bottom-line { | ||||||
|  |  | ||||||
|  | @ -101,6 +101,7 @@ | ||||||
|   --color-markup-code-block: #292d39; |   --color-markup-code-block: #292d39; | ||||||
|   --color-button: #353846; |   --color-button: #353846; | ||||||
|   --color-code-bg: #2a2e3a; |   --color-code-bg: #2a2e3a; | ||||||
|  |   --color-code-sidebar-bg: #2e323e; | ||||||
|   --color-shadow: #00000060; |   --color-shadow: #00000060; | ||||||
|   --color-secondary-bg: #2a2e3a; |   --color-secondary-bg: #2a2e3a; | ||||||
|   --color-text-focus: #fff; |   --color-text-focus: #fff; | ||||||
|  | @ -430,11 +431,6 @@ td.blob-hunk { | ||||||
|   background-color: #bbbbbb !important; |   background-color: #bbbbbb !important; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .lines-commit, |  | ||||||
| .blame .lines-num { |  | ||||||
|   background: #2e323e !important; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .lines-num { | .lines-num { | ||||||
|   color: var(--color-secondary-dark-6) !important; |   color: var(--color-secondary-dark-6) !important; | ||||||
|   border-color: var(--color-secondary) !important; |   border-color: var(--color-secondary) !important; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue