+
+
{{ mergeForm.textClearMergeMessageHint }}
diff --git a/web_src/js/components/RepoBranchTagDropdown.js b/web_src/js/components/RepoBranchTagDropdown.js
deleted file mode 100644
index a8945b82d..000000000
--- a/web_src/js/components/RepoBranchTagDropdown.js
+++ /dev/null
@@ -1,208 +0,0 @@
-import {createApp, nextTick} from 'vue';
-import $ from 'jquery';
-
-export function initRepoBranchTagDropdown(selector) {
- $(selector).each(function (dropdownIndex, elRoot) {
- const data = {
- csrfToken: window.config.csrfToken,
- items: [],
- searchTerm: '',
- menuVisible: false,
- createTag: false,
- release: null,
-
- isViewTag: false,
- isViewBranch: false,
- isViewTree: false,
-
- active: 0,
-
- ...window.config.pageData.branchDropdownDataList[dropdownIndex],
- };
-
- // 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: ['${', '}'],
- data() {
- return data;
- },
- computed: {
- filteredItems() {
- const items = this.items.filter((item) => {
- return ((this.mode === 'branches' && item.branch) || (this.mode === 'tags' && item.tag)) &&
- (!this.searchTerm || item.name.toLowerCase().includes(this.searchTerm.toLowerCase()));
- });
-
- // no idea how to fix this so linting rule is disabled instead
- this.active = (items.length === 0 && this.showCreateNewBranch ? 0 : -1); // eslint-disable-line vue/no-side-effects-in-computed-properties
- return items;
- },
- showNoResults() {
- return this.filteredItems.length === 0 && !this.showCreateNewBranch;
- },
- showCreateNewBranch() {
- if (this.disableCreateBranch || !this.searchTerm) {
- return false;
- }
-
- return this.items.filter((item) => item.name.toLowerCase() === this.searchTerm.toLowerCase()).length === 0;
- }
- },
-
- watch: {
- menuVisible(visible) {
- if (visible) {
- this.focusSearchField();
- }
- }
- },
-
- beforeMount() {
- switch (data.viewType) {
- case 'tree':
- this.isViewTree = true;
- break;
- case 'tag':
- this.isViewTag = true;
- break;
- default:
- this.isViewBranch = true;
- break;
- }
-
- document.body.addEventListener('click', (event) => {
- if (elRoot.contains(event.target)) return;
- if (this.menuVisible) {
- this.menuVisible = false;
- }
- });
- },
-
- methods: {
- selectItem(item) {
- const prev = this.getSelected();
- if (prev !== null) {
- prev.selected = false;
- }
- item.selected = true;
- const url = (item.tag) ? this.tagURLPrefix + item.url + this.tagURLSuffix : this.branchURLPrefix + item.url + this.branchURLSuffix;
- if (!this.branchForm) {
- window.location.href = url;
- } else {
- this.isViewTree = false;
- this.isViewTag = false;
- this.isViewBranch = false;
- this.$refs.dropdownRefName.textContent = item.name;
- if (this.setAction) {
- $(`#${this.branchForm}`).attr('action', url);
- } else {
- $(`#${this.branchForm} input[name="refURL"]`).val(url);
- }
- $(`#${this.branchForm} input[name="ref"]`).val(item.name);
- if (item.tag) {
- this.isViewTag = true;
- $(`#${this.branchForm} input[name="refType"]`).val('tag');
- } else {
- this.isViewBranch = true;
- $(`#${this.branchForm} input[name="refType"]`).val('branch');
- }
- if (this.submitForm) {
- $(`#${this.branchForm}`).trigger('submit');
- }
- this.menuVisible = false;
- }
- },
- createNewBranch() {
- if (!this.showCreateNewBranch) return;
- $(this.$refs.newBranchForm).trigger('submit');
- },
- focusSearchField() {
- nextTick(() => {
- this.$refs.searchField.focus();
- });
- },
- getSelected() {
- for (let i = 0, j = this.items.length; i < j; ++i) {
- if (this.items[i].selected) return this.items[i];
- }
- return null;
- },
- getSelectedIndexInFiltered() {
- for (let i = 0, j = this.filteredItems.length; i < j; ++i) {
- if (this.filteredItems[i].selected) return i;
- }
- return -1;
- },
- scrollToActive() {
- let el = this.$refs[`listItem${this.active}`];
- if (!el || !el.length) return;
- if (Array.isArray(el)) {
- el = el[0];
- }
-
- const cont = this.$refs.scrollContainer;
- if (el.offsetTop < cont.scrollTop) {
- cont.scrollTop = el.offsetTop;
- } else if (el.offsetTop + el.clientHeight > cont.scrollTop + cont.clientHeight) {
- cont.scrollTop = el.offsetTop + el.clientHeight - cont.clientHeight;
- }
- },
- keydown(event) {
- if (event.keyCode === 40) { // arrow down
- event.preventDefault();
-
- if (this.active === -1) {
- this.active = this.getSelectedIndexInFiltered();
- }
-
- if (this.active + (this.showCreateNewBranch ? 0 : 1) >= this.filteredItems.length) {
- return;
- }
- this.active++;
- this.scrollToActive();
- } else if (event.keyCode === 38) { // arrow up
- event.preventDefault();
-
- if (this.active === -1) {
- this.active = this.getSelectedIndexInFiltered();
- }
-
- if (this.active <= 0) {
- return;
- }
- this.active--;
- this.scrollToActive();
- } else if (event.keyCode === 13) { // enter
- event.preventDefault();
-
- if (this.active >= this.filteredItems.length) {
- this.createNewBranch();
- } else if (this.active >= 0) {
- this.selectItem(this.filteredItems[this.active]);
- }
- } else if (event.keyCode === 27) { // escape
- event.preventDefault();
- this.menuVisible = false;
- }
- }
- }
- });
- view.mount(this);
- });
-}
diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue
new file mode 100644
index 000000000..6a65eeec6
--- /dev/null
+++ b/web_src/js/components/RepoBranchTagSelector.vue
@@ -0,0 +1,293 @@
+
+
+
+
+
+
+
+
diff --git a/web_src/js/features/repo-findfile.js b/web_src/js/features/repo-findfile.js
index 093f90fe8..078c822aa 100644
--- a/web_src/js/features/repo-findfile.js
+++ b/web_src/js/features/repo-findfile.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import {svg} from '../svg.js';
import {toggleElem} from '../utils/dom.js';
+import {pathEscapeSegments} from '../utils/url.js';
const {csrf} = window.config;
@@ -73,10 +74,6 @@ export function filterRepoFilesWeighted(files, filter) {
return filterResult;
}
-export function escapePath(s) {
- return s.split('/').map(encodeURIComponent).join('/');
-}
-
function filterRepoFiles(filter) {
const treeLink = $repoFindFileInput.attr('data-url-tree-link');
$repoFindFileTableBody.empty();
@@ -88,7 +85,7 @@ function filterRepoFiles(filter) {
for (const r of filterResult) {
const $row = $(tmplRow);
const $a = $row.find('a');
- $a.attr('href', `${treeLink}/${escapePath(r.matchResult.join(''))}`);
+ $a.attr('href', `${treeLink}/${pathEscapeSegments(r.matchResult.join(''))}`);
const $octiconFile = $(svg('octicon-file')).addClass('gt-mr-3');
$a.append($octiconFile);
// if the target file path is "abc/xyz", to search "bx", then the matchResult is ['a', 'b', 'c/', 'x', 'yz']
diff --git a/web_src/js/features/repo-findfile.test.js b/web_src/js/features/repo-findfile.test.js
index 503218539..a90b0bf0a 100644
--- a/web_src/js/features/repo-findfile.test.js
+++ b/web_src/js/features/repo-findfile.test.js
@@ -1,5 +1,5 @@
import {describe, expect, test} from 'vitest';
-import {strSubMatch, calcMatchedWeight, filterRepoFilesWeighted, escapePath} from './repo-findfile.js';
+import {strSubMatch, calcMatchedWeight, filterRepoFilesWeighted} from './repo-findfile.js';
describe('Repo Find Files', () => {
test('strSubMatch', () => {
@@ -32,9 +32,4 @@ describe('Repo Find Files', () => {
expect(res).toHaveLength(2);
expect(res[0].matchResult).toEqual(['', 'word', '.txt']);
});
-
- test('escapePath', () => {
- expect(escapePath('a/b/c')).toEqual('a/b/c');
- expect(escapePath('a/b/ c')).toEqual('a/b/%20c');
- });
});
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js
index 5346a0d27..4454b92cc 100644
--- a/web_src/js/features/repo-legacy.js
+++ b/web_src/js/features/repo-legacy.js
@@ -11,7 +11,7 @@ import {
import {initUnicodeEscapeButton} from './repo-unicode-escape.js';
import {svg} from '../svg.js';
import {htmlEscape} from 'escape-goat';
-import {initRepoBranchTagDropdown} from '../components/RepoBranchTagDropdown.js';
+import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue';
import {
initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown,
initRepoCommonLanguageStats,
@@ -486,7 +486,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 .ui.dropdown');
+ initRepoBranchTagSelector('.js-branch-tag-selector');
}
// Wiki
diff --git a/web_src/js/svg.js b/web_src/js/svg.js
index 9eabca3fd..e431ca57e 100644
--- a/web_src/js/svg.js
+++ b/web_src/js/svg.js
@@ -1,3 +1,4 @@
+import {h} from 'vue';
import octiconChevronDown from '../../public/img/svg/octicon-chevron-down.svg';
import octiconChevronRight from '../../public/img/svg/octicon-chevron-right.svg';
import octiconClock from '../../public/img/svg/octicon-clock.svg';
@@ -40,6 +41,8 @@ import giteaDoubleChevronLeft from '../../public/img/svg/gitea-double-chevron-le
import giteaDoubleChevronRight from '../../public/img/svg/gitea-double-chevron-right.svg';
import octiconChevronLeft from '../../public/img/svg/octicon-chevron-left.svg';
import octiconOrganization from '../../public/img/svg/octicon-organization.svg';
+import octiconTag from '../../public/img/svg/octicon-tag.svg';
+import octiconGitBranch from '../../public/img/svg/octicon-git-branch.svg';
const svgs = {
'octicon-blocked': octiconBlocked,
@@ -84,9 +87,13 @@ const svgs = {
'gitea-double-chevron-right': giteaDoubleChevronRight,
'octicon-chevron-left': octiconChevronLeft,
'octicon-organization': octiconOrganization,
+ 'octicon-tag': octiconTag,
+ 'octicon-git-branch': octiconGitBranch,
};
-// TODO: use a more general approach to access SVG icons. At the moment, developers must check, pick and fill the names manually, most of the SVG icons in assets couldn't be used directly.
+// TODO: use a more general approach to access SVG icons.
+// At the moment, developers must check, pick and fill the names manually,
+// most of the SVG icons in assets couldn't be used directly.
const parser = new DOMParser();
const serializer = new XMLSerializer();
@@ -112,12 +119,7 @@ export const SvgIcon = {
size: {type: Number, default: 16},
className: {type: String, default: ''},
},
-
- computed: {
- svg() {
- return svg(this.name, this.size, this.className);
- },
+ render() {
+ return h('span', {innerHTML: svg(this.name, this.size, this.className)});
},
-
- template: `
`
};
diff --git a/web_src/js/utils/url.js b/web_src/js/utils/url.js
new file mode 100644
index 000000000..a40737ca6
--- /dev/null
+++ b/web_src/js/utils/url.js
@@ -0,0 +1,3 @@
+export function pathEscapeSegments(s) {
+ return s.split('/').map(encodeURIComponent).join('/');
+}
diff --git a/web_src/js/utils/url.test.js b/web_src/js/utils/url.test.js
new file mode 100644
index 000000000..ef2ffaa5f
--- /dev/null
+++ b/web_src/js/utils/url.test.js
@@ -0,0 +1,7 @@
+import {expect, test} from 'vitest';
+import {pathEscapeSegments} from './url.js';
+
+test('pathEscapeSegments', () => {
+ expect(pathEscapeSegments('a/b/c')).toEqual('a/b/c');
+ expect(pathEscapeSegments('a/b/ c')).toEqual('a/b/%20c');
+});
diff --git a/web_src/less/_base.less b/web_src/less/_base.less
index 1cf65e784..cabf707aa 100644
--- a/web_src/less/_base.less
+++ b/web_src/less/_base.less
@@ -1924,10 +1924,6 @@ footer {
display: block;
}
-[v-cloak] {
- display: none !important;
-}
-
.repos-search {
padding-bottom: 0 !important;
}
diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less
index 0069a31ce..c842c4ca6 100644
--- a/web_src/less/_repository.less
+++ b/web_src/less/_repository.less
@@ -222,12 +222,6 @@
font-size: 1.2em;
}
- .choose.reference {
- .header .icon {
- font-size: 1.4em;
- }
- }
-
.repo-path {
.section,
diff --git a/webpack.config.js b/webpack.config.js
index 245791e7e..46bdd6acf 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -196,6 +196,10 @@ export default {
],
},
plugins: [
+ new webpack.DefinePlugin({
+ __VUE_OPTIONS_API__: true, // at the moment, many Vue components still use the Vue Options API
+ __VUE_PROD_DEVTOOLS__: false, // do not enable devtools support in production
+ }),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: 'css/[name].css',