Skip to content

Commit c1c5778

Browse files
committed
refactor
1 parent 69b8063 commit c1c5778

File tree

11 files changed

+183
-242
lines changed

11 files changed

+183
-242
lines changed

routers/web/repo/commit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ func Diff(ctx *context.Context) {
370370
return
371371
}
372372

373-
ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil)
373+
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, nil)
374374
}
375375

376376
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)

routers/web/repo/compare.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ func PrepareCompareDiff(
639639
return false
640640
}
641641

642-
ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil)
642+
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, nil)
643643
}
644644

645645
headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID)

routers/web/repo/pull.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -829,12 +829,12 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
829829
// This sort of sucks because we already fetch this when getting the diff
830830
review, err := pull_model.GetNewestReviewState(ctx, ctx.Doer.ID, issue.ID)
831831
if err == nil && review != nil && review.UpdatedFiles != nil {
832-
// If there wasn't an error and we have a review with updated files, use that
832+
// If there wasn't an error, and we have a review with updated files, use that
833833
filesViewedState = review.UpdatedFiles
834834
}
835835
}
836-
837-
ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, filesViewedState)
836+
// FIXME: filesViewedState is always nil?
837+
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, filesViewedState)
838838
}
839839

840840
ctx.Data["Diff"] = diff

routers/web/repo/treelist.go

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package repo
55

66
import (
77
"net/http"
8+
"strings"
89

910
pull_model "code.gitea.io/gitea/models/pull"
1011
"code.gitea.io/gitea/modules/base"
@@ -57,34 +58,72 @@ func isExcludedEntry(entry *git.TreeEntry) bool {
5758
return false
5859
}
5960

60-
type FileDiffFile struct {
61-
Name string
62-
NameHash string
63-
IsSubmodule bool
64-
IsViewed bool
65-
Status string
61+
// WebDiffFileItem is used by frontend, check the field names in frontend before changing
62+
type WebDiffFileItem struct {
63+
FullName string
64+
DisplayName string
65+
NameHash string
66+
DiffStatus string
67+
EntryMode string
68+
IsViewed bool
69+
Children []*WebDiffFileItem
70+
ViewedChildrenCount int
6671
}
6772

68-
// transformDiffTreeForUI transforms a DiffTree into a slice of FileDiffFile for UI rendering
69-
// it also takes a map of file names to their viewed state, which is used to mark files as viewed
70-
func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) []FileDiffFile {
71-
files := make([]FileDiffFile, 0, len(diffTree.Files))
73+
// WebDiffFileTree is used by frontend, check the field names in frontend before changing
74+
type WebDiffFileTree struct {
75+
TreeRoot WebDiffFileItem
76+
}
7277

78+
// transformDiffTreeForWeb transforms a gitdiff.DiffTree into a WebDiffFileTree for Web UI rendering
79+
// it also takes a map of file names to their viewed state, which is used to mark files as viewed
80+
// TODO: add some tests
81+
func transformDiffTreeForWeb(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) (dft WebDiffFileTree) {
82+
dirNodes := map[string]*WebDiffFileItem{"": &dft.TreeRoot}
7383
for _, file := range diffTree.Files {
74-
nameHash := git.HashFilePathForWebUI(file.HeadPath)
75-
isSubmodule := file.HeadMode == git.EntryModeCommit
76-
isViewed := filesViewedState[file.HeadPath] == pull_model.Viewed
77-
78-
files = append(files, FileDiffFile{
79-
Name: file.HeadPath,
80-
NameHash: nameHash,
81-
IsSubmodule: isSubmodule,
82-
IsViewed: isViewed,
83-
Status: file.Status,
84-
})
84+
item := &WebDiffFileItem{FullName: file.HeadPath, DiffStatus: file.Status}
85+
// FIXME: filesViewedState is always nil?
86+
item.IsViewed = filesViewedState[item.FullName] == pull_model.Viewed
87+
item.NameHash = git.HashFilePathForWebUI(item.FullName)
88+
89+
switch file.HeadMode {
90+
case git.EntryModeTree:
91+
item.EntryMode = "tree"
92+
case git.EntryModeCommit:
93+
item.EntryMode = "commit" // submodule
94+
default:
95+
// default to empty, and will be treated as "blob" file because there is no "symlink" support yet
96+
}
97+
98+
var parentPath string
99+
pos := strings.LastIndexByte(item.FullName, '/')
100+
if pos == -1 {
101+
item.DisplayName = item.FullName
102+
} else {
103+
parentPath = item.FullName[:pos]
104+
item.DisplayName = item.FullName[pos+1:]
105+
}
106+
parentNode, parentExists := dirNodes[parentPath]
107+
if !parentExists {
108+
parentNode = &dft.TreeRoot
109+
fields := strings.Split(parentPath, "/")
110+
for idx, field := range fields {
111+
nodePath := strings.Join(fields[:idx+1], "/")
112+
node, ok := dirNodes[nodePath]
113+
if !ok {
114+
node = &WebDiffFileItem{EntryMode: "tree", DisplayName: field, FullName: nodePath}
115+
dirNodes[nodePath] = node
116+
parentNode.Children = append(parentNode.Children, node)
117+
}
118+
parentNode = node
119+
}
120+
}
121+
parentNode.Children = append(parentNode.Children, item)
85122
}
86123

87-
return files
124+
// TODO: merge into one level if there is only one sub directory
125+
126+
return dft
88127
}
89128

90129
func TreeViewNodes(ctx *context.Context) {

web_src/js/components/DiffFileTree.vue

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
11
<script lang="ts" setup>
22
import DiffFileTreeItem from './DiffFileTreeItem.vue';
33
import {toggleElem} from '../utils/dom.ts';
4-
import {diffTreeStore} from '../modules/stores.ts';
4+
import {diffTreeStore} from '../modules/diff-file.ts';
55
import {setFileFolding} from '../features/file-fold.ts';
6-
import {computed, onMounted, onUnmounted} from 'vue';
7-
import {pathListToTree, mergeChildIfOnlyOneDir} from '../utils/filetree.ts';
6+
import {onMounted, onUnmounted} from 'vue';
87
98
const LOCAL_STORAGE_KEY = 'diff_file_tree_visible';
109
1110
const store = diffTreeStore();
1211
13-
const fileTree = computed(() => {
14-
const result = pathListToTree(store.files);
15-
mergeChildIfOnlyOneDir(result); // mutation
16-
return result;
17-
});
18-
1912
onMounted(() => {
2013
// Default to true if unset
2114
store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) !== 'false';
@@ -50,7 +43,7 @@ function toggleVisibility() {
5043
5144
function updateVisibility(visible: boolean) {
5245
store.fileTreeIsVisible = visible;
53-
localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible);
46+
localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible.toString());
5447
updateState(store.fileTreeIsVisible);
5548
}
5649
@@ -69,7 +62,7 @@ function updateState(visible: boolean) {
6962
<template>
7063
<div v-if="store.fileTreeIsVisible" class="diff-file-tree-items">
7164
<!-- only render the tree if we're visible. in many cases this is something that doesn't change very often -->
72-
<DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item"/>
65+
<DiffFileTreeItem v-for="item in store.diffFileTree.TreeRoot.Children" :key="item.FullName" :item="item"/>
7366
</div>
7467
</template>
7568

web_src/js/components/DiffFileTreeItem.vue

Lines changed: 28 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,65 @@
11
<script lang="ts" setup>
22
import {SvgIcon, type SvgName} from '../svg.ts';
3-
import {diffTreeStore} from '../modules/stores.ts';
4-
import {computed, nextTick, ref, watch} from 'vue';
5-
import type {Item, DirItem, File, FileStatus} from '../utils/filetree.ts';
3+
import {ref} from 'vue';
4+
import {type DiffStatus, type DiffTreeEntry, diffTreeStore} from '../modules/diff-file.ts';
65
7-
// ------------------------------------------------------------
8-
// Component Props
9-
// ------------------------------------------------------------
10-
const props = defineProps<{
11-
item: Item,
12-
setViewed?:(val: boolean) => void,
6+
defineProps<{
7+
item: DiffTreeEntry,
138
}>();
149
15-
// ------------------------------------------------------------
16-
// Reactive State & Refs
17-
// ------------------------------------------------------------
1810
const store = diffTreeStore();
19-
const count = ref(0);
20-
let pendingUpdate = 0;
21-
let pendingTimer: Promise<void> | undefined;
22-
// ------------------------------------------------------------
23-
// Batch Update Mechanism
24-
// ------------------------------------------------------------
25-
/**
26-
* Handles batched updates to count value
27-
* Prevents multiple updates within the same tick
28-
*/
29-
const setCount = (isViewed: boolean) => {
30-
pendingUpdate += (isViewed ? 1 : -1);
11+
const collapsed = ref(false);
3112
32-
if (pendingTimer === undefined) {
33-
pendingTimer = nextTick(() => {
34-
count.value = Math.max(0, count.value + pendingUpdate);
35-
pendingUpdate = 0;
36-
pendingTimer = undefined;
37-
});
38-
}
39-
};
40-
41-
// ------------------------------------------------------------
42-
// Computed Properties
43-
// ------------------------------------------------------------
44-
/**
45-
* Determines viewed state based on item type:
46-
* - Files: Directly use IsViewed property
47-
* - Directories: Compare children count with viewed count
48-
*/
49-
const isViewed = computed(() => {
50-
return props.item.isFile ? props.item.file.IsViewed : (props.item as DirItem).children.length === count.value;
51-
});
52-
// ------------------------------------------------------------
53-
// Watchers & Side Effects
54-
// ------------------------------------------------------------
55-
/**
56-
* Propagate viewed state changes to parent component
57-
* Using post flush to ensure DOM updates are complete
58-
*/
59-
watch(
60-
() => isViewed.value,
61-
(newVal) => {
62-
if (props.setViewed) {
63-
props.setViewed(newVal);
64-
}
65-
},
66-
{immediate: true, flush: 'post'},
67-
);
68-
69-
// ------------------------------------------------------------
70-
// Collapse Behavior Documentation
71-
// ------------------------------------------------------------
72-
/**
73-
* Collapse behavior rules:
74-
* - Viewed folders start collapsed initially
75-
* - Manual expand/collapse takes precedence over automatic behavior
76-
* - State persists after manual interaction
77-
*/
78-
const collapsed = ref(isViewed.value);
79-
80-
function getIconForDiffStatus(pType: FileStatus) {
81-
const diffTypes: Record<FileStatus, { name: SvgName, classes: Array<string> }> = {
13+
function getIconForDiffStatus(pType: DiffStatus) {
14+
const diffTypes: Record<DiffStatus, { name: SvgName, classes: Array<string> }> = {
8215
'added': {name: 'octicon-diff-added', classes: ['text', 'green']},
8316
'modified': {name: 'octicon-diff-modified', classes: ['text', 'yellow']},
8417
'deleted': {name: 'octicon-diff-removed', classes: ['text', 'red']},
8518
'renamed': {name: 'octicon-diff-renamed', classes: ['text', 'teal']},
8619
'copied': {name: 'octicon-diff-renamed', classes: ['text', 'green']},
8720
'typechange': {name: 'octicon-diff-modified', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok
8821
};
89-
return diffTypes[pType];
22+
return diffTypes[pType] ?? {name: 'octicon-blocked', classes: ['text', 'red']};
9023
}
9124
92-
function fileIcon(file: File) {
93-
if (file.IsSubmodule) {
25+
function entryIcon(entry: DiffTreeEntry) {
26+
if (entry.EntryMode === 'commit') {
9427
return 'octicon-file-submodule';
9528
}
9629
return 'octicon-file';
9730
}
9831
</script>
9932

10033
<template>
101-
<!--title instead of tooltip above as the tooltip needs too much work with the current methods, i.e. not being loaded or staying open for "too long"-->
102-
<a
103-
v-if="item.isFile" class="item-file"
104-
:class="{ 'selected': store.selectedItem === '#diff-' + item.file.NameHash, 'viewed': isViewed }"
105-
:title="item.name" :href="'#diff-' + item.file.NameHash"
106-
>
107-
<!-- file -->
108-
<SvgIcon :name="fileIcon(item.file)"/>
109-
<span class="gt-ellipsis tw-flex-1">{{ item.name }}</span>
110-
<SvgIcon
111-
:name="getIconForDiffStatus(item.file.Status).name"
112-
:class="getIconForDiffStatus(item.file.Status).classes"
113-
/>
114-
</a>
115-
116-
<template v-else-if="item.isFile === false">
117-
<div class="item-directory" :class="{ 'viewed': isViewed }" :title="item.name" @click.stop="collapsed = !collapsed">
34+
<template v-if="item.EntryMode === 'tree'">
35+
<div class="item-directory" :class="{ 'viewed': item.IsViewed }" :title="item.DisplayName" @click.stop="collapsed = !collapsed">
11836
<!-- directory -->
11937
<SvgIcon :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'"/>
12038
<SvgIcon
12139
class="text primary"
12240
:name="collapsed ? 'octicon-file-directory-fill' : 'octicon-file-directory-open-fill'"
12341
/>
124-
<span class="gt-ellipsis">{{ item.name }}</span>
42+
<span class="gt-ellipsis">{{ item.DisplayName }}</span>
12543
</div>
12644

12745
<div v-show="!collapsed" class="sub-items">
128-
<DiffFileTreeItem v-for="childItem in item.children" :key="childItem.name" :item="childItem" :set-viewed="setCount"/>
46+
<DiffFileTreeItem v-for="childItem in item.Children" :key="childItem.DisplayName" :item="childItem"/>
12947
</div>
13048
</template>
49+
<a
50+
v-else
51+
class="item-file" :class="{ 'selected': store.selectedItem === '#diff-' + item.NameHash, 'viewed': item.IsViewed }"
52+
:title="item.DisplayName" :href="'#diff-' + item.NameHash"
53+
>
54+
<!-- file -->
55+
<SvgIcon :name="entryIcon(item)"/>
56+
<span class="gt-ellipsis tw-flex-1">{{ item.DisplayName }}</span>
57+
<SvgIcon
58+
:name="getIconForDiffStatus(item.DiffStatus).name"
59+
:class="getIconForDiffStatus(item.DiffStatus).classes"
60+
/>
61+
</a>
62+
13163
</template>
13264
<style scoped>
13365
a,

web_src/js/features/file-fold.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {svg} from '../svg.ts';
55
// The fold arrow is the icon displayed on the upper left of the file box, especially intended for components having the 'fold-file' class.
66
// The file content box is the box that should be hidden or shown, especially intended for components having the 'file-content' class.
77
//
8-
export function setFileFolding(fileContentBox: HTMLElement, foldArrow: HTMLElement, newFold: boolean) {
8+
export function setFileFolding(fileContentBox: Element, foldArrow: HTMLElement, newFold: boolean) {
99
foldArrow.innerHTML = svg(`octicon-chevron-${newFold ? 'right' : 'down'}`, 18);
1010
fileContentBox.setAttribute('data-folded', String(newFold));
1111
if (newFold && fileContentBox.getBoundingClientRect().top < 0) {

web_src/js/features/pull-view-file.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {diffTreeStore} from '../modules/stores.ts';
1+
import {diffTreeStore, diffTreeStoreSetViewed} from '../modules/diff-file.ts';
22
import {setFileFolding} from './file-fold.ts';
33
import {POST} from '../modules/fetch.ts';
44

@@ -58,11 +58,8 @@ export function initViewedCheckboxListenerFor() {
5858

5959
const fileName = checkbox.getAttribute('name');
6060

61-
// check if the file is in our difftreestore and if we find it -> change the IsViewed status
62-
const fileInPageData = diffTreeStore().files.find((x: Record<string, any>) => x.Name === fileName);
63-
if (fileInPageData) {
64-
fileInPageData.IsViewed = this.checked;
65-
}
61+
// check if the file is in our diffTreeStore and if we find it -> change the IsViewed status
62+
diffTreeStoreSetViewed(fileName, this.checked);
6663

6764
// Unfortunately, actual forms cause too many problems, hence another approach is needed
6865
const files: Record<string, boolean> = {};

0 commit comments

Comments
 (0)