diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2023-03-14 12:09:06 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-14 12:09:06 +0800 |
commit | e82f1b15c7120ad13fd3b67cf7e2c6cb9915c22d (patch) | |
tree | 1da00ac20e4f62bf55bbf68e914d27cd1920a5d6 /web_src/js/components | |
parent | b942838bd486f5d3919a14a128efe22fc55c6112 (diff) | |
download | gitea-e82f1b15c7120ad13fd3b67cf7e2c6cb9915c22d.tar.gz gitea-e82f1b15c7120ad13fd3b67cf7e2c6cb9915c22d.zip |
Refactor dashboard repo list to Vue SFC (#23405)
Similar to #23394
The dashboard repo list mixes jQuery/Fomantic UI/Vue together, it's very
diffcult to maintain and causes unfixable a11y problems.
This PR uses two steps to refactor the repo list:
1. move `data-` attributes to JS object and use Vue data as much as
possible
https://github.com/go-gitea/gitea/pull/23405/commits/d3adc0dcacf7de87b9819277e6598ac3993bbfa3
2. move the code into a Vue SFC
https://github.com/go-gitea/gitea/pull/23405/commits/7ebe55df6e67adfd272a4bf0a96ad6688edf661f
Total: +516 −585
Screenshots:
<details>
![image](https://user-images.githubusercontent.com/2114189/224271457-a23e05be-d7d3-4247-a803-f0ee30c36f44.png)
![image](https://user-images.githubusercontent.com/2114189/224271504-76fbd3da-4d7a-4725-b0d1-fbff83caac63.png)
![image](https://user-images.githubusercontent.com/2114189/224271845-f007cadf-6c49-46bd-a65c-a3fc75bdba3b.png)
</details>
---------
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Diffstat (limited to 'web_src/js/components')
-rw-r--r-- | web_src/js/components/DashboardRepoList.js | 345 | ||||
-rw-r--r-- | web_src/js/components/DashboardRepoList.vue | 432 | ||||
-rw-r--r-- | web_src/js/components/RepoActivityTopAuthors.vue | 9 | ||||
-rw-r--r-- | web_src/js/components/RepoBranchTagDropdown.js | 3 | ||||
-rw-r--r-- | web_src/js/components/VueComponentLoader.js | 49 |
5 files changed, 439 insertions, 399 deletions
diff --git a/web_src/js/components/DashboardRepoList.js b/web_src/js/components/DashboardRepoList.js deleted file mode 100644 index 2328cc83a9..0000000000 --- a/web_src/js/components/DashboardRepoList.js +++ /dev/null @@ -1,345 +0,0 @@ -import {createApp, nextTick} from 'vue'; -import $ from 'jquery'; -import {initVueSvg, vueDelimiters} from './VueComponentLoader.js'; -import {initTooltip} from '../modules/tippy.js'; - -const {appSubUrl, assetUrlPrefix, pageData} = window.config; - -function initVueComponents(app) { - app.component('repo-search', { - delimiters: vueDelimiters, - props: { - searchLimit: { - type: Number, - default: 10 - }, - subUrl: { - type: String, - required: true - }, - uid: { - type: Number, - default: 0 - }, - teamId: { - type: Number, - required: false, - default: 0 - }, - organizations: { - type: Array, - default: () => [], - }, - isOrganization: { - type: Boolean, - default: true - }, - canCreateOrganization: { - type: Boolean, - default: false - }, - organizationsTotalCount: { - type: Number, - default: 0 - }, - moreReposLink: { - type: String, - default: '' - } - }, - - data() { - const params = new URLSearchParams(window.location.search); - - let tab = params.get('repo-search-tab'); - if (!tab) { - tab = 'repos'; - } - - let reposFilter = params.get('repo-search-filter'); - if (!reposFilter) { - reposFilter = 'all'; - } - - let privateFilter = params.get('repo-search-private'); - if (!privateFilter) { - privateFilter = 'both'; - } - - let archivedFilter = params.get('repo-search-archived'); - if (!archivedFilter) { - archivedFilter = 'unarchived'; - } - - let searchQuery = params.get('repo-search-query'); - if (!searchQuery) { - searchQuery = ''; - } - - let page = 1; - try { - page = parseInt(params.get('repo-search-page')); - } catch { - // noop - } - if (!page) { - page = 1; - } - - return { - hasMounted: false, // accessing $refs in computed() need to wait for mounted - 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', - }, - } - }; - }, - - 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.hasMounted && this.$refs.checkboxArchivedFilter?.getAttribute(`data-title-${this.archivedFilter}`); - }, - checkboxArchivedFilterProps() { - return {checked: this.archivedFilter === 'archived', indeterminate: this.archivedFilter === 'both'}; - }, - checkboxPrivateFilterTitle() { - return this.hasMounted && this.$refs.checkboxPrivateFilter?.getAttribute(`data-title-${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.hasMounted = true; - }, - - 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, - data() { - return { - searchLimit: dashboardRepoListData.searchLimit || 0, - subUrl: appSubUrl, - uid: dashboardRepoListData.uid || 0, - }; - }, - }); - 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 0000000000..e295910fd0 --- /dev/null +++ b/web_src/js/components/DashboardRepoList.vue @@ -0,0 +1,432 @@ +<template> + <div> + <div v-if="!isOrganization" class="ui two item tabable menu"> + <a :class="{item: true, active: tab === 'repos'}" @click="changeTab('repos')">{{ textRepository }}</a> + <a :class="{item: true, active: tab === 'organizations'}" @click="changeTab('organizations')">{{ textOrganization }}</a> + </div> + <div v-show="tab === 'repos'" class="ui tab active list dashboard-repos"> + <h4 class="ui top attached header gt-df gt-ac"> + <div class="gt-f1 gt-df gt-ac"> + {{ textMyRepos }} + <span class="ui grey label gt-ml-3">{{ reposTotalCount }}</span> + </div> + <a class="tooltip" :href="subUrl + '/repo/create'" :data-content="textNewRepo" data-position="left center"> + <svg-icon name="octicon-plus"/> + <span class="sr-only">{{ textNewRepo }}</span> + </a> + </h4> + <div class="ui attached segment repos-search"> + <div class="ui fluid right action left icon input" :class="{loading: isLoading}"> + <input @input="changeReposFilter(reposFilter)" v-model="searchQuery" ref="search" :placeholder="textSearchRepos"> + <i class="icon gt-df gt-ac gt-jc"><svg-icon name="octicon-search" :size="16"/></i> + <div class="ui dropdown icon button" :title="textFilter"> + <i class="icon gt-df gt-ac gt-jc gt-m-0"><svg-icon name="octicon-filter" :size="16"/></i> + <div class="menu"> + <a class="item" @click="toggleArchivedFilter()"> + <div class="ui checkbox" ref="checkboxArchivedFilter" :title="checkboxArchivedFilterTitle"> + <!--the "hidden" is necessary to make the checkbox work without Fomantic UI js, + otherwise if the "input" handles click event for intermediate status, it breaks the internal state--> + <input type="checkbox" class="hidden" v-bind.prop="checkboxArchivedFilterProps"> + <label> + <svg-icon name="octicon-archive" :size="16" class-name="gt-mr-2"/> + {{ textShowArchived }} + </label> + </div> + </a> + <a class="item" @click="togglePrivateFilter()"> + <div class="ui checkbox" ref="checkboxPrivateFilter" :title="checkboxPrivateFilterTitle"> + <input type="checkbox" class="hidden" v-bind.prop="checkboxPrivateFilterProps"> + <label> + <svg-icon name="octicon-lock" :size="16" class-name="gt-mr-2"/> + {{ textShowPrivate }} + </label> + </div> + </a> + </div> + </div> + </div> + <div class="ui secondary tiny pointing borderless menu center grid repos-filter"> + <a class="item" :class="{active: reposFilter === 'all'}" @click="changeReposFilter('all')"> + {{ textAll }} + <div v-show="reposFilter === 'all'" class="ui circular mini grey label">{{ repoTypeCount }}</div> + </a> + <a class="item" :class="{active: reposFilter === 'sources'}" @click="changeReposFilter('sources')"> + {{ textSources }} + <div v-show="reposFilter === 'sources'" class="ui circular mini grey label">{{ repoTypeCount }}</div> + </a> + <a class="item" :class="{active: reposFilter === 'forks'}" @click="changeReposFilter('forks')"> + {{ textForks }} + <div v-show="reposFilter === 'forks'" class="ui circular mini grey label">{{ repoTypeCount }}</div> + </a> + <a class="item" :class="{active: reposFilter === 'mirrors'}" @click="changeReposFilter('mirrors')" v-if="isMirrorsEnabled"> + {{ textMirrors }} + <div v-show="reposFilter === 'mirrors'" class="ui circular mini grey label">{{ repoTypeCount }}</div> + </a> + <a class="item" :class="{active: reposFilter === 'collaborative'}" @click="changeReposFilter('collaborative')"> + {{ textCollaborative }} + <div v-show="reposFilter === 'collaborative'" class="ui circular mini grey label">{{ repoTypeCount }}</div> + </a> + </div> + </div> + <div v-if="repos.length" class="ui attached table segment gt-rounded-bottom"> + <ul class="repo-owner-name-list"> + <li v-for="repo in repos" :class="{'private': repo.private || repo.internal}" :key="repo.id"> + <a class="repo-list-link gt-df gt-ac gt-sb" :href="repo.link"> + <div class="item-name gt-df gt-ac gt-f1 gt-mr-2"> + <svg-icon :name="repoIcon(repo)" size="16" class-name="gt-mr-2"/> + <div class="text gt-bold truncate gt-ml-1">{{ repo.full_name }}</div> + <span v-if="repo.archived"> + <svg-icon name="octicon-archive" :size="16" class-name="gt-ml-2"/> + </span> + </div> + <div class="text light grey gt-df gt-ac" v-if="isStarsEnabled"> + {{ repo.stars_count }} + <svg-icon name="octicon-star" :size="16" class-name="gt-ml-2"/> + </div> + </a> + </li> + </ul> + <div v-if="showMoreReposLink" class="center gt-py-3 gt-border-secondary-top"> + <div class="ui borderless pagination menu narrow"> + <a + class="item navigation gt-py-2" :class="{'disabled': page === 1}" + @click="changePage(1)" :title="textFirstPage" + > + <svg-icon name="gitea-double-chevron-left" :size="16" class-name="gt-mr-2"/> + </a> + <a + class="item navigation gt-py-2" :class="{'disabled': page === 1}" + @click="changePage(page - 1)" :title="textPreviousPage" + > + <svg-icon name="octicon-chevron-left" :size="16" clsas-name="gt-mr-2"/> + </a> + <a class="active item gt-py-2">{{ page }}</a> + <a + class="item navigation" :class="{'disabled': page === finalPage}" + @click="changePage(page + 1)" :title="textNextPage" + > + <svg-icon name="octicon-chevron-right" :size="16" class-name="gt-ml-2"/> + </a> + <a + class="item navigation gt-py-2" :class="{'disabled': page === finalPage}" + @click="changePage(finalPage)" :title="textLastPage" + > + <svg-icon name="gitea-double-chevron-right" :size="16" class-name="gt-ml-2"/> + </a> + </div> + </div> + </div> + </div> + <div v-if="!isOrganization" v-show="tab === 'organizations'" class="ui tab active list dashboard-orgs"> + <h4 class="ui top attached header gt-df gt-ac"> + <div class="gt-f1 gt-df gt-ac"> + {{ textMyOrgs }} + <span class="ui grey label gt-ml-3">{{ organizationsTotalCount }}</span> + </div> + <a v-if="canCreateOrganization" class="tooltip" :href="subUrl + '/org/create'" :data-content="textNewOrg" data-position="left center"> + <svg-icon name="octicon-plus"/> + <span class="sr-only">{{ textNewOrg }}</span> + </a> + </h4> + <div v-if="organizations.length" class="ui attached table segment gt-rounded-bottom"> + <ul class="repo-owner-name-list"> + <li v-for="org in organizations" :key="org.name"> + <a class="repo-list-link gt-df gt-ac gt-sb" :href="subUrl + '/' + encodeURIComponent(org.name)"> + <div class="text truncate item-name gt-f1"> + <svg-icon name="octicon-organization" :size="16" class-name="gt-mr-2"/> + <strong>{{ org.name }}</strong> + </div> + <div class="text light grey gt-df gt-ac"> + {{ org.num_repos }} + <svg-icon name="octicon-repo" :size="16" class-name="gt-ml-2 gt-mt-1"/> + </div> + </a> + </li> + </ul> + </div> + </div> + </div> +</template> + +<script> +import {createApp, nextTick} from 'vue'; +import $ from 'jquery'; +import {initTooltip} from '../modules/tippy.js'; +import {SvgIcon} from '../svg.js'; + +const {appSubUrl, assetUrlPrefix, pageData} = window.config; + +const sfc = { + 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: { + 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'; + } + }, +}; + +export function initDashboardRepoList() { + const el = document.getElementById('dashboard-repo-list'); + if (el) { + createApp(sfc).mount(el); + } +} + +export default sfc; // activate the IDE's Vue plugin + +</script> diff --git a/web_src/js/components/RepoActivityTopAuthors.vue b/web_src/js/components/RepoActivityTopAuthors.vue index 37b6df9187..294ee6f7bc 100644 --- a/web_src/js/components/RepoActivityTopAuthors.vue +++ b/web_src/js/components/RepoActivityTopAuthors.vue @@ -51,7 +51,7 @@ <script> import VueBarGraph from 'vue-bar-graph'; -import {initVueApp} from './VueComponentLoader.js'; +import {createApp} from 'vue'; const sfc = { components: {VueBarGraph}, @@ -102,8 +102,11 @@ const sfc = { }; export function initRepoActivityTopAuthorsChart() { - initVueApp('#repo-activity-top-authors-chart', sfc); + const el = document.getElementById('repo-activity-top-authors-chart'); + if (el) { + createApp(sfc).mount(el); + } } -export default sfc; // this line is necessary to activate the IDE's Vue plugin +export default sfc; // activate the IDE's Vue plugin </script> diff --git a/web_src/js/components/RepoBranchTagDropdown.js b/web_src/js/components/RepoBranchTagDropdown.js index e1bf35c129..a8945b82d1 100644 --- a/web_src/js/components/RepoBranchTagDropdown.js +++ b/web_src/js/components/RepoBranchTagDropdown.js @@ -1,6 +1,5 @@ import {createApp, nextTick} from 'vue'; import $ from 'jquery'; -import {vueDelimiters} from './VueComponentLoader.js'; export function initRepoBranchTagDropdown(selector) { $(selector).each(function (dropdownIndex, elRoot) { @@ -39,7 +38,7 @@ export function initRepoBranchTagDropdown(selector) { } const view = createApp({ - delimiters: vueDelimiters, + delimiters: ['${', '}'], data() { return data; }, diff --git a/web_src/js/components/VueComponentLoader.js b/web_src/js/components/VueComponentLoader.js deleted file mode 100644 index 33ebf95eff..0000000000 --- a/web_src/js/components/VueComponentLoader.js +++ /dev/null @@ -1,49 +0,0 @@ -import {createApp} from 'vue'; -import {svgs} from '../svg.js'; - -export const vueDelimiters = ['${', '}']; - -let vueEnvInited = false; -export function initVueEnv() { - if (vueEnvInited) return; - vueEnvInited = true; - - // As far as I could tell, this is no longer possible. - // But there seem not to be a guide what to do instead. - // const isProd = window.config.runModeIsProd; - // Vue.config.devtools = !isProd; -} - -let vueSvgInited = false; -export function initVueSvg(app) { - if (vueSvgInited) return; - vueSvgInited = true; - - // register svg icon vue components, e.g. <octicon-repo size="16"/> - 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); - } - if (!el) return null; - - return createApp( - {delimiters: vueDelimiters, ...opts} - ).mount(el); -} |