mirror of
https://github.com/go-gitea/gitea
synced 2025-02-02 17:37:46 +01:00
Improve implementation of diff-file-tree
This commit is contained in:
parent
145b583631
commit
3be6a1dc15
@ -448,12 +448,20 @@ func getCommitFileLineCount(commit *git.Commit, filePath string) int {
|
||||
return lineCount
|
||||
}
|
||||
|
||||
type FileTreeNode struct {
|
||||
IsFile bool
|
||||
Name string
|
||||
File *DiffFile
|
||||
Children []*FileTreeNode
|
||||
}
|
||||
|
||||
// Diff represents a difference between two git trees.
|
||||
type Diff struct {
|
||||
Start, End string
|
||||
NumFiles int
|
||||
TotalAddition, TotalDeletion int
|
||||
Files []*DiffFile
|
||||
FileTree []*FileTreeNode
|
||||
IsIncomplete bool
|
||||
NumViewedFiles int // user-specific
|
||||
}
|
||||
@ -1212,6 +1220,8 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
|
||||
}
|
||||
}
|
||||
|
||||
diff.FileTree = buildTree(diff.Files)
|
||||
|
||||
if opts.FileOnly {
|
||||
return diff, nil
|
||||
}
|
||||
@ -1384,3 +1394,65 @@ func GetWhitespaceFlag(whitespaceBehavior string) git.TrustedCmdArgs {
|
||||
log.Warn("unknown whitespace behavior: %q, default to 'show-all'", whitespaceBehavior)
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildTree(files []*DiffFile) []*FileTreeNode {
|
||||
result := make(map[string]*FileTreeNode)
|
||||
for _, file := range files {
|
||||
splits := strings.Split(file.Name, "/")
|
||||
currentNode := &FileTreeNode{Name: splits[0], IsFile: false}
|
||||
if _, exists := result[splits[0]]; !exists {
|
||||
result[splits[0]] = currentNode
|
||||
} else {
|
||||
currentNode = result[splits[0]]
|
||||
}
|
||||
|
||||
parent := currentNode
|
||||
for _, split := range splits[1:] {
|
||||
found := false
|
||||
for _, child := range parent.Children {
|
||||
if child.Name == split {
|
||||
parent = child
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
newNode := &FileTreeNode{Name: split, IsFile: false}
|
||||
parent.Children = append(parent.Children, newNode)
|
||||
parent = newNode
|
||||
}
|
||||
}
|
||||
|
||||
lastNode := parent
|
||||
lastNode.IsFile = true
|
||||
lastNode.File = file
|
||||
}
|
||||
|
||||
var roots []*FileTreeNode
|
||||
for _, node := range result {
|
||||
if len(node.Children) > 0 {
|
||||
mergedNode := mergeSingleChildDirs(node)
|
||||
roots = append(roots, mergedNode)
|
||||
}
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
func mergeSingleChildDirs(node *FileTreeNode) *FileTreeNode {
|
||||
if len(node.Children) == 1 && !node.Children[0].IsFile {
|
||||
merged := &FileTreeNode{
|
||||
Name: fmt.Sprintf("%s/%s", node.Name, node.Children[0].Name),
|
||||
Children: node.Children[0].Children,
|
||||
IsFile: node.Children[0].IsFile,
|
||||
File: node.Children[0].File,
|
||||
}
|
||||
if merged.File != nil {
|
||||
merged.IsFile = true
|
||||
}
|
||||
return merged
|
||||
}
|
||||
for _, child := range node.Children {
|
||||
mergeSingleChildDirs(child)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
@ -61,35 +61,29 @@
|
||||
</div>
|
||||
{{end}}
|
||||
<script id="diff-data-script" type="module">
|
||||
const diffDataFiles = [{{range $i, $file := .Diff.Files}}{Name:"{{$file.Name}}",NameHash:"{{$file.NameHash}}",Type:{{$file.Type}},IsBin:{{$file.IsBin}},Addition:{{$file.Addition}},Deletion:{{$file.Deletion}},IsViewed:{{$file.IsViewed}}},{{end}}];
|
||||
const diffData = {
|
||||
isIncomplete: {{.Diff.IsIncomplete}},
|
||||
tooManyFilesMessage: "{{ctx.Locale.Tr "repo.diff.too_many_files"}}",
|
||||
binaryFileMessage: "{{ctx.Locale.Tr "repo.diff.bin"}}",
|
||||
showMoreMessage: "{{ctx.Locale.Tr "repo.diff.show_more"}}",
|
||||
statisticsMessage: "{{ctx.Locale.Tr "repo.diff.stats_desc_file"}}",
|
||||
linkLoadMore: "?skip-to={{.Diff.End}}&file-only=true",
|
||||
};
|
||||
|
||||
// for first time loading, the diffFileInfo is a plain object
|
||||
// after the Vue component is mounted, the diffFileInfo is a reactive object
|
||||
// keep in mind that this script block would be executed many times when loading more files, by "loadMoreFiles"
|
||||
let diffFileInfo = window.config.pageData.diffFileInfo || {
|
||||
files:[],
|
||||
fileTreeIsVisible: false,
|
||||
fileListIsVisible: false,
|
||||
isLoadingNewData: false,
|
||||
selectedItem: '',
|
||||
};
|
||||
diffFileInfo = Object.assign(diffFileInfo, diffData);
|
||||
diffFileInfo.files.push(...diffDataFiles);
|
||||
window.config.pageData.diffFileInfo = diffFileInfo;
|
||||
</script>
|
||||
<div id="diff-file-list"></div>
|
||||
{{end}}
|
||||
<div id="diff-container">
|
||||
{{if $showFileTree}}
|
||||
<div id="diff-file-tree" class="tw-hidden not-mobile"></div>
|
||||
{{template "repo/diff/file_tree" dict "Files" .Diff.FileTree "IsIncomplete" .Diff.IsIncomplete "LoadMoreLink" "?skip-to={{.Diff.End}}&file-only=true"}}
|
||||
<script>
|
||||
if (diffTreeVisible) document.getElementById('diff-file-tree').classList.remove('tw-hidden');
|
||||
</script>
|
||||
@ -228,7 +222,7 @@
|
||||
<div class="diff-file-box diff-box file-content tw-mt-2" id="diff-incomplete">
|
||||
<h4 class="ui top attached header tw-font-normal tw-flex tw-items-center tw-justify-between">
|
||||
{{ctx.Locale.Tr "repo.diff.too_many_files"}}
|
||||
<a class="ui basic tiny button" id="diff-show-more-files" data-href="?skip-to={{.Diff.End}}&file-only=true">{{ctx.Locale.Tr "repo.diff.show_more"}}</a>
|
||||
<a class="ui basic tiny button diff-show-more-files" data-href="?skip-to={{.Diff.End}}&file-only=true">{{ctx.Locale.Tr "repo.diff.show_more"}}</a>
|
||||
</h4>
|
||||
</div>
|
||||
{{end}}
|
||||
|
12
templates/repo/diff/file_tree.tmpl
Normal file
12
templates/repo/diff/file_tree.tmpl
Normal file
@ -0,0 +1,12 @@
|
||||
<div id="diff-file-tree" class="file-tree tw-hidden not-mobile">
|
||||
<div class="file-tree-items">
|
||||
{{range .Files}}
|
||||
{{template "repo/diff/file_tree_item" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .IsIncomplete}}
|
||||
<div class="tw-pt-1">
|
||||
<a class="ui basic tiny button diff-show-more-files" data-href="{{ .LoadMoreLink }}">{{ctx.Locale.Tr "repo.diff.show_more"}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
32
templates/repo/diff/file_tree_item.tmpl
Normal file
32
templates/repo/diff/file_tree_item.tmpl
Normal file
@ -0,0 +1,32 @@
|
||||
{{if .IsFile}}
|
||||
<a class="item-file {{if .File.IsViewed}} viewed {{end}}" title="{{ .Name }}" href="#diff-{{ .File.NameHash }}">
|
||||
<!-- file -->
|
||||
{{svg "octicon-file"}}
|
||||
<span class="gt-ellipsis tw-flex-1">{{ .Name }}</span>
|
||||
{{if eq .File.Type 1}}
|
||||
{{svg "octicon-diff-added" 16 "text green"}}
|
||||
{{else if eq .File.Type 2}}
|
||||
{{svg "octicon-diff-modified" 16 "text yellow"}}
|
||||
{{else if eq .File.Type 3}}
|
||||
{{svg "octicon-diff-removed" 16 "text red"}}
|
||||
{{else if eq .File.Type 4}}
|
||||
{{svg "octicon-diff-renamed" 16 "text teal"}}
|
||||
{{else if eq .File.Type 5}}
|
||||
{{svg "octicon-diff-renamed" 16 "text green"}}
|
||||
{{end}}
|
||||
</a>
|
||||
{{else}}
|
||||
<div class="item-directory" title="{{ .Name }}">
|
||||
<!-- directory -->
|
||||
{{svg "octicon-chevron-down"}}
|
||||
{{svg "octicon-file-directory-open-fill" 16 "text primary"}}
|
||||
<span class="gt-ellipsis">{{ .Name }}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and .Children (gt (len .Children) 0)}}
|
||||
<div class="sub-items">
|
||||
{{range .Children}}
|
||||
{{template "repo/diff/file_tree_item" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
@ -2377,7 +2377,7 @@ tbody.commit-list {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#diff-file-tree {
|
||||
.file-tree {
|
||||
flex: 0 0 20%;
|
||||
max-width: 380px;
|
||||
line-height: inherit;
|
||||
@ -2389,6 +2389,60 @@ tbody.commit-list {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.file-tree .file-tree-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.file-tree .file-tree-items a, a:hover {
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.file-tree .file-tree-items .sub-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
margin-left: 13px;
|
||||
border-left: 1px solid var(--color-secondary);
|
||||
}
|
||||
|
||||
.file-tree .file-tree-items .sub-items .item-file {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.file-tree .file-tree-items .item-file.selected {
|
||||
color: var(--color-text);
|
||||
background: var(--color-active);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.file-tree .file-tree-items .item-file.viewed {
|
||||
color: var(--color-text-light-3);
|
||||
}
|
||||
|
||||
.file-tree .file-tree-items .item-directory {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.file-tree .file-tree-items .item-file,
|
||||
.file-tree .file-tree-items .item-directory {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.file-tree .file-tree-items .item-file:hover,
|
||||
.file-tree .file-tree-items .item-directory:hover {
|
||||
color: var(--color-text);
|
||||
background: var(--color-hover);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui.message.unicode-escape-prompt {
|
||||
margin-bottom: 0;
|
||||
border-radius: 0;
|
||||
|
@ -1,13 +1,58 @@
|
||||
import {createApp} from 'vue';
|
||||
import DiffFileTree from '../components/DiffFileTree.vue';
|
||||
import {toggleElem} from '../utils/dom.ts';
|
||||
import {diffTreeStore} from '../modules/stores.ts';
|
||||
import {setFileFolding} from './file-fold.ts';
|
||||
import DiffFileList from '../components/DiffFileList.vue';
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'diff_file_tree_visible';
|
||||
|
||||
function hashChangeListener() {
|
||||
for (const el of document.querySelectorAll<HTMLAnchorElement>('.file-tree-items .item-file')) {
|
||||
el.classList.toggle('selected', el.hash === `${window.location.hash}`);
|
||||
}
|
||||
expandSelectedFile(window.location.hash);
|
||||
}
|
||||
|
||||
function expandSelectedFile(selectedItem) {
|
||||
// expand file if the selected file is folded
|
||||
if (selectedItem) {
|
||||
const box = document.querySelector(selectedItem);
|
||||
const folded = box?.getAttribute('data-folded') === 'true';
|
||||
if (folded) setFileFolding(box, box.querySelector('.fold-file'), false);
|
||||
}
|
||||
}
|
||||
|
||||
function updateState(visible) {
|
||||
const btn = document.querySelector('.diff-toggle-file-tree-button');
|
||||
const [toShow, toHide] = btn.querySelectorAll('.icon');
|
||||
const tree = document.querySelector('#diff-file-tree');
|
||||
const newTooltip = btn.getAttribute(visible ? 'data-hide-text' : 'data-show-text');
|
||||
btn.setAttribute('data-tooltip-content', newTooltip);
|
||||
toggleElem(tree, visible);
|
||||
toggleElem(toShow, !visible);
|
||||
toggleElem(toHide, visible);
|
||||
}
|
||||
|
||||
export function initDiffFileTree() {
|
||||
const el = document.querySelector('#diff-file-tree');
|
||||
if (!el) return;
|
||||
|
||||
const fileTreeView = createApp(DiffFileTree);
|
||||
fileTreeView.mount(el);
|
||||
const store = diffTreeStore();
|
||||
store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) !== 'false';
|
||||
document.querySelector('.diff-toggle-file-tree-button').addEventListener('click', () => {
|
||||
store.fileTreeIsVisible = !store.fileTreeIsVisible;
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible);
|
||||
updateState(store.fileTreeIsVisible);
|
||||
});
|
||||
|
||||
hashChangeListener();
|
||||
window.addEventListener('hashchange', hashChangeListener);
|
||||
|
||||
for (const el of document.querySelectorAll<HTMLInputElement>('.file-tree-items .item-directory')) {
|
||||
el.addEventListener('click', () => {
|
||||
toggleElem(el.nextElementSibling);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function initDiffFileList() {
|
||||
|
@ -166,7 +166,7 @@ function onShowMoreFiles() {
|
||||
}
|
||||
|
||||
export async function loadMoreFiles(url) {
|
||||
const target = document.querySelector('a#diff-show-more-files');
|
||||
const target = document.querySelector('a.diff-show-more-files');
|
||||
if (target?.classList.contains('disabled') || pageData.diffFileInfo.isLoadingNewData) {
|
||||
return;
|
||||
}
|
||||
@ -195,7 +195,7 @@ export async function loadMoreFiles(url) {
|
||||
}
|
||||
|
||||
function initRepoDiffShowMore() {
|
||||
$(document).on('click', 'a#diff-show-more-files', (e) => {
|
||||
$(document).on('click', 'a.diff-show-more-files', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const linkLoadMore = e.target.getAttribute('data-href');
|
||||
|
Loading…
x
Reference in New Issue
Block a user