diff --git a/templates/user/dashboard/repolist.tmpl b/templates/user/dashboard/repolist.tmpl
index 16168a76b89de..0a8f427f9da15 100644
--- a/templates/user/dashboard/repolist.tmpl
+++ b/templates/user/dashboard/repolist.tmpl
@@ -51,148 +51,4 @@ data.canCreateOrganization = {{.SignedUser.CanCreateOrganization}}
window.config.pageData.dashboardRepoList = data;
-
-
-
-
-
-
-
+
diff --git a/web_src/js/components/DashboardRepoList.js b/web_src/js/components/DashboardRepoList.js
deleted file mode 100644
index f18a3cca601b0..0000000000000
--- a/web_src/js/components/DashboardRepoList.js
+++ /dev/null
@@ -1,288 +0,0 @@
-import {createApp, nextTick} from 'vue';
-import $ from 'jquery';
-import {initVueSvg, vueDelimiters} from './VueComponentLoader.js';
-import {initTooltip} from '../modules/tippy.js';
-import {SvgIcon} from '../svg.js';
-
-const {appSubUrl, assetUrlPrefix, pageData} = window.config;
-
-function initVueComponents(app) {
- app.component('repo-search', {
- delimiters: vueDelimiters,
- components: {SvgIcon},
- data() {
- const params = new URLSearchParams(window.location.search);
- const tab = params.get('repo-search-tab') || 'repos';
- const reposFilter = params.get('repo-search-filter') || 'all';
- const privateFilter = params.get('repo-search-private') || 'both';
- const archivedFilter = params.get('repo-search-archived') || 'unarchived';
- const searchQuery = params.get('repo-search-query') || '';
- const page = Number(params.get('repo-search-page')) || 1;
-
- return {
- tab,
- repos: [],
- reposTotalCount: 0,
- reposFilter,
- archivedFilter,
- privateFilter,
- page,
- finalPage: 1,
- searchQuery,
- isLoading: false,
- staticPrefix: assetUrlPrefix,
- counts: {},
- repoTypes: {
- all: {
- searchMode: '',
- },
- forks: {
- searchMode: 'fork',
- },
- mirrors: {
- searchMode: 'mirror',
- },
- sources: {
- searchMode: 'source',
- },
- collaborative: {
- searchMode: 'collaborative',
- },
- },
- textArchivedFilterTitles: {},
- textPrivateFilterTitles: {},
-
- organizations: [],
- isOrganization: true,
- canCreateOrganization: false,
- organizationsTotalCount: 0,
-
- subUrl: appSubUrl,
- ...pageData.dashboardRepoList,
- };
- },
-
- computed: {
- // used in `repolist.tmpl`
- showMoreReposLink() {
- return this.repos.length > 0 && this.repos.length < this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`];
- },
- searchURL() {
- return `${this.subUrl}/repo/search?sort=updated&order=desc&uid=${this.uid}&team_id=${this.teamId}&q=${this.searchQuery
- }&page=${this.page}&limit=${this.searchLimit}&mode=${this.repoTypes[this.reposFilter].searchMode
- }${this.reposFilter !== 'all' ? '&exclusive=1' : ''
- }${this.archivedFilter === 'archived' ? '&archived=true' : ''}${this.archivedFilter === 'unarchived' ? '&archived=false' : ''
- }${this.privateFilter === 'private' ? '&is_private=true' : ''}${this.privateFilter === 'public' ? '&is_private=false' : ''
- }`;
- },
- repoTypeCount() {
- return this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`];
- },
- checkboxArchivedFilterTitle() {
- return this.textArchivedFilterTitles[this.archivedFilter];
- },
- checkboxArchivedFilterProps() {
- return {checked: this.archivedFilter === 'archived', indeterminate: this.archivedFilter === 'both'};
- },
- checkboxPrivateFilterTitle() {
- return this.textPrivateFilterTitles[this.privateFilter];
- },
- checkboxPrivateFilterProps() {
- return {checked: this.privateFilter === 'private', indeterminate: this.privateFilter === 'both'};
- },
- },
-
- mounted() {
- const el = document.getElementById('dashboard-repo-list');
- this.changeReposFilter(this.reposFilter);
- for (const elTooltip of el.querySelectorAll('.tooltip')) {
- initTooltip(elTooltip);
- }
- $(el).find('.dropdown').dropdown();
- nextTick(() => {
- this.$refs.search.focus();
- });
-
- this.textArchivedFilterTitles = {
- 'archived': this.textShowOnlyArchived,
- 'unarchived': this.textShowOnlyUnarchived,
- 'both': this.textShowBothArchivedUnarchived,
- };
-
- this.textPrivateFilterTitles = {
- 'private': this.textShowOnlyPrivate,
- 'public': this.textShowOnlyPublic,
- 'both': this.textShowBothPrivatePublic,
- };
- },
-
- methods: {
- changeTab(t) {
- this.tab = t;
- this.updateHistory();
- },
-
- changeReposFilter(filter) {
- this.reposFilter = filter;
- this.repos = [];
- this.page = 1;
- this.counts[`${filter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
- this.searchRepos();
- },
-
- updateHistory() {
- const params = new URLSearchParams(window.location.search);
-
- if (this.tab === 'repos') {
- params.delete('repo-search-tab');
- } else {
- params.set('repo-search-tab', this.tab);
- }
-
- if (this.reposFilter === 'all') {
- params.delete('repo-search-filter');
- } else {
- params.set('repo-search-filter', this.reposFilter);
- }
-
- if (this.privateFilter === 'both') {
- params.delete('repo-search-private');
- } else {
- params.set('repo-search-private', this.privateFilter);
- }
-
- if (this.archivedFilter === 'unarchived') {
- params.delete('repo-search-archived');
- } else {
- params.set('repo-search-archived', this.archivedFilter);
- }
-
- if (this.searchQuery === '') {
- params.delete('repo-search-query');
- } else {
- params.set('repo-search-query', this.searchQuery);
- }
-
- if (this.page === 1) {
- params.delete('repo-search-page');
- } else {
- params.set('repo-search-page', `${this.page}`);
- }
-
- const queryString = params.toString();
- if (queryString) {
- window.history.replaceState({}, '', `?${queryString}`);
- } else {
- window.history.replaceState({}, '', window.location.pathname);
- }
- },
-
- toggleArchivedFilter() {
- if (this.archivedFilter === 'unarchived') {
- this.archivedFilter = 'archived';
- } else if (this.archivedFilter === 'archived') {
- this.archivedFilter = 'both';
- } else { // including both
- this.archivedFilter = 'unarchived';
- }
- this.page = 1;
- this.repos = [];
- this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
- this.searchRepos();
- },
-
- togglePrivateFilter() {
- if (this.privateFilter === 'both') {
- this.privateFilter = 'public';
- } else if (this.privateFilter === 'public') {
- this.privateFilter = 'private';
- } else { // including private
- this.privateFilter = 'both';
- }
- this.page = 1;
- this.repos = [];
- this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
- this.searchRepos();
- },
-
-
- changePage(page) {
- this.page = page;
- if (this.page > this.finalPage) {
- this.page = this.finalPage;
- }
- if (this.page < 1) {
- this.page = 1;
- }
- this.repos = [];
- this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
- this.searchRepos();
- },
-
- async searchRepos() {
- this.isLoading = true;
-
- const searchedMode = this.repoTypes[this.reposFilter].searchMode;
- const searchedURL = this.searchURL;
- const searchedQuery = this.searchQuery;
-
- let response, json;
- try {
- if (!this.reposTotalCount) {
- const totalCountSearchURL = `${this.subUrl}/repo/search?count_only=1&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`;
- response = await fetch(totalCountSearchURL);
- this.reposTotalCount = response.headers.get('X-Total-Count');
- }
-
- response = await fetch(searchedURL);
- json = await response.json();
- } catch {
- if (searchedURL === this.searchURL) {
- this.isLoading = false;
- }
- return;
- }
-
- if (searchedURL === this.searchURL) {
- this.repos = json.data;
- const count = response.headers.get('X-Total-Count');
- if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') {
- this.reposTotalCount = count;
- }
- this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = count;
- this.finalPage = Math.ceil(count / this.searchLimit);
- this.updateHistory();
- this.isLoading = false;
- }
- },
-
- repoIcon(repo) {
- if (repo.fork) {
- return 'octicon-repo-forked';
- } else if (repo.mirror) {
- return 'octicon-mirror';
- } else if (repo.template) {
- return `octicon-repo-template`;
- } else if (repo.private) {
- return 'octicon-lock';
- } else if (repo.internal) {
- return 'octicon-repo';
- }
- return 'octicon-repo';
- }
- },
-
- template: document.getElementById('dashboard-repo-list-template'),
- });
-}
-
-export function initDashboardRepoList() {
- const el = document.getElementById('dashboard-repo-list');
- const dashboardRepoListData = pageData.dashboardRepoList || null;
- if (!el || !dashboardRepoListData) return;
-
- const app = createApp({delimiters: vueDelimiters});
- initVueSvg(app);
- initVueComponents(app);
- app.mount(el);
-}
diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue
new file mode 100644
index 0000000000000..9692fe6050590
--- /dev/null
+++ b/web_src/js/components/DashboardRepoList.vue
@@ -0,0 +1,431 @@
+
+
+
+
+
diff --git a/web_src/js/components/VueComponentLoader.js b/web_src/js/components/VueComponentLoader.js
index 33ebf95eff96e..5f3bb237e2c5b 100644
--- a/web_src/js/components/VueComponentLoader.js
+++ b/web_src/js/components/VueComponentLoader.js
@@ -1,5 +1,4 @@
import {createApp} from 'vue';
-import {svgs} from '../svg.js';
export const vueDelimiters = ['${', '}'];
@@ -14,29 +13,6 @@ export function initVueEnv() {
// Vue.config.devtools = !isProd;
}
-let vueSvgInited = false;
-export function initVueSvg(app) {
- if (vueSvgInited) return;
- vueSvgInited = true;
-
- // register svg icon vue components, e.g.
- for (const [name, htmlString] of Object.entries(svgs)) {
- const template = htmlString
- .replace(/height="[0-9]+"/, 'v-bind:height="size"')
- .replace(/width="[0-9]+"/, 'v-bind:width="size"');
-
- app.component(name, {
- props: {
- size: {
- type: String,
- default: '16',
- },
- },
- template,
- });
- }
-}
-
export function initVueApp(el, opts = {}) {
if (typeof el === 'string') {
el = document.querySelector(el);
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 611c09d2b8c16..0469cbfa33915 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -4,7 +4,7 @@ import './bootstrap.js';
import $ from 'jquery';
import {initVueEnv} from './components/VueComponentLoader.js';
import {initRepoActivityTopAuthorsChart} from './components/RepoActivityTopAuthors.vue';
-import {initDashboardRepoList} from './components/DashboardRepoList.js';
+import {initDashboardRepoList} from './components/DashboardRepoList.vue';
import {attachTribute} from './features/tribute.js';
import {initGlobalCopyToClipboardListener} from './features/clipboard.js';
diff --git a/web_src/js/svg.js b/web_src/js/svg.js
index 85bfd501539e5..e77d45d4d6196 100644
--- a/web_src/js/svg.js
+++ b/web_src/js/svg.js
@@ -41,7 +41,7 @@ import giteaDoubleChevronRight from '../../public/img/svg/gitea-double-chevron-r
import octiconChevronLeft from '../../public/img/svg/octicon-chevron-left.svg';
import octiconOrganization from '../../public/img/svg/octicon-organization.svg';
-export const svgs = {
+const svgs = {
'octicon-blocked': octiconBlocked,
'octicon-check-circle-fill': octiconCheckCircleFill,
'octicon-chevron-down': octiconChevronDown,