11<script  lang="ts">
2- import  {nextTick , defineComponent } from  ' vue' 
2+ import  {nextTick , defineComponent ,  computed ,  ref ,  type   Ref } from  ' vue' 
33import  {SvgIcon } from  ' ../svg.ts' 
44import  {GET } from  ' ../modules/fetch.ts' 
55import  {fomanticQuery } from  ' ../modules/fomantic/base.ts' 
6+ import  DashboardRepoGroup  from  ' ./DashboardRepoGroup.vue' 
67
78const =  window .config ;
89
9- type  CommitStatus  =  ' pending' |  ' success' |  ' error' |  ' failure' |  ' warning' |  ' skipped' 
10+ export   type  CommitStatus  =  ' pending' |  ' success' |  ' error' |  ' failure' |  ' warning' |  ' skipped' 
1011
11- type  CommitStatusMap  =  {
12-   [status  in  CommitStatus ]:  { 
12+ export   type  CommitStatusMap  =  {
13+   [  status  in  CommitStatus   ]:  { 
1314    name:  string , 
1415    color:  string , 
1516  }; 
1617}; 
1718
1819//  make sure this matches templates/repo/commit_status.tmpl
19- const :  CommitStatusMap  =  {
20+ export   const :  CommitStatusMap  =  {
2021  pending: {name: ' octicon-dot-fill' ' yellow'  
2122  success: {name: ' octicon-check' ' green'  
2223  error: {name: ' gitea-exclamation' ' red'  
2324  failure: {name: ' octicon-x' ' red'  
2425  warning: {name: ' gitea-exclamation' ' yellow'  
2526  skipped: {name: ' octicon-skip' ' grey'  
2627}; 
27- 
28+ export  type  GroupMapType  =  {
29+   repos:  any [] 
30+   subgroups:  number [] 
31+   id:  number  
32+   [ k : string  ]:  any  
33+ } 
2834export  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 =  ref (new  Map <number , GroupMapType >()); 
69+     const =  ref (new  Map <number , boolean >([[0 , true ]])); 
70+     return  {groupsRef: groups , loadedRef: loadedMap }; 
71+   }, 
3072  data() { 
3173    const =  new  URLSearchParams (window .location .search ); 
3274    const =  params .get (' repo-search-tab' ||  ' repos'  
@@ -39,6 +81,7 @@ export default defineComponent({
3981    return  { 
4082      tab , 
4183      repos: [], 
84+       groupData: this .groupsRef , 
4285      reposTotalCount: null , 
4386      reposFilter , 
4487      archivedFilter , 
@@ -78,15 +121,15 @@ export default defineComponent({
78121      subUrl: appSubUrl , 
79122      ... pageData .dashboardRepoList , 
80123      activeIndex: - 1 , //  don't select anything at load, first cursor down will select 
124+       expandedGroupsRaw: [], 
81125    }; 
82126  }, 
83- 
84127  computed: { 
85128    showMoreReposLink() { 
86129      return  this .repos .length  >  0  &&  this .repos .length  <  this .counts [` ${this .reposFilter }:${this .archivedFilter }:${this .privateFilter } ` ]; 
87130    }, 
88131    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   
90133      }&page=${this .page }&limit=${this .searchLimit }&mode=${this .repoTypes [this .reposFilter ].searchMode  
91134      }${this .archivedFilter  ===  ' archived' ?  ' &archived=true' :  ' ' this .archivedFilter  ===  ' unarchived' ?  ' &archived=false' :  ' '  
92135      }${this .privateFilter  ===  ' private' ?  ' &is_private=true' :  ' ' this .privateFilter  ===  ' public' ?  ' &is_private=false' :  ' '  
@@ -107,6 +150,38 @@ export default defineComponent({
107150    checkboxPrivateFilterProps() { 
108151      return  {checked: this .privateFilter  ===  ' private' this .privateFilter  ===  ' both'  
109152    }, 
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 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+     }, 
110185  }, 
111186
112187  mounted() { 
@@ -136,6 +211,9 @@ export default defineComponent({
136211    changeReposFilter(filter :  string ) { 
137212      this .reposFilter  =  filter ; 
138213      this .repos  =  []; 
214+       this .groups  =  new  Map (); 
215+       this .loadedMap  =  new  Map (); 
216+       this .expandedGroupsRaw  =  []; 
139217      this .page  =  1 ; 
140218      this .counts [` ${filter }:${this .archivedFilter }:${this .privateFilter } ` ] =  0 ; 
141219      this .searchRepos (); 
@@ -212,6 +290,8 @@ export default defineComponent({
212290      } 
213291      this .page  =  1 ; 
214292      this .repos  =  []; 
293+       this .groups  =  new  Map (); 
294+       this .loadedMap  =  new  Map (); 
215295      this .counts [` ${this .reposFilter }:${this .archivedFilter }:${this .privateFilter } ` ] =  0 ; 
216296      this .searchRepos (); 
217297    }, 
@@ -227,6 +307,8 @@ export default defineComponent({
227307        this .page  =  1 ; 
228308      } 
229309      this .repos  =  []; 
310+       this .groups  =  new  Map (); 
311+       this .loadedMap  =  new  Map (); 
230312      this .counts [` ${this .reposFilter }:${this .archivedFilter }:${this .privateFilter } ` ] =  0 ; 
231313      await  this .searchRepos (); 
232314    }, 
@@ -235,7 +317,7 @@ export default defineComponent({
235317      this .isLoading  =  true ; 
236318
237319      const =  this .repoTypes [this .reposFilter ].searchMode ; 
238-       const =  this .searchURL ; 
320+       const =  ` ${ this .searchURL }&group_id=-1 ` ; 
239321      const =  this .searchQuery ; 
240322
241323      let  response, json; 
@@ -257,22 +339,40 @@ export default defineComponent({
257339        response  =  await  GET (searchedURL ); 
258340        json  =  await  response .json (); 
259341      } catch  { 
260-         if  (searchedURL   ===   this .searchURL ) { 
342+         if  (searchedURL . startsWith ( this .searchURL ) ) { 
261343          this .isLoading  =  false ; 
262344        } 
263345        return ; 
264346      } 
265347
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 ) =>  { 
268350          return  { 
269351            ... webSearchRepo .repository , 
270352            latest_commit_status_state: webSearchRepo .latest_commit_status ?.State , //  if latest_commit_status is null, it means there is no commit status 
271353            latest_commit_status_state_link: webSearchRepo .latest_commit_status ?.TargetURL , 
272354            locale_latest_commit_status_state: webSearchRepo .locale_latest_commit_status , 
273355          }; 
274356        }); 
275-         const =  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 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 =  this .repos .length ; 
276376        if  (searchedQuery  ===  ' ' &&  searchedMode  ===  ' ' &&  this .archivedFilter  ===  ' both'  
277377          this .reposTotalCount  =  count ; 
278378        } 
@@ -372,7 +472,7 @@ export default defineComponent({
372472      <div  v-else  class =" ui attached segment repos-search" 
373473        <div  class =" ui small fluid action left icon input" 
374474          <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 >
376476          <div  class =" ui dropdown icon button" :title =" textFilter" 
377477            <svg-icon  name =" octicon-filter" :size =" 16" 
378478            <div  class =" menu" 
@@ -401,69 +501,55 @@ export default defineComponent({
401501        </div >
402502        <overflow-menu  class =" ui secondary pointing tabular borderless menu repos-filter" 
403503          <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')" 
405505              {{ textAll }}
406506              <div  v-show =" reposFilter === 'all'" class =" ui circular mini grey label" div >
407507            </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')" 
409509              {{ textSources }}
410510              <div  v-show =" reposFilter === 'sources'" class =" ui circular mini grey label" div >
411511            </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')" 
413513              {{ textForks }}
414514              <div  v-show =" reposFilter === 'forks'" class =" ui circular mini grey label" div >
415515            </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" 
417517              {{ textMirrors }}
418518              <div  v-show =" reposFilter === 'mirrors'" class =" ui circular mini grey label" div >
419519            </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')" 
421521              {{ textCollaborative }}
422522              <div  v-show =" reposFilter === 'collaborative'" class =" ui circular mini grey label" div >
423523            </a >
424524          </div >
425525        </overflow-menu >
426526      </div >
427527      <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" 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)" 
443529        <div  v-if =" showMoreReposLink" class =" tw-text-center" 
444530          <div  class =" divider tw-my-0" 
445531          <div  class =" ui borderless pagination menu narrow tw-my-2" 
446532            <a 
447-               class =" item navigation tw-py-1" :class =" {'disabled': page === 1}" 
533+               class =" item navigation tw-py-1" :class =" {  'disabled': page === 1  }" 
448534              @click =" changePage(1)" :title =" textFirstPage" 
449535            >
450536              <svg-icon  name =" gitea-double-chevron-left" :size =" 16" class =" tw-mr-1" 
451537            </a >
452538            <a 
453-               class =" item navigation tw-py-1" :class =" {'disabled': page === 1}" 
539+               class =" item navigation tw-py-1" :class =" {  'disabled': page === 1  }" 
454540              @click =" changePage(page - 1)" :title =" textPreviousPage" 
455541            >
456542              <svg-icon  name =" octicon-chevron-left" :size =" 16" class =" tw-mr-1" 
457543            </a >
458544            <a  class =" active item tw-py-1" a >
459545            <a 
460-               class =" item navigation" :class =" {'disabled': page === finalPage}" 
546+               class =" item navigation" :class =" {  'disabled': page === finalPage  }" 
461547              @click =" changePage(page + 1)" :title =" textNextPage" 
462548            >
463549              <svg-icon  name =" octicon-chevron-right" :size =" 16" class =" tw-ml-1" 
464550            </a >
465551            <a 
466-               class =" item navigation tw-py-1" :class =" {'disabled': page === finalPage}" 
552+               class =" item navigation tw-py-1" :class =" {  'disabled': page === finalPage  }" 
467553              @click =" changePage(finalPage)" :title =" textLastPage" 
468554            >
469555              <svg-icon  name =" gitea-double-chevron-right" :size =" 16" class =" tw-ml-1" 
@@ -496,7 +582,7 @@ export default defineComponent({
496582              <div  class =" text truncate" div >
497583              <div ><!--  div to prevent underline of label on hover --> 
498584                <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 }}
500586                </span >
501587              </div >
502588            </a >
0 commit comments