Frontend refactor: move Vue related code from index.js
to components
dir, and remove unused codes. (#17301)
* frontend refactor * Apply suggestions from code review Co-authored-by: delvh <dev.lh@web.de> * Update templates/base/head.tmpl Co-authored-by: delvh <dev.lh@web.de> * Update docs/content/doc/developers/guidelines-frontend.md Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> * fix typo * fix typo * refactor PageData to pageData * Apply suggestions from code review Co-authored-by: delvh <dev.lh@web.de> * Simply for the visual difference. Co-authored-by: delvh <dev.lh@web.de> * Revert "Apply suggestions from code review" This reverts commit 4d78ad9b0e96ca180e0823de17659a2e0814c099. Co-authored-by: delvh <dev.lh@web.de> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
parent
96ff3e310f
commit
56362043d3
20 changed files with 718 additions and 634 deletions
|
@ -70,4 +70,3 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped/>
|
||||
|
|
370
web_src/js/components/DashboardRepoList.js
Normal file
370
web_src/js/components/DashboardRepoList.js
Normal file
|
@ -0,0 +1,370 @@
|
|||
import Vue from 'vue';
|
||||
import {initVueSvg, vueDelimiters} from './VueComponentLoader.js';
|
||||
|
||||
const {AppSubUrl, AssetUrlPrefix, pageData} = window.config;
|
||||
|
||||
function initVueComponents() {
|
||||
Vue.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 {
|
||||
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}/api/v1/repos/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}`];
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.changeReposFilter(this.reposFilter);
|
||||
$(this.$el).find('.poping.up').popup();
|
||||
$(this.$el).find('.dropdown').dropdown();
|
||||
this.setCheckboxes();
|
||||
Vue.nextTick(() => {
|
||||
this.$refs.search.focus();
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeTab(t) {
|
||||
this.tab = t;
|
||||
this.updateHistory();
|
||||
},
|
||||
|
||||
setCheckboxes() {
|
||||
switch (this.archivedFilter) {
|
||||
case 'unarchived':
|
||||
$('#archivedFilterCheckbox').checkbox('set unchecked');
|
||||
break;
|
||||
case 'archived':
|
||||
$('#archivedFilterCheckbox').checkbox('set checked');
|
||||
break;
|
||||
case 'both':
|
||||
$('#archivedFilterCheckbox').checkbox('set indeterminate');
|
||||
break;
|
||||
default:
|
||||
this.archivedFilter = 'unarchived';
|
||||
$('#archivedFilterCheckbox').checkbox('set unchecked');
|
||||
break;
|
||||
}
|
||||
switch (this.privateFilter) {
|
||||
case 'public':
|
||||
$('#privateFilterCheckbox').checkbox('set unchecked');
|
||||
break;
|
||||
case 'private':
|
||||
$('#privateFilterCheckbox').checkbox('set checked');
|
||||
break;
|
||||
case 'both':
|
||||
$('#privateFilterCheckbox').checkbox('set indeterminate');
|
||||
break;
|
||||
default:
|
||||
this.privateFilter = 'both';
|
||||
$('#privateFilterCheckbox').checkbox('set indeterminate');
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
changeReposFilter(filter) {
|
||||
this.reposFilter = filter;
|
||||
this.repos = [];
|
||||
this.page = 1;
|
||||
Vue.set(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() {
|
||||
switch (this.archivedFilter) {
|
||||
case 'both':
|
||||
this.archivedFilter = 'unarchived';
|
||||
break;
|
||||
case 'unarchived':
|
||||
this.archivedFilter = 'archived';
|
||||
break;
|
||||
case 'archived':
|
||||
this.archivedFilter = 'both';
|
||||
break;
|
||||
default:
|
||||
this.archivedFilter = 'unarchived';
|
||||
break;
|
||||
}
|
||||
this.page = 1;
|
||||
this.repos = [];
|
||||
this.setCheckboxes();
|
||||
Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
|
||||
this.searchRepos();
|
||||
},
|
||||
|
||||
togglePrivateFilter() {
|
||||
switch (this.privateFilter) {
|
||||
case 'both':
|
||||
this.privateFilter = 'public';
|
||||
break;
|
||||
case 'public':
|
||||
this.privateFilter = 'private';
|
||||
break;
|
||||
case 'private':
|
||||
this.privateFilter = 'both';
|
||||
break;
|
||||
default:
|
||||
this.privateFilter = 'both';
|
||||
break;
|
||||
}
|
||||
this.page = 1;
|
||||
this.repos = [];
|
||||
this.setCheckboxes();
|
||||
Vue.set(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 = [];
|
||||
Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
|
||||
this.searchRepos();
|
||||
},
|
||||
|
||||
searchRepos() {
|
||||
this.isLoading = true;
|
||||
|
||||
if (!this.reposTotalCount) {
|
||||
const totalCountSearchURL = `${this.subUrl}/api/v1/repos/search?sort=updated&order=desc&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`;
|
||||
$.getJSON(totalCountSearchURL, (_result, _textStatus, request) => {
|
||||
this.reposTotalCount = request.getResponseHeader('X-Total-Count');
|
||||
});
|
||||
}
|
||||
|
||||
const searchedMode = this.repoTypes[this.reposFilter].searchMode;
|
||||
const searchedURL = this.searchURL;
|
||||
const searchedQuery = this.searchQuery;
|
||||
|
||||
$.getJSON(searchedURL, (result, _textStatus, request) => {
|
||||
if (searchedURL === this.searchURL) {
|
||||
this.repos = result.data;
|
||||
const count = request.getResponseHeader('X-Total-Count');
|
||||
if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') {
|
||||
this.reposTotalCount = count;
|
||||
}
|
||||
Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, count);
|
||||
this.finalPage = Math.ceil(count / this.searchLimit);
|
||||
this.updateHistory();
|
||||
}
|
||||
}).always(() => {
|
||||
if (searchedURL === this.searchURL) {
|
||||
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';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function initDashboardRepoList() {
|
||||
const el = document.getElementById('dashboard-repo-list');
|
||||
const dashboardRepoListData = pageData.dashboardRepoList || null;
|
||||
if (!el || !dashboardRepoListData) return;
|
||||
|
||||
initVueSvg();
|
||||
initVueComponents();
|
||||
new Vue({
|
||||
el,
|
||||
delimiters: vueDelimiters,
|
||||
data: () => {
|
||||
return {
|
||||
searchLimit: dashboardRepoListData.searchLimit || 0,
|
||||
subUrl: AppSubUrl,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export {initDashboardRepoList};
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="activity-bar-graph" ref="style" style="width:0px;height:0px"/>
|
||||
<div class="activity-bar-graph-alt" ref="altStyle" style="width:0px;height:0px"/>
|
||||
<div class="activity-bar-graph" ref="style" style="width: 0; height: 0;"/>
|
||||
<div class="activity-bar-graph-alt" ref="altStyle" style="width: 0; height: 0;"/>
|
||||
<vue-bar-graph
|
||||
:points="graphData"
|
||||
:points="graphPoints"
|
||||
:show-x-axis="true"
|
||||
:show-y-axis="false"
|
||||
:show-values="true"
|
||||
|
@ -15,9 +15,9 @@
|
|||
:label-height="20"
|
||||
>
|
||||
<template #label="opt">
|
||||
<g v-for="(author, idx) in authors" :key="author.position">
|
||||
<g v-for="(author, idx) in graphAuthors" :key="author.position">
|
||||
<a
|
||||
v-if="opt.bar.index === idx && author.home_link !== ''"
|
||||
v-if="opt.bar.index === idx && author.home_link"
|
||||
:href="author.home_link"
|
||||
>
|
||||
<image
|
||||
|
@ -39,7 +39,7 @@
|
|||
</g>
|
||||
</template>
|
||||
<template #title="opt">
|
||||
<tspan v-for="(author, idx) in authors" :key="author.position">
|
||||
<tspan v-for="(author, idx) in graphAuthors" :key="author.position">
|
||||
<tspan v-if="opt.bar.index === idx">
|
||||
{{ author.name }}
|
||||
</tspan>
|
||||
|
@ -48,32 +48,39 @@
|
|||
</vue-bar-graph>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueBarGraph from 'vue-bar-graph';
|
||||
import {initVueApp} from './VueComponentLoader.js';
|
||||
|
||||
export default {
|
||||
const sfc = {
|
||||
components: {VueBarGraph},
|
||||
props: {
|
||||
data: {type: Array, default: () => []},
|
||||
},
|
||||
data: () => ({
|
||||
colors: {
|
||||
barColor: 'green',
|
||||
textColor: 'black',
|
||||
textAltColor: 'white',
|
||||
},
|
||||
|
||||
// possible keys:
|
||||
// * avatar_link: (...)
|
||||
// * commits: (...)
|
||||
// * home_link: (...)
|
||||
// * login: (...)
|
||||
// * name: (...)
|
||||
activityTopAuthors: window.config.pageData.repoActivityTopAuthors || [],
|
||||
}),
|
||||
computed: {
|
||||
graphData() {
|
||||
return this.data.map((item) => {
|
||||
graphPoints() {
|
||||
return this.activityTopAuthors.map((item) => {
|
||||
return {
|
||||
value: item.commits,
|
||||
label: item.name,
|
||||
};
|
||||
});
|
||||
},
|
||||
authors() {
|
||||
return this.data.map((item, idx) => {
|
||||
graphAuthors() {
|
||||
return this.activityTopAuthors.map((item, idx) => {
|
||||
return {
|
||||
position: idx + 1,
|
||||
...item,
|
||||
|
@ -81,21 +88,23 @@ export default {
|
|||
});
|
||||
},
|
||||
graphWidth() {
|
||||
return this.data.length * 40;
|
||||
return this.activityTopAuthors.length * 40;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const st = window.getComputedStyle(this.$refs.style);
|
||||
const stalt = window.getComputedStyle(this.$refs.altStyle);
|
||||
const refStyle = window.getComputedStyle(this.$refs.style);
|
||||
const refAltStyle = window.getComputedStyle(this.$refs.altStyle);
|
||||
|
||||
this.colors.barColor = st.backgroundColor;
|
||||
this.colors.textColor = st.color;
|
||||
this.colors.textAltColor = stalt.color;
|
||||
},
|
||||
methods: {
|
||||
hasHomeLink(i) {
|
||||
return this.graphData[i].homeLink !== '' && this.graphData[i].homeLink !== null;
|
||||
},
|
||||
this.colors.barColor = refStyle.backgroundColor;
|
||||
this.colors.textColor = refStyle.color;
|
||||
this.colors.textAltColor = refAltStyle.color;
|
||||
}
|
||||
};
|
||||
|
||||
function initRepoActivityTopAuthorsChart() {
|
||||
initVueApp('#repo-activity-top-authors-chart', sfc);
|
||||
}
|
||||
|
||||
export default sfc;
|
||||
export {initRepoActivityTopAuthorsChart};
|
||||
</script>
|
161
web_src/js/components/RepoBranchTagDropdown.js
Normal file
161
web_src/js/components/RepoBranchTagDropdown.js
Normal file
|
@ -0,0 +1,161 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
function initRepoBranchTagDropdown(selector) {
|
||||
$(selector).each(function () {
|
||||
const $dropdown = $(this);
|
||||
const $data = $dropdown.find('.data');
|
||||
const data = {
|
||||
items: [],
|
||||
mode: $data.data('mode'),
|
||||
searchTerm: '',
|
||||
noResults: '',
|
||||
canCreateBranch: false,
|
||||
menuVisible: false,
|
||||
createTag: false,
|
||||
active: 0
|
||||
};
|
||||
$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();
|
||||
new Vue({
|
||||
el: this,
|
||||
delimiters: ['${', '}'],
|
||||
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.canCreateBranch || !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() {
|
||||
this.noResults = this.$el.getAttribute('data-no-results');
|
||||
this.canCreateBranch = this.$el.getAttribute('data-can-create-branch') === 'true';
|
||||
|
||||
document.body.addEventListener('click', (event) => {
|
||||
if (this.$el.contains(event.target)) return;
|
||||
if (this.menuVisible) {
|
||||
Vue.set(this, 'menuVisible', false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
selectItem(item) {
|
||||
const prev = this.getSelected();
|
||||
if (prev !== null) {
|
||||
prev.selected = false;
|
||||
}
|
||||
item.selected = true;
|
||||
window.location.href = item.url;
|
||||
},
|
||||
createNewBranch() {
|
||||
if (!this.showCreateNewBranch) return;
|
||||
$(this.$refs.newBranchForm).trigger('submit');
|
||||
},
|
||||
focusSearchField() {
|
||||
Vue.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export {initRepoBranchTagDropdown};
|
52
web_src/js/components/VueComponentLoader.js
Normal file
52
web_src/js/components/VueComponentLoader.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
import Vue from 'vue';
|
||||
import {svgs} from '../svg.js';
|
||||
|
||||
const vueDelimiters = ['${', '}'];
|
||||
|
||||
let vueEnvInited = false;
|
||||
function initVueEnv() {
|
||||
if (vueEnvInited) return;
|
||||
vueEnvInited = true;
|
||||
|
||||
const isProd = window.config.IsProd;
|
||||
Vue.config.productionTip = false;
|
||||
Vue.config.devtools = !isProd;
|
||||
}
|
||||
|
||||
let vueSvgInited = false;
|
||||
function initVueSvg() {
|
||||
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"');
|
||||
|
||||
Vue.component(name, {
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
default: '16',
|
||||
},
|
||||
},
|
||||
template,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function initVueApp(el, opts = {}) {
|
||||
if (typeof el === 'string') {
|
||||
el = document.querySelector(el);
|
||||
}
|
||||
if (!el) return null;
|
||||
|
||||
return new Vue(Object.assign({
|
||||
el,
|
||||
delimiters: vueDelimiters,
|
||||
}, opts));
|
||||
}
|
||||
|
||||
export {vueDelimiters, initVueEnv, initVueSvg, initVueApp};
|
Loading…
Add table
Add a link
Reference in a new issue