]> source.dussan.org Git - gitea.git/commitdiff
Use frontend fetch for branch dropdown component (#25719)
authorHesterG <hestergong@gmail.com>
Fri, 21 Jul 2023 11:20:04 +0000 (19:20 +0800)
committerGitHub <noreply@github.com>
Fri, 21 Jul 2023 11:20:04 +0000 (11:20 +0000)
- Send request to get branch/tag list, use loading icon when waiting for
response.
- Only fetch when the first time branch/tag list shows.
- For backend, removed assignment to `ctx.Data["Branches"]` and
`ctx.Data["Tags"]` from `context/repo.go` and passed these data wherever
needed.
- Changed some `v-if` to `v-show` and used native `svg` as mentioned in
https://github.com/go-gitea/gitea/pull/25719#issuecomment-1631712757 to
improve perfomance when there are a lot of branches.
- Places Used the dropdown component:

     Repo Home Page

<img width="1429" alt="Screen Shot 2023-07-06 at 12 17 51"
src="https://github.com/go-gitea/gitea/assets/17645053/6accc7b6-8d37-4e88-ae1a-bd2b3b927ea0">

    Commits Page

<img width="1431" alt="Screen Shot 2023-07-06 at 12 18 34"
src="https://github.com/go-gitea/gitea/assets/17645053/2d0bf306-d1e2-45a8-a784-bc424879f537">

    Specific commit -> operations -> cherry-pick

<img width="758" alt="Screen Shot 2023-07-06 at 12 23 28"
src="https://github.com/go-gitea/gitea/assets/17645053/1e557948-3881-4e45-a625-8ef36d45ae2d">

    Release Page

<img width="1433" alt="Screen Shot 2023-07-06 at 12 25 05"
src="https://github.com/go-gitea/gitea/assets/17645053/3ec82af1-15a4-4162-a50b-04a9502161bb">

- Demo

https://github.com/go-gitea/gitea/assets/17645053/d45d266b-3eb0-465a-82f9-57f78dc5f9f3

- Note:

UI of dropdown menu could be improved in another PR as it should apply
to more dropdown menus.

Fix #14180

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
13 files changed:
modules/context/repo.go
routers/web/repo/compare.go
routers/web/repo/issue.go
routers/web/repo/pull.go
routers/web/repo/release.go
routers/web/repo/repo.go
routers/web/repo/setting/protected_branch.go
routers/web/web.go
templates/repo/branch_dropdown.tmpl
web_src/css/repo.css
web_src/js/components/RepoBranchTagSelector.vue
web_src/js/modules/toast.js
web_src/js/svg.js

index eae71cfb7be6759718fabc963cda482b66672547..2c67735c934c603e55343078000ecc6ac88475e6 100644 (file)
@@ -660,13 +660,6 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
                return cancel
        }
 
-       tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
-       if err != nil {
-               ctx.ServerError("GetTagNamesByRepoID", err)
-               return cancel
-       }
-       ctx.Data["Tags"] = tags
-
        branchOpts := git_model.FindBranchOptions{
                RepoID:          ctx.Repo.Repository.ID,
                IsDeletedBranch: util.OptionalBoolFalse,
@@ -680,7 +673,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
                return cancel
        }
 
-       // non empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
+       // non-empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
        if branchesTotal == 0 { // fallback to do a sync immediately
                branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
                if err != nil {
@@ -689,24 +682,19 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
                }
        }
 
-       // FIXME: use paganation and async loading
-       branchOpts.ExcludeBranchNames = []string{ctx.Repo.Repository.DefaultBranch}
-       brs, err := git_model.FindBranchNames(ctx, branchOpts)
-       if err != nil {
-               ctx.ServerError("GetBranches", err)
-               return cancel
-       }
-       // always put default branch on the top
-       ctx.Data["Branches"] = append(branchOpts.ExcludeBranchNames, brs...)
        ctx.Data["BranchesCount"] = branchesTotal
 
-       // If not branch selected, try default one.
-       // If default branch doesn't exist, fall back to some other branch.
+       // If no branch is set in the request URL, try to guess a default one.
        if len(ctx.Repo.BranchName) == 0 {
                if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
                        ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
-               } else if len(brs) > 0 {
-                       ctx.Repo.BranchName = brs[0]
+               } else {
+                       ctx.Repo.BranchName, _ = gitRepo.GetDefaultBranch()
+                       if ctx.Repo.BranchName == "" {
+                               // If it still can't get a default branch, fall back to default branch from setting.
+                               // Something might be wrong. Either site admin should fix the repo sync or Gitea should fix a potential bug.
+                               ctx.Repo.BranchName = setting.Repository.DefaultBranch
+                       }
                }
                ctx.Repo.RefName = ctx.Repo.BranchName
        }
index 7089c219ad25ca88e6d813eb616cf5a06992c585..4ceb52d03994aa62d7868d25c12b39bc328cc3b9 100644 (file)
@@ -754,6 +754,12 @@ func CompareDiff(ctx *context.Context) {
        }
        ctx.Data["HeadBranches"] = headBranches
 
+       // For compare repo branches
+       PrepareBranchList(ctx)
+       if ctx.Written() {
+               return
+       }
+
        headTags, err := repo_model.GetTagNamesByRepoID(ctx, ci.HeadRepo.ID)
        if err != nil {
                ctx.ServerError("GetTagNamesByRepoID", err)
index 8fb6fe04dfcf66de6459ca4ec60efd96342b4db7..f8555140484911eb772226ff60387e96383f886d 100644 (file)
@@ -785,18 +785,10 @@ func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull
                return nil
        }
 
-       brs, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
-               RepoID: ctx.Repo.Repository.ID,
-               ListOptions: db.ListOptions{
-                       ListAll: true,
-               },
-               IsDeletedBranch: util.OptionalBoolFalse,
-       })
-       if err != nil {
-               ctx.ServerError("GetBranches", err)
+       PrepareBranchList(ctx)
+       if ctx.Written() {
                return nil
        }
-       ctx.Data["Branches"] = brs
 
        // Contains true if the user can create issue dependencies
        ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.Doer, isPull)
@@ -921,6 +913,13 @@ func NewIssue(ctx *context.Context) {
 
        RetrieveRepoMetas(ctx, ctx.Repo.Repository, false)
 
+       tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
+       if err != nil {
+               ctx.ServerError("GetTagNamesByRepoID", err)
+               return
+       }
+       ctx.Data["Tags"] = tags
+
        _, templateErrs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
        if errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates); len(errs) > 0 {
                for k, v := range errs {
@@ -1918,6 +1917,19 @@ func ViewIssue(ctx *context.Context) {
        ctx.Data["ShouldShowCommentType"] = func(commentType issues_model.CommentType) bool {
                return hiddenCommentTypes == nil || hiddenCommentTypes.Bit(int(commentType)) == 0
        }
+       // For sidebar
+       PrepareBranchList(ctx)
+
+       if ctx.Written() {
+               return
+       }
+
+       tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
+       if err != nil {
+               ctx.ServerError("GetTagNamesByRepoID", err)
+               return
+       }
+       ctx.Data["Tags"] = tags
 
        ctx.HTML(http.StatusOK, tplIssueView)
 }
index c5f260adfa745bf89cc14d90cb6ebd1a216d52af..adfc01c2517f55b9e8ad4a347b48600b1a1f07fa 100644 (file)
@@ -729,6 +729,11 @@ func ViewPullCommits(ctx *context.Context) {
        ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
        ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID)
 
+       // For PR commits page
+       PrepareBranchList(ctx)
+       if ctx.Written() {
+               return
+       }
        getBranchData(ctx, issue)
        ctx.HTML(http.StatusOK, tplPullCommits)
 }
@@ -893,6 +898,11 @@ func ViewPullFiles(ctx *context.Context) {
        ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
 
        ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
+       // For files changed page
+       PrepareBranchList(ctx)
+       if ctx.Written() {
+               return
+       }
        upload.AddUploadContext(ctx, "comment")
 
        ctx.HTML(http.StatusOK, tplPullFiles)
index 1a0303f1f21ba615258a4d0ad7030930b1425107..3d991384e56a7c7a94f687e53cb2d0dbbf43968f 100644 (file)
@@ -352,6 +352,20 @@ func NewRelease(ctx *context.Context) {
        ctx.Data["Assignees"] = MakeSelfOnTop(ctx, assigneeUsers)
 
        upload.AddUploadContext(ctx, "release")
+
+       // For New Release page
+       PrepareBranchList(ctx)
+       if ctx.Written() {
+               return
+       }
+
+       tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
+       if err != nil {
+               ctx.ServerError("GetTagNamesByRepoID", err)
+               return
+       }
+       ctx.Data["Tags"] = tags
+
        ctx.HTML(http.StatusOK, tplReleaseNew)
 }
 
@@ -361,6 +375,13 @@ func NewReleasePost(ctx *context.Context) {
        ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
        ctx.Data["PageIsReleaseList"] = true
 
+       tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
+       if err != nil {
+               ctx.ServerError("GetTagNamesByRepoID", err)
+               return
+       }
+       ctx.Data["Tags"] = tags
+
        if ctx.HasError() {
                ctx.HTML(http.StatusOK, tplReleaseNew)
                return
index 0a2b334eb90aaff25ef3962dd5e690ff1c4828c5..0de804dbce6fa7139ce9c18d44150fe738869169 100644 (file)
@@ -622,3 +622,64 @@ func SearchRepo(ctx *context.Context) {
                Data: results,
        })
 }
+
+type branchTagSearchResponse struct {
+       Results []string `json:"results"`
+}
+
+// GetBranchesList get branches for current repo'
+func GetBranchesList(ctx *context.Context) {
+       branchOpts := git_model.FindBranchOptions{
+               RepoID:          ctx.Repo.Repository.ID,
+               IsDeletedBranch: util.OptionalBoolFalse,
+               ListOptions: db.ListOptions{
+                       ListAll: true,
+               },
+       }
+       branches, err := git_model.FindBranchNames(ctx, branchOpts)
+       if err != nil {
+               ctx.JSON(http.StatusInternalServerError, err)
+               return
+       }
+       resp := &branchTagSearchResponse{}
+       // always put default branch on the top if it exists
+       if util.SliceContains(branches, ctx.Repo.Repository.DefaultBranch) {
+               branches = util.SliceRemoveAll(branches, ctx.Repo.Repository.DefaultBranch)
+               branches = append([]string{ctx.Repo.Repository.DefaultBranch}, branches...)
+       }
+       resp.Results = branches
+       ctx.JSON(http.StatusOK, resp)
+}
+
+// GetTagList get tag list for current repo
+func GetTagList(ctx *context.Context) {
+       tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
+       if err != nil {
+               ctx.JSON(http.StatusInternalServerError, err)
+               return
+       }
+       resp := &branchTagSearchResponse{}
+       resp.Results = tags
+       ctx.JSON(http.StatusOK, resp)
+}
+
+func PrepareBranchList(ctx *context.Context) {
+       branchOpts := git_model.FindBranchOptions{
+               RepoID:          ctx.Repo.Repository.ID,
+               IsDeletedBranch: util.OptionalBoolFalse,
+               ListOptions: db.ListOptions{
+                       ListAll: true,
+               },
+       }
+       brs, err := git_model.FindBranchNames(ctx, branchOpts)
+       if err != nil {
+               ctx.ServerError("GetBranches", err)
+               return
+       }
+       // always put default branch on the top if it exists
+       if util.SliceContains(brs, ctx.Repo.Repository.DefaultBranch) {
+               brs = util.SliceRemoveAll(brs, ctx.Repo.Repository.DefaultBranch)
+               brs = append([]string{ctx.Repo.Repository.DefaultBranch}, brs...)
+       }
+       ctx.Data["Branches"] = brs
+}
index 777d52c8580613cc33b2c0b3d5137823bbf0d1ab..b8775cca2d3cfbdc293a630a592c9f834daca046 100644 (file)
@@ -21,6 +21,7 @@ import (
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/setting"
        "code.gitea.io/gitea/modules/web"
+       "code.gitea.io/gitea/routers/web/repo"
        "code.gitea.io/gitea/services/forms"
        pull_service "code.gitea.io/gitea/services/pull"
        "code.gitea.io/gitea/services/repository"
@@ -44,6 +45,11 @@ func ProtectedBranchRules(ctx *context.Context) {
        }
        ctx.Data["ProtectedBranches"] = rules
 
+       repo.PrepareBranchList(ctx)
+       if ctx.Written() {
+               return
+       }
+
        ctx.HTML(http.StatusOK, tplBranches)
 }
 
@@ -52,6 +58,11 @@ func SetDefaultBranchPost(ctx *context.Context) {
        ctx.Data["Title"] = ctx.Tr("repo.settings.branches.update_default_branch")
        ctx.Data["PageIsSettingsBranches"] = true
 
+       repo.PrepareBranchList(ctx)
+       if ctx.Written() {
+               return
+       }
+
        repo := ctx.Repo.Repository
 
        switch ctx.FormString("action") {
index 00e0ca36dab0e36415b591652342b9707d2bb270..f091bfefb86d4d71b37feb4d7b649505efb04247 100644 (file)
@@ -1094,6 +1094,7 @@ func registerRoutes(m *web.Route) {
                }, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived())
 
                m.Group("/branches", func() {
+                       m.Get("/list", repo.GetBranchesList)
                        m.Group("/_new", func() {
                                m.Post("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.CreateBranch)
                                m.Post("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.CreateBranch)
@@ -1108,6 +1109,7 @@ func registerRoutes(m *web.Route) {
        m.Group("/{username}/{reponame}", func() {
                m.Group("/tags", func() {
                        m.Get("", repo.TagsList)
+                       m.Get("/list", repo.GetTagList)
                        m.Get(".rss", feedEnabled, repo.TagsListFeedRSS)
                        m.Get(".atom", feedEnabled, repo.TagsListFeedAtom)
                }, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
index 984899552dc0b8a0996c432a0f359762eb3301f7..4ec2fd557c76ab61fee4017081ab8dcf49e2aeaa 100644 (file)
@@ -44,8 +44,6 @@
                'tagName': {{.root.TagName}},
                'branchName': {{.root.BranchName}},
                'noTag': {{.noTag}},
-               'branches': {{.root.Branches}},
-               'tags': {{.root.Tags}},
                'defaultBranch': {{$defaultBranch}},
                'enableFeed': {{.root.EnableFeed}},
                'rssURLPrefix': '{{$.root.RepoLink}}/rss/branch/',
index 877f0d9c1734e45565aaddc7faf43bfd780729fe..6cd7c2537e513473cc22a42128dc38458ed39c9e 100644 (file)
@@ -3359,3 +3359,7 @@ tbody.commit-list {
   font-size: 18px;
   margin-left: 4px;
 }
+
+#cherry-pick-modal .scrolling.menu {
+  max-height: 200px;
+}
index 4fc393624469de0e0a141355d0197494aba4025a..e6e72e3886d703716d6261916515e5b037bf7ad8 100644 (file)
@@ -11,7 +11,7 @@
       </span>
       <svg-icon name="octicon-triangle-down" :size="14" class-name="dropdown icon"/>
     </button>
-    <div class="menu transition" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak>
+    <div class="menu transition" :class="{visible: menuVisible}" v-show="menuVisible" v-cloak>
       <div class="ui icon search input">
         <i class="icon"><svg-icon name="octicon-filter" :size="16"/></i>
         <input name="search" ref="searchField" autocomplete="off" v-model="searchTerm" @keydown="keydown($event)" :placeholder="searchFieldPlaceholder">
         <div class="header branch-tag-choice">
           <div class="ui grid">
             <div class="two column row">
-              <a class="reference column" href="#" @click="createTag = false; mode = 'branches'; focusSearchField()">
+              <a class="reference column" href="#" @click="handleTabSwitch('branches')">
                 <span class="text" :class="{black: mode === 'branches'}">
                   <svg-icon name="octicon-git-branch" :size="16" class-name="gt-mr-2"/>{{ textBranches }}
                 </span>
               </a>
               <template v-if="!noTag">
-                <a class="reference column" href="#" @click="createTag = true; mode = 'tags'; focusSearchField()">
+                <a class="reference column" href="#" @click="handleTabSwitch('tags')">
                   <span class="text" :class="{black: mode === 'tags'}">
                     <svg-icon name="octicon-tag" :size="16" class-name="gt-mr-2"/>{{ textTags }}
                   </span>
         </div>
       </template>
       <div class="scrolling menu" ref="scrollContainer">
+        <svg-icon name="octicon-rss" symbol-id="svg-symbol-octicon-rss"/>
+        <div class="loading-indicator is-loading" v-if="isLoading"/>
         <div v-for="(item, index) in filteredItems" :key="item.name" class="item" :class="{selected: item.selected, active: active === index}" @click="selectItem(item)" :ref="'listItem' + index">
           {{ item.name }}
-          <a v-if="enableFeed && mode === 'branches'" role="button" class="rss-icon ui compact right" :href="rssURLPrefix + item.url" target="_blank" @click.stop>
-            <svg-icon name="octicon-rss" :size="14"/>
+          <a v-show="enableFeed && mode === 'branches'" role="button" class="rss-icon ui compact right" :href="rssURLPrefix + item.url" target="_blank" @click.stop>
+            <!-- creating a lot of Vue component is pretty slow, so we use a static SVG here -->
+            <svg width="14" height="14" class="svg octicon-rss"><use href="#svg-symbol-octicon-rss"/></svg>
           </a>
         </div>
         <div class="item" v-if="showCreateNewBranch" :class="{active: active === filteredItems.length}" :ref="'listItem' + filteredItems.length">
           <a href="#" @click="createNewBranch()">
-            <div v-show="createTag">
+            <div v-show="shouldCreateTag">
               <i class="reference tags icon"/>
               <!-- eslint-disable-next-line vue/no-v-html -->
               <span v-html="textCreateTag.replace('%s', searchTerm)"/>
             </div>
-            <div v-show="!createTag">
+            <div v-show="!shouldCreateTag">
               <svg-icon name="octicon-git-branch"/>
               <!-- eslint-disable-next-line vue/no-v-html -->
               <span v-html="textCreateBranch.replace('%s', searchTerm)"/>
           <form ref="newBranchForm" :action="formActionUrl" method="post">
             <input type="hidden" name="_csrf" :value="csrfToken">
             <input type="hidden" name="new_branch_name" v-model="searchTerm">
-            <input type="hidden" name="create_tag" v-model="createTag">
+            <input type="hidden" name="create_tag" v-model="shouldCreateTag">
             <input type="hidden" name="current_path" v-model="treePath" v-if="treePath">
           </form>
         </div>
       </div>
-      <div class="message" v-if="showNoResults">
+      <div class="message" v-if="showNoResults && !isLoading">
         {{ noResults }}
       </div>
     </div>
@@ -81,6 +84,7 @@ import {createApp, nextTick} from 'vue';
 import $ from 'jquery';
 import {SvgIcon} from '../svg.js';
 import {pathEscapeSegments} from '../utils/url.js';
+import {showErrorToast} from '../modules/toast.js';
 
 const sfc = {
   components: {SvgIcon},
@@ -110,12 +114,16 @@ const sfc = {
     formActionUrl() {
       return `${this.repoLink}/branches/_new/${this.branchNameSubURL}`;
     },
+    shouldCreateTag() {
+      return this.mode === 'tags';
+    }
   },
 
   watch: {
     menuVisible(visible) {
       if (visible) {
         this.focusSearchField();
+        this.fetchBranchesOrTags();
       }
     }
   },
@@ -139,7 +147,6 @@ const sfc = {
       }
     });
   },
-
   methods: {
     selectItem(item) {
       const prev = this.getSelected();
@@ -246,7 +253,44 @@ const sfc = {
         event.preventDefault();
         this.menuVisible = false;
       }
-    }
+    },
+    handleTabSwitch(mode) {
+      if (this.isLoading) return;
+      this.mode = mode;
+      this.focusSearchField();
+      this.fetchBranchesOrTags();
+    },
+    async fetchBranchesOrTags() {
+      if (!['branches', 'tags'].includes(this.mode) || this.isLoading) return;
+      // only fetch when branch/tag list has not been initialized
+      if (this.hasListInitialized[this.mode] ||
+        (this.mode === 'branches' && !this.showBranchesInDropdown) ||
+        (this.mode === 'tags' && this.noTag)
+      ) {
+        return;
+      }
+      this.isLoading = true;
+      try {
+        // the "data.defaultBranch" is ambiguous, it could be "branch name" or "tag name"
+        const reqUrl = `${this.repoLink}/${this.mode}/list`;
+        const resp = await fetch(reqUrl);
+        const {results} = await resp.json();
+        for (const result of results) {
+          let selected = false;
+          if (this.mode === 'branches') {
+            selected = result === this.defaultBranch;
+          } else {
+            selected = result === (this.release ? this.release.tagName : this.defaultBranch);
+          }
+          this.items.push({name: result, url: pathEscapeSegments(result), branch: this.mode === 'branches', tag: this.mode === 'tags', selected});
+        }
+        this.hasListInitialized[this.mode] = true;
+      } catch (e) {
+        showErrorToast(`Network error when fetching ${this.mode}, error: ${e}`);
+      } finally {
+        this.isLoading = false;
+      }
+    },
   }
 };
 
@@ -258,7 +302,6 @@ export function initRepoBranchTagSelector(selector) {
       searchTerm: '',
       refNameText: '',
       menuVisible: false,
-      createTag: false,
       release: null,
 
       isViewTag: false,
@@ -266,27 +309,15 @@ export function initRepoBranchTagSelector(selector) {
       isViewTree: false,
 
       active: 0,
-
+      isLoading: false,
+      // This means whether branch list/tag list has initialized
+      hasListInitialized: {
+        'branches': false,
+        'tags': false,
+      },
       ...window.config.pageData.branchDropdownDataList[elIndex],
     };
 
-    // the "data.defaultBranch" is ambiguous, it could be "branch name" or "tag name"
-
-    if (data.showBranchesInDropdown && data.branches) {
-      for (const branch of data.branches) {
-        data.items.push({name: branch, url: pathEscapeSegments(branch), branch: true, tag: false, selected: branch === data.defaultBranch});
-      }
-    }
-    if (!data.noTag && data.tags) {
-      for (const tag of data.tags) {
-        if (data.release) {
-          data.items.push({name: tag, url: pathEscapeSegments(tag), branch: false, tag: true, selected: tag === data.release.tagName});
-        } else {
-          data.items.push({name: tag, url: pathEscapeSegments(tag), branch: false, tag: true, selected: tag === data.defaultBranch});
-        }
-      }
-    }
-
     const comp = {...sfc, data() { return data }};
     createApp(comp).mount(elRoot);
   }
@@ -302,4 +333,8 @@ export default sfc; // activate IDE's Vue plugin
 .menu .item:hover .rss-icon {
   display: inline-block;
 }
+
+.scrolling.menu .loading-indicator {
+  height: 4em;
+}
 </style>
index b0d02dc64470ef03e3112d13fcbc552fc175be4d..b5899052d48c6883e981b231a68323f37f2ce0d9 100644 (file)
@@ -1,5 +1,6 @@
 import {htmlEscape} from 'escape-goat';
 import {svg} from '../svg.js';
+import Toastify from 'toastify-js';
 
 const levels = {
   info: {
@@ -23,7 +24,6 @@ const levels = {
 async function showToast(message, level, {gravity, position, duration, ...other} = {}) {
   if (!message) return;
 
-  const {default: Toastify} = await import(/* webpackChunkName: 'toastify' */'toastify-js');
   const {icon, background, duration: levelDuration} = levels[level ?? 'info'];
 
   const toast = Toastify({
index b0c55e4e371df005291ce6a0187c155e7dcdf21d..2ef839aa21d29be26a56e9024a18caadec7d27b4 100644 (file)
@@ -185,9 +185,10 @@ export const SvgIcon = {
     name: {type: String, required: true},
     size: {type: Number, default: 16},
     className: {type: String, default: ''},
+    symbolId: {type: String}
   },
   render() {
-    const {svgOuter, svgInnerHtml} = svgParseOuterInner(this.name);
+    let {svgOuter, svgInnerHtml} = svgParseOuterInner(this.name);
     // https://vuejs.org/guide/extras/render-function.html#creating-vnodes
     // the `^` is used for attr, set SVG attributes like 'width', `aria-hidden`, `viewBox`, etc
     const attrs = {};
@@ -207,7 +208,10 @@ export const SvgIcon = {
     if (this.className) {
       classes.push(...this.className.split(/\s+/).filter(Boolean));
     }
-
+    if (this.symbolId) {
+      classes.push('gt-hidden', 'svg-symbol-container');
+      svgInnerHtml = `<symbol id="${this.symbolId}" viewBox="${attrs['^viewBox']}">${svgInnerHtml}</symbol>`;
+    }
     // create VNode
     return h('svg', {
       ...attrs,