1
1
<script lang="ts">
2
- import {nextTick , defineComponent } from ' vue' ;
2
+ import {nextTick , defineComponent , computed , ref , type Ref } from ' vue' ;
3
3
import {SvgIcon } from ' ../svg.ts' ;
4
4
import {GET } from ' ../modules/fetch.ts' ;
5
5
import {fomanticQuery } from ' ../modules/fomantic/base.ts' ;
6
+ import DashboardRepoGroup from ' ./DashboardRepoGroup.vue' ;
6
7
7
8
const {appSubUrl, assetUrlPrefix, pageData} = window .config ;
8
9
9
- type CommitStatus = ' pending' | ' success' | ' error' | ' failure' | ' warning' | ' skipped' ;
10
+ export type CommitStatus = ' pending' | ' success' | ' error' | ' failure' | ' warning' | ' skipped' ;
10
11
11
- type CommitStatusMap = {
12
- [status in CommitStatus ]: {
12
+ export type CommitStatusMap = {
13
+ [status in CommitStatus ]: {
13
14
name: string ,
14
15
color: string ,
15
16
};
16
17
};
17
18
18
19
// make sure this matches templates/repo/commit_status.tmpl
19
- const commitStatus: CommitStatusMap = {
20
+ export const commitStatus: CommitStatusMap = {
20
21
pending: {name: ' octicon-dot-fill' , color: ' yellow' },
21
22
success: {name: ' octicon-check' , color: ' green' },
22
23
error: {name: ' gitea-exclamation' , color: ' red' },
23
24
failure: {name: ' octicon-x' , color: ' red' },
24
25
warning: {name: ' gitea-exclamation' , color: ' yellow' },
25
26
skipped: {name: ' octicon-skip' , color: ' grey' },
26
27
};
27
-
28
+ export type GroupMapType = {
29
+ repos: any []
30
+ subgroups: number []
31
+ id: number
32
+ [ k : string ]: any
33
+ }
28
34
export default defineComponent ({
29
- components: {SvgIcon },
35
+ components: {SvgIcon , DashboardRepoGroup },
36
+ provide() {
37
+ return {
38
+ expandedGroups: computed ({
39
+ get : () => {
40
+ return this .expandedGroups ;
41
+ },
42
+ set : (v ) => {
43
+ this .expandedGroups = v ;
44
+ },
45
+ }),
46
+ searchURL: this .searchURL ,
47
+ groups: computed ({
48
+ get : () => {
49
+ return this .groups ;
50
+ },
51
+ set : (v ) => {
52
+ this .groups = v ;
53
+ },
54
+ }),
55
+ repos: computed (() => this .computedRepos ),
56
+ loadedMap: computed ({
57
+ get : () => {
58
+ return this .loadedMap ;
59
+ },
60
+ set : (v ) => {
61
+ this .loadedMap = v ;
62
+ },
63
+ }),
64
+ orgName: this .organizationName ,
65
+ };
66
+ },
67
+ setup() {
68
+ const groups = ref (new Map <number , GroupMapType >());
69
+ const loadedMap = ref (new Map <number , boolean >([[0 , true ]]));
70
+ return {groupsRef: groups , loadedRef: loadedMap };
71
+ },
30
72
data() {
31
73
const params = new URLSearchParams (window .location .search );
32
74
const tab = params .get (' repo-search-tab' ) || ' repos' ;
@@ -39,6 +81,7 @@ export default defineComponent({
39
81
return {
40
82
tab ,
41
83
repos: [],
84
+ groupData: this .groupsRef ,
42
85
reposTotalCount: null ,
43
86
reposFilter ,
44
87
archivedFilter ,
@@ -78,15 +121,15 @@ export default defineComponent({
78
121
subUrl: appSubUrl ,
79
122
... pageData .dashboardRepoList ,
80
123
activeIndex: - 1 , // don't select anything at load, first cursor down will select
124
+ expandedGroupsRaw: [],
81
125
};
82
126
},
83
-
84
127
computed: {
85
128
showMoreReposLink() {
86
129
return this .repos .length > 0 && this .repos .length < this .counts [` ${this .reposFilter }:${this .archivedFilter }:${this .privateFilter } ` ];
87
130
},
88
131
searchURL() {
89
- return ` ${this .subUrl }/repo /search?sort=updated&order=desc&uid=${this .uid }&team_id=${this .teamId }&q=${this .searchQuery
132
+ return ` ${this .subUrl }/group /search?sort=updated&order=desc&uid=${this .uid }&team_id=${this .teamId }&q=${this .searchQuery
90
133
}&page=${this .page }&limit=${this .searchLimit }&mode=${this .repoTypes [this .reposFilter ].searchMode
91
134
}${this .archivedFilter === ' archived' ? ' &archived=true' : ' ' }${this .archivedFilter === ' unarchived' ? ' &archived=false' : ' '
92
135
}${this .privateFilter === ' private' ? ' &is_private=true' : ' ' }${this .privateFilter === ' public' ? ' &is_private=false' : ' '
@@ -107,6 +150,38 @@ export default defineComponent({
107
150
checkboxPrivateFilterProps() {
108
151
return {checked: this .privateFilter === ' private' , indeterminate: this .privateFilter === ' both' };
109
152
},
153
+ expandedGroups: {
154
+ get() {
155
+ return this .expandedGroupsRaw ;
156
+ },
157
+ set(val : number []) {
158
+ this .expandedGroupsRaw = val ;
159
+ },
160
+ },
161
+ groups: {
162
+ get() {
163
+ return this .groupData ;
164
+ },
165
+ set(v : Map <number , GroupMapType >) {
166
+ for (const [k, val] of v ) {
167
+ this .groupData .set (k , val );
168
+ }
169
+ },
170
+ },
171
+ computedRepos() {
172
+ return this .repos ;
173
+ },
174
+ root() {
175
+ return [... (this .groups .get (0 )?.subgroups ?? []), ... this .repos ];
176
+ },
177
+ loadedMap: {
178
+ get() {
179
+ return this .loadedRef ;
180
+ },
181
+ set(v : Ref <Map <number , boolean >>) {
182
+ this .loadedRef = v ;
183
+ },
184
+ },
110
185
},
111
186
112
187
mounted() {
@@ -136,6 +211,9 @@ export default defineComponent({
136
211
changeReposFilter(filter : string ) {
137
212
this .reposFilter = filter ;
138
213
this .repos = [];
214
+ this .groups = new Map ();
215
+ this .loadedMap = new Map ();
216
+ this .expandedGroupsRaw = [];
139
217
this .page = 1 ;
140
218
this .counts [` ${filter }:${this .archivedFilter }:${this .privateFilter } ` ] = 0 ;
141
219
this .searchRepos ();
@@ -212,6 +290,8 @@ export default defineComponent({
212
290
}
213
291
this .page = 1 ;
214
292
this .repos = [];
293
+ this .groups = new Map ();
294
+ this .loadedMap = new Map ();
215
295
this .counts [` ${this .reposFilter }:${this .archivedFilter }:${this .privateFilter } ` ] = 0 ;
216
296
this .searchRepos ();
217
297
},
@@ -227,6 +307,8 @@ export default defineComponent({
227
307
this .page = 1 ;
228
308
}
229
309
this .repos = [];
310
+ this .groups = new Map ();
311
+ this .loadedMap = new Map ();
230
312
this .counts [` ${this .reposFilter }:${this .archivedFilter }:${this .privateFilter } ` ] = 0 ;
231
313
await this .searchRepos ();
232
314
},
@@ -235,7 +317,7 @@ export default defineComponent({
235
317
this .isLoading = true ;
236
318
237
319
const searchedMode = this .repoTypes [this .reposFilter ].searchMode ;
238
- const searchedURL = this .searchURL ;
320
+ const searchedURL = ` ${ this .searchURL }&group_id=-1 ` ;
239
321
const searchedQuery = this .searchQuery ;
240
322
241
323
let response, json;
@@ -257,22 +339,40 @@ export default defineComponent({
257
339
response = await GET (searchedURL );
258
340
json = await response .json ();
259
341
} catch {
260
- if (searchedURL === this .searchURL ) {
342
+ if (searchedURL . startsWith ( this .searchURL ) ) {
261
343
this .isLoading = false ;
262
344
}
263
345
return ;
264
346
}
265
347
266
- if (searchedURL === this .searchURL ) {
267
- this .repos = json .data .map ((webSearchRepo : any ) => {
348
+ if (searchedURL . startsWith ( this .searchURL ) ) {
349
+ this .repos = json .data .repos . map ((webSearchRepo : any ) => {
268
350
return {
269
351
... webSearchRepo .repository ,
270
352
latest_commit_status_state: webSearchRepo .latest_commit_status ?.State , // if latest_commit_status is null, it means there is no commit status
271
353
latest_commit_status_state_link: webSearchRepo .latest_commit_status ?.TargetURL ,
272
354
locale_latest_commit_status_state: webSearchRepo .locale_latest_commit_status ,
273
355
};
274
356
});
275
- const count = Number (response .headers .get (' X-Total-Count' ));
357
+ this .groups .set (0 , {
358
+ repos: this .repos .filter ((a : any ) => ! a .group_id ),
359
+ subgroups: json .data .subgroups .map ((g : any ) => {
360
+ return g .group .id ;
361
+ }),
362
+ data: {},
363
+ });
364
+ for (const g of json .data .subgroups ) {
365
+ this .groups .set (g .group .id , {
366
+ subgroups: g .subgroups .map ((h : any ) => h .group .id ),
367
+ repos: g .repos ,
368
+ ... g .group ,
369
+ latest_commit_status_state: g .latest_commit_status ?.State , // if latest_commit_status is null, it means there is no commit status
370
+ latest_commit_status_state_link: g .latest_commit_status ?.TargetURL ,
371
+ locale_latest_commit_status_state: g .locale_latest_commit_status ,
372
+ id: g .group .id ,
373
+ });
374
+ }
375
+ const count = this .repos .length ;
276
376
if (searchedQuery === ' ' && searchedMode === ' ' && this .archivedFilter === ' both' ) {
277
377
this .reposTotalCount = count ;
278
378
}
@@ -372,7 +472,7 @@ export default defineComponent({
372
472
<div v-else class =" ui attached segment repos-search" >
373
473
<div class =" ui small fluid action left icon input" >
374
474
<input type =" search" spellcheck =" false" maxlength =" 255" @input =" changeReposFilter(reposFilter)" v-model =" searchQuery" ref =" search" @keydown =" reposFilterKeyControl" :placeholder =" textSearchRepos" >
375
- <i class =" icon loading-icon-3px" :class =" {'is-loading': isLoading}" ><svg-icon name =" octicon-search" :size =" 16" /></i >
475
+ <i class =" icon loading-icon-3px" :class =" {'is-loading': isLoading}" ><svg-icon name =" octicon-search" :size =" 16" /></i >
376
476
<div class =" ui dropdown icon button" :title =" textFilter" >
377
477
<svg-icon name =" octicon-filter" :size =" 16" />
378
478
<div class =" menu" >
@@ -401,69 +501,55 @@ export default defineComponent({
401
501
</div >
402
502
<overflow-menu class =" ui secondary pointing tabular borderless menu repos-filter" >
403
503
<div class =" overflow-menu-items tw-justify-center" >
404
- <a class =" item" tabindex =" 0" :class =" {active: reposFilter === 'all'}" @click =" changeReposFilter('all')" >
504
+ <a class =" item" tabindex =" 0" :class =" {active: reposFilter === 'all'}" @click =" changeReposFilter('all')" >
405
505
{{ textAll }}
406
506
<div v-show =" reposFilter === 'all'" class =" ui circular mini grey label" >{{ repoTypeCount }}</div >
407
507
</a >
408
- <a class =" item" tabindex =" 0" :class =" {active: reposFilter === 'sources'}" @click =" changeReposFilter('sources')" >
508
+ <a class =" item" tabindex =" 0" :class =" {active: reposFilter === 'sources'}" @click =" changeReposFilter('sources')" >
409
509
{{ textSources }}
410
510
<div v-show =" reposFilter === 'sources'" class =" ui circular mini grey label" >{{ repoTypeCount }}</div >
411
511
</a >
412
- <a class =" item" tabindex =" 0" :class =" {active: reposFilter === 'forks'}" @click =" changeReposFilter('forks')" >
512
+ <a class =" item" tabindex =" 0" :class =" {active: reposFilter === 'forks'}" @click =" changeReposFilter('forks')" >
413
513
{{ textForks }}
414
514
<div v-show =" reposFilter === 'forks'" class =" ui circular mini grey label" >{{ repoTypeCount }}</div >
415
515
</a >
416
- <a class =" item" tabindex =" 0" :class =" {active: reposFilter === 'mirrors'}" @click =" changeReposFilter('mirrors')" v-if =" isMirrorsEnabled" >
516
+ <a class =" item" tabindex =" 0" :class =" {active: reposFilter === 'mirrors'}" @click =" changeReposFilter('mirrors')" v-if =" isMirrorsEnabled" >
417
517
{{ textMirrors }}
418
518
<div v-show =" reposFilter === 'mirrors'" class =" ui circular mini grey label" >{{ repoTypeCount }}</div >
419
519
</a >
420
- <a class =" item" tabindex =" 0" :class =" {active: reposFilter === 'collaborative'}" @click =" changeReposFilter('collaborative')" >
520
+ <a class =" item" tabindex =" 0" :class =" {active: reposFilter === 'collaborative'}" @click =" changeReposFilter('collaborative')" >
421
521
{{ textCollaborative }}
422
522
<div v-show =" reposFilter === 'collaborative'" class =" ui circular mini grey label" >{{ repoTypeCount }}</div >
423
523
</a >
424
524
</div >
425
525
</overflow-menu >
426
526
</div >
427
527
<div v-if =" repos.length" class =" ui attached table segment tw-rounded-b" >
428
- <ul class =" repo-owner-name-list" >
429
- <li class =" tw-flex tw-items-center tw-py-2" v-for =" (repo, index) in repos" :class =" {'active': index === activeIndex}" :key =" repo.id" >
430
- <a class =" repo-list-link muted" :href =" repo.link" >
431
- <svg-icon :name =" repoIcon(repo)" :size =" 16" class =" repo-list-icon" />
432
- <div class =" text truncate" >{{ repo.full_name }}</div >
433
- <div v-if =" repo.archived" >
434
- <svg-icon name =" octicon-archive" :size =" 16" />
435
- </div >
436
- </a >
437
- <a class =" tw-flex tw-items-center" v-if =" repo.latest_commit_status_state" :href =" repo.latest_commit_status_state_link || null" :data-tooltip-content =" repo.locale_latest_commit_status_state" >
438
- <!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl -->
439
- <svg-icon :name =" statusIcon(repo.latest_commit_status_state)" :class =" 'tw-ml-2 commit-status icon text ' + statusColor(repo.latest_commit_status_state)" :size =" 16" />
440
- </a >
441
- </li >
442
- </ul >
528
+ <dashboard-repo-group :items =" root" :depth =" 1" :cur-group =" 0" @load-changed =" (nv: boolean) => (isLoading = nv)" />
443
529
<div v-if =" showMoreReposLink" class =" tw-text-center" >
444
530
<div class =" divider tw-my-0" />
445
531
<div class =" ui borderless pagination menu narrow tw-my-2" >
446
532
<a
447
- class =" item navigation tw-py-1" :class =" {'disabled': page === 1}"
533
+ class =" item navigation tw-py-1" :class =" {'disabled': page === 1}"
448
534
@click =" changePage(1)" :title =" textFirstPage"
449
535
>
450
536
<svg-icon name =" gitea-double-chevron-left" :size =" 16" class =" tw-mr-1" />
451
537
</a >
452
538
<a
453
- class =" item navigation tw-py-1" :class =" {'disabled': page === 1}"
539
+ class =" item navigation tw-py-1" :class =" {'disabled': page === 1}"
454
540
@click =" changePage(page - 1)" :title =" textPreviousPage"
455
541
>
456
542
<svg-icon name =" octicon-chevron-left" :size =" 16" class =" tw-mr-1" />
457
543
</a >
458
544
<a class =" active item tw-py-1" >{{ page }}</a >
459
545
<a
460
- class =" item navigation" :class =" {'disabled': page === finalPage}"
546
+ class =" item navigation" :class =" {'disabled': page === finalPage}"
461
547
@click =" changePage(page + 1)" :title =" textNextPage"
462
548
>
463
549
<svg-icon name =" octicon-chevron-right" :size =" 16" class =" tw-ml-1" />
464
550
</a >
465
551
<a
466
- class =" item navigation tw-py-1" :class =" {'disabled': page === finalPage}"
552
+ class =" item navigation tw-py-1" :class =" {'disabled': page === finalPage}"
467
553
@click =" changePage(finalPage)" :title =" textLastPage"
468
554
>
469
555
<svg-icon name =" gitea-double-chevron-right" :size =" 16" class =" tw-ml-1" />
@@ -496,7 +582,7 @@ export default defineComponent({
496
582
<div class =" text truncate" >{{ org.full_name ? `${org.full_name} (${org.name})` : org.name }}</div >
497
583
<div ><!-- div to prevent underline of label on hover -->
498
584
<span class =" ui tiny basic label" v-if =" org.org_visibility !== 'public'" >
499
- {{ org.org_visibility === 'limited' ? textOrgVisibilityLimited: textOrgVisibilityPrivate }}
585
+ {{ org.org_visibility === 'limited' ? textOrgVisibilityLimited: textOrgVisibilityPrivate }}
500
586
</span >
501
587
</div >
502
588
</a >
0 commit comments