diff options
author | wxiaoguang | 2023-03-11 18:47:09 +0800 |
---|---|---|
committer | GitHub | 2023-03-11 12:47:09 +0200 |
commit | 75022f8b1a513ca2fd7ca66a2f05ecc49e2f1460 (patch) | |
tree | 71cf0369fc8fedac368e26f869c027d6529f081f | |
parent | f20bf2fe3b17d998940d5277db75c16b41d2a12c (diff) |
Refactor branch/tag selector dropdown (first step) (#23394)
Follow:
* #23345
The branch/tag selector dropdown mixes jQuery/Fomantic UI/Vue together,
it's very diffcult to maintain and causes unfixable a11y problems. It
also causes problems like #19851 #21314 #21952
This PR is the first step for the refactoring, move `data-` attributes
to JS object and use Vue data as much as possible.
The old selector `'.choose.reference .dropdown'` was also wrong, it hits
`<div class="choose reference"><svg class="dropdown icon">` and would
cause undefined behaviors.
I have done some quick tests and it works. After this PR gets merged, I
will move the code into a Vue SFC in next PR.
![image](https://user-images.githubusercontent.com/2114189/224099638-378a8a86-0865-47d1-bcba-f972506374c7.png)
![image](https://user-images.githubusercontent.com/2114189/224099690-70276cf5-b1e4-404a-b0c6-582448abf40e.png)
---------
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
-rw-r--r-- | templates/repo/branch_dropdown.tmpl | 128 | ||||
-rw-r--r-- | templates/repo/commit_page.tmpl | 8 | ||||
-rw-r--r-- | web_src/js/components/RepoBranchTagDropdown.js | 67 | ||||
-rw-r--r-- | web_src/js/features/repo-legacy.js | 2 |
4 files changed, 101 insertions, 104 deletions
diff --git a/templates/repo/branch_dropdown.tmpl b/templates/repo/branch_dropdown.tmpl index 89d65146a..8e81373ae 100644 --- a/templates/repo/branch_dropdown.tmpl +++ b/templates/repo/branch_dropdown.tmpl @@ -2,101 +2,111 @@ {{$defaultBranch := $.root.BranchName}}{{if and .root.IsViewTag (not .noTag)}}{{$defaultBranch = .root.TagName}}{{end}}{{if eq $defaultBranch ""}}{{$defaultBranch = $.root.Repository.DefaultBranch}}{{end}} {{$type := ""}}{{if and .root.IsViewTag (not .noTag)}}{{$type = "tag"}}{{else if .root.IsViewBranch}}{{$type = "branch"}}{{else}}{{$type = "tree"}}{{end}} {{$showBranchesInDropdown := not .root.HideBranchesInDropdown}} + +<script type="module"> + const data = { + 'textReleaseCompare': {{.root.locale.Tr "repo.release.compare"}}, + 'textCreateTag': {{.root.locale.Tr "repo.tag.create_tag"}}, + 'textCreateBranch': {{.root.locale.Tr "repo.branch.create_branch"}}, + 'textCreateBranchFrom': {{.root.locale.Tr "repo.branch.create_from"}}, + 'textBranches': {{.root.locale.Tr "repo.branches"}}, + 'textTags': {{.root.locale.Tr "repo.tags"}}, + + 'mode': '{{if or .root.IsViewTag .isTag}}tags{{else}}branches{{end}}', + 'showBranchesInDropdown': {{$showBranchesInDropdown}}, + 'searchFieldPlaceholder': '{{if $.noTag}}{{.root.locale.Tr "repo.pulls.filter_branch"}}{{else if $showBranchesInDropdown}}{{.root.locale.Tr "repo.filter_branch_and_tag"}}{{else}}{{.root.locale.Tr "repo.find_tag"}}{{end}}...', + 'branchForm': {{$.branchForm}}, + 'disableCreateBranch': {{if .disableCreateBranch}}{{.disableCreateBranch}}{{else}}{{not .root.CanCreateBranch}}{{end}}, + 'setAction': {{.setAction}}, + 'submitForm': {{.submitForm}}, + 'viewType': {{$type}}, + 'refName': {{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}, + 'commitIdShort': {{ShortSha .root.CommitID}}, + 'tagName': {{.root.TagName}}, + 'branchName': {{.root.BranchName}}, + 'noTag': {{.noTag}}, + 'branches': {{.root.Branches}}, + 'tags': {{.root.Tags}}, + 'defaultBranch': {{$defaultBranch}}, + 'branchURLPrefix': '{{if .branchURLPrefix}}{{.branchURLPrefix}}{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/branch/{{end}}', + 'branchURLSuffix': '{{if .branchURLSuffix}}{{.branchURLSuffix}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}', + 'tagURLPrefix': '{{if .tagURLPrefix}}{{.tagURLPrefix}}{{else if $release}}{{$.root.RepoLink}}/compare/{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/tag/{{end}}', + 'tagURLSuffix': '{{if .tagURLSuffix}}{{.tagURLSuffix}}{{else if $release}}...{{if $release.IsDraft}}{{PathEscapeSegments $release.Target}}{{else}}{{if $release.TagName}}{{PathEscapeSegments $release.TagName}}{{else}}{{PathEscapeSegments $release.Sha1}}{{end}}{{end}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}', + 'repoLink': {{.root.RepoLink}}, + 'treePath': {{.root.TreePath}}, + 'branchNameSubURL': {{.root.BranchNameSubURL}}, + 'noResults': {{.root.locale.Tr "repo.pulls.no_results"}}, + }; + {{if .release}} + data.release = { + 'tagName': {{.release.TagName}}, + }; + {{end}} + window.config.pageData.branchDropdownDataList = window.config.pageData.branchDropdownDataList || []; + window.config.pageData.branchDropdownDataList.push(data); +</script> + <div class="fitted item choose reference"> - <div class="ui floating filter dropdown custom" - data-branch-form="{{if $.branchForm}}{{$.branchForm}}{{end}}" - data-can-create-branch="{{if .canCreateBranch}}{{.canCreateBranch}}{{else}}{{.root.CanCreateBranch}}{{end}}" - data-no-results="{{.root.locale.Tr "repo.pulls.no_results"}}" - data-set-action="{{.setAction}}" data-submit-form="{{.submitForm}}" - data-view-type="{{$type}}" - data-ref-name="{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}" - data-branch-url-prefix="{{if .branchURLPrefix}}{{.branchURLPrefix}}{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/branch/{{end}}" - data-branch-url-suffix="{{if .branchURLSuffix}}{{.branchURLSuffix}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}" - data-tag-url-prefix="{{if .tagURLPrefix}}{{.tagURLPrefix}}{{else if $release}}{{$.root.RepoLink}}/compare/{{else}}{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/tag/{{end}}" - data-tag-url-suffix="{{if .tagURLSuffix}}{{.tagURLSuffix}}{{else if $release}}...{{if $release.IsDraft}}{{PathEscapeSegments $release.Target}}{{else}}{{if $release.TagName}}{{PathEscapeSegments $release.TagName}}{{else}}{{PathEscapeSegments $release.Sha1}}{{end}}{{end}}{{else}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}{{end}}"> + <div class="ui floating filter dropdown custom"> <button class="branch-dropdown-button gt-ellipsis ui basic small compact button gt-df" @click="menuVisible = !menuVisible" @keyup.enter="menuVisible = !menuVisible"> <span class="text gt-df gt-ac gt-mr-2"> - {{if $release}} - {{.root.locale.Tr "repo.release.compare"}} - {{else}} - <span :class="{visible: isViewTag}" v-if="isViewTag" {{if not (eq $type "tag")}}v-cloak{{end}}>{{svg "octicon-tag"}}</span> - <span :class="{visible: isViewBranch}" v-if="isViewBranch" {{if not (eq $type "branch")}}v-cloak{{end}}>{{svg "octicon-git-branch"}}</span> - <span :class="{visible: isViewTree}" v-if="isViewTree" {{if not (eq $type "tree")}}v-cloak{{end}}>{{svg "octicon-git-branch"}}</span> - <strong ref="dropdownRefName" class="gt-ml-3">{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}</strong> - {{end}} + {{/* v-cloak is used to hide unnecessary elements before Vue componment is mounted */}} + <span v-cloak v-if="release">${ textReleaseCompare }</span> + <span :class="{visible: isViewTag}" v-if="isViewTag" {{if not (eq $type "tag")}}v-cloak{{end}}>{{svg "octicon-tag"}}</span> + <span :class="{visible: isViewBranch}" v-if="isViewBranch" {{if not (eq $type "branch")}}v-cloak{{end}}>{{svg "octicon-git-branch"}}</span> + <span :class="{visible: isViewTree}" v-if="isViewTree" {{if not (eq $type "tree")}}v-cloak{{end}}>{{svg "octicon-git-branch"}}</span> + <strong ref="dropdownRefName" class="gt-ml-3">{{if and .root.IsViewTag (not .noTag)}}{{.root.TagName}}{{else if .root.IsViewBranch}}{{.root.BranchName}}{{else}}{{ShortSha .root.CommitID}}{{end}}</strong> </span> {{svg "octicon-triangle-down" 14 "dropdown icon"}} </button> - <div class="data gt-hidden" data-mode="{{if or .root.IsViewTag .isTag}}tags{{else}}branches{{end}}"> - {{if $showBranchesInDropdown}} - {{range .root.Branches}} - <div class="item branch {{if eq $defaultBranch .}}selected{{end}}" data-url="{{PathEscapeSegments .}}">{{.}}</div> - {{end}} - {{end}} - {{if (not .noTag)}} - {{range .root.Tags}} - {{if $release}} - <div class="item tag {{if eq $release.TagName .}}selected{{end}}" data-url="{{PathEscapeSegments .}}">{{.}}</div> - {{else}} - <div class="item tag {{if eq $defaultBranch .}}selected{{end}}" data-url="{{PathEscapeSegments .}}">{{.}}</div> - {{end}} - {{end}} - {{end}} - </div> <div class="menu transition" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak> <div class="ui icon search input"> <i class="icon gt-df gt-ac gt-jc gt-m-0">{{svg "octicon-filter" 16}}</i> - <input name="search" ref="searchField" autocomplete="off" v-model="searchTerm" @keydown="keydown($event)" placeholder="{{if $.noTag}}{{.root.locale.Tr "repo.pulls.filter_branch"}}{{else if $showBranchesInDropdown}}{{.root.locale.Tr "repo.filter_branch_and_tag"}}{{else}}{{.root.locale.Tr "repo.find_tag"}}{{end}}..."> + <input name="search" ref="searchField" autocomplete="off" v-model="searchTerm" @keydown="keydown($event)" :placeholder="searchFieldPlaceholder"> </div> - {{if $showBranchesInDropdown}} + <template v-if="showBranchesInDropdown"> <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()"> - <span class="text" :class="{black: mode == 'branches'}"> - {{svg "octicon-git-branch" 16 "gt-mr-2"}}{{.root.locale.Tr "repo.branches"}} + <span class="text" :class="{black: mode === 'branches'}"> + {{svg "octicon-git-branch" 16 "gt-mr-2"}}${ textBranches } </span> </a> - {{if not .noTag}} + <template v-if="!noTag"> <a class="reference column" href="#" @click="createTag = true; mode = 'tags'; focusSearchField()"> - <span class="text" :class="{black: mode == 'tags'}"> - {{svg "octicon-tag" 16 "gt-mr-2"}}{{.root.locale.Tr "repo.tags"}} + <span class="text" :class="{black: mode === 'tags'}"> + {{svg "octicon-tag" 16 "gt-mr-2"}}${ textTags } </span> </a> - {{end}} + </template> </div> </div> </div> - {{end}} + </template> <div class="scrolling menu" ref="scrollContainer"> - <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 }</div> - <div class="item" v-if="showCreateNewBranch" :class="{active: active == filteredItems.length}" :ref="'listItem' + filteredItems.length"> + <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 }</div> + <div class="item" v-if="showCreateNewBranch" :class="{active: active === filteredItems.length}" :ref="'listItem' + filteredItems.length"> <a href="#" @click="createNewBranch()"> <div v-show="createTag"> <i class="reference tags icon"></i> - {{.root.locale.Tr "repo.tag.create_tag" `${ searchTerm }` | Safe}} + <span v-html="textCreateTag.replace('%s', searchTerm)"></span> </div> <div v-show="!createTag"> {{svg "octicon-git-branch"}} - {{.root.locale.Tr "repo.branch.create_branch" `${ searchTerm }` | Safe}} + <span v-html="textCreateBranch.replace('%s', searchTerm)"></span> </div> <div class="text small"> - {{if or .root.IsViewBranch $release}} - {{.root.locale.Tr "repo.branch.create_from" .root.BranchName}} - {{else if .root.IsViewTag}} - {{.root.locale.Tr "repo.branch.create_from" .root.TagName}} - {{else}} - {{.root.locale.Tr "repo.branch.create_from" (ShortSha .root.CommitID)}} - {{end}} + <span v-if="isViewBranch || release">${ textCreateBranchFrom.replace('%s', branchName) }</span> + <span v-else-if="isViewTag">${ textCreateBranchFrom.replace('%s', tagName) }</span> + <span v-else>${ textCreateBranchFrom.replace('%s', commitIdShort) }</span> </div> </a> <form ref="newBranchForm" action="{{.root.RepoLink}}/branches/_new/{{.root.BranchNameSubURL}}" method="post"> - {{.root.CsrfTokenHtml}} + <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"> - {{if $.root.TreePath}} - <input type="hidden" name="current_path" value="{{.root.TreePath}}"> - {{end}} + <input type="hidden" name="current_path" v-model="treePath" v-if="treePath"> </form> </div> </div> diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index 028fdc7e5..f19a4d422 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -52,13 +52,13 @@ data-modal-cherry-pick-type="revert" data-modal-cherry-pick-header="{{$.locale.Tr "repo.commit.revert-header" (ShortSha .CommitID)}}" data-modal-cherry-pick-content="{{$.locale.Tr "repo.commit.revert-content"}}" - data-modal-cherry-pick-submit="{{.locale.Tr "repo.commit.revert"}}">{{.locale.Tr "repo.commit.revert"}}</a></div> + data-modal-cherry-pick-submit="{{.locale.Tr "repo.commit.revert"}}">{{.locale.Tr "repo.commit.revert"}}</div> <div class="item cherry-pick-button show-modal" data-modal="#cherry-pick-modal" data-modal-cherry-pick-type="cherry-pick" data-modal-cherry-pick-header="{{$.locale.Tr "repo.commit.cherry-pick-header" (ShortSha .CommitID)}}" data-modal-cherry-pick-content="{{$.locale.Tr "repo.commit.cherry-pick-content"}}" - data-modal-cherry-pick-submit="{{.locale.Tr "repo.commit.cherry-pick"}}">{{.locale.Tr "repo.commit.cherry-pick"}}</a></div> + data-modal-cherry-pick-submit="{{.locale.Tr "repo.commit.cherry-pick"}}">{{.locale.Tr "repo.commit.cherry-pick"}}</div> <div class="ui basic modal" id="cherry-pick-modal"> <div class="ui icon header"> <span id="cherry-pick-header"></span> @@ -66,10 +66,10 @@ <div class="content center"> <p id="cherry-pick-content" class="branch-dropdown"></p> {{template "repo/branch_dropdown" dict "root" . - "noTag" "true" "canCreateBranch" "false" + "noTag" true "disableCreateBranch" true "branchForm" "branch-dropdown-form" "branchURLPrefix" (printf "%s/_cherrypick/%s/" $.RepoLink .CommitID) "branchURLSuffix" "" - "setAction" "true" "submitForm" "true"}} + "setAction" true "submitForm" true}} <form method="GET" action="{{$.RepoLink}}/_cherrypick/{{.CommitID}}/{{if $.BranchName}}{{PathEscapeSegments $.BranchName}}{{else}}{{PathEscapeSegments $.Repository.DefaultBranch}}{{end}}" id="branch-dropdown-form"> <input type="hidden" name="ref" value="{{if $.BranchName}}{{$.BranchName}}{{else}}{{$.Repository.DefaultBranch}}{{end}}"> <input type="hidden" name="refType" value="branch"> diff --git a/web_src/js/components/RepoBranchTagDropdown.js b/web_src/js/components/RepoBranchTagDropdown.js index 8bed305fc..e1bf35c12 100644 --- a/web_src/js/components/RepoBranchTagDropdown.js +++ b/web_src/js/components/RepoBranchTagDropdown.js @@ -3,43 +3,41 @@ import $ from 'jquery'; import {vueDelimiters} from './VueComponentLoader.js'; export function initRepoBranchTagDropdown(selector) { - $(selector).each(function () { - const $dropdown = $(this); - const $data = $dropdown.find('.data'); + $(selector).each(function (dropdownIndex, elRoot) { const data = { + csrfToken: window.config.csrfToken, items: [], - mode: $data.data('mode'), searchTerm: '', - refName: '', - noResults: '', - canCreateBranch: false, menuVisible: false, createTag: false, + release: null, + isViewTag: false, isViewBranch: false, isViewTree: false, + active: 0, - branchForm: '', - branchURLPrefix: '', - branchURLSuffix: '', - tagURLPrefix: '', - tagURLSuffix: '', - setAction: false, - submitForm: false, + + ...window.config.pageData.branchDropdownDataList[dropdownIndex], }; - $data.find('.item').each(function () { - data.items.push({ - name: $(this).text(), - url: $(this).data('url'), - branch: $(this).hasClass('branch'), - tag: $(this).hasClass('tag'), - selected: $(this).hasClass('selected') - }); - }); - $data.remove(); - // eslint-disable-next-line unicorn/no-this-assignment - const elRoot = this; + // 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: 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: tag, branch: false, tag: true, selected: tag === data.release.tagName}); + } else { + data.items.push({name: tag, url: tag, branch: false, tag: true, selected: tag === data.defaultBranch}); + } + } + } + const view = createApp({ delimiters: vueDelimiters, data() { @@ -60,7 +58,7 @@ export function initRepoBranchTagDropdown(selector) { return this.filteredItems.length === 0 && !this.showCreateNewBranch; }, showCreateNewBranch() { - if (!this.canCreateBranch || !this.searchTerm) { + if (this.disableCreateBranch || !this.searchTerm) { return false; } @@ -77,10 +75,7 @@ export function initRepoBranchTagDropdown(selector) { }, beforeMount() { - this.noResults = elRoot.getAttribute('data-no-results'); - this.canCreateBranch = elRoot.getAttribute('data-can-create-branch') === 'true'; - this.branchForm = elRoot.getAttribute('data-branch-form'); - switch (elRoot.getAttribute('data-view-type')) { + switch (data.viewType) { case 'tree': this.isViewTree = true; break; @@ -91,14 +86,6 @@ export function initRepoBranchTagDropdown(selector) { this.isViewBranch = true; break; } - this.refName = elRoot.getAttribute('data-ref-name'); - this.branchURLPrefix = elRoot.getAttribute('data-branch-url-prefix'); - this.branchURLSuffix = elRoot.getAttribute('data-branch-url-suffix'); - this.tagURLPrefix = elRoot.getAttribute('data-tag-url-prefix'); - this.tagURLSuffix = elRoot.getAttribute('data-tag-url-suffix'); - this.setAction = elRoot.getAttribute('data-set-action') === 'true'; - this.submitForm = elRoot.getAttribute('data-submit-form') === 'true'; - document.body.addEventListener('click', (event) => { if (elRoot.contains(event.target)) return; @@ -116,7 +103,7 @@ export function initRepoBranchTagDropdown(selector) { } item.selected = true; const url = (item.tag) ? this.tagURLPrefix + item.url + this.tagURLSuffix : this.branchURLPrefix + item.url + this.branchURLSuffix; - if (this.branchForm === '') { + if (!this.branchForm) { window.location.href = url; } else { this.isViewTree = false; diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 5173a5b59..70542ad88 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -485,7 +485,7 @@ export function initRepository() { // File list and commits if ($('.repository.file.list').length > 0 || $('.branch-dropdown').length > 0 || $('.repository.commits').length > 0 || $('.repository.release').length > 0) { - initRepoBranchTagDropdown('.choose.reference .dropdown'); + initRepoBranchTagDropdown('.choose.reference .ui.dropdown'); } // Wiki |