Skip to content

Commit 04e97b8

Browse files
authored
Refactor from Vue2 to Vue3 (#20044)
Close #19902
1 parent 726afe8 commit 04e97b8

18 files changed

+379
-601
lines changed

package-lock.json

+271-490
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"@claviska/jquery-minicolors": "2.3.6",
1111
"@mcaptcha/vanilla-glue": "0.1.0-alpha-2",
1212
"@primer/octicons": "17.5.0",
13+
"@vue/compiler-sfc": "3.2.37",
1314
"add-asset-webpack-plugin": "2.0.1",
1415
"css-loader": "6.7.1",
1516
"dropzone": "6.0.0-beta.2",
@@ -34,11 +35,10 @@
3435
"tippy.js": "6.3.7",
3536
"tributejs": "5.1.3",
3637
"uint8-to-base64": "0.2.0",
37-
"vue": "2.6.14",
38-
"vue-bar-graph": "1.3.1",
39-
"vue-calendar-heatmap": "0.8.4",
40-
"vue-loader": "15.9.8",
41-
"vue-template-compiler": "2.6.14",
38+
"vue": "3.2.37",
39+
"vue-bar-graph": "2.0.0",
40+
"vue-loader": "17.0.0",
41+
"vue3-calendar-heatmap": "2.0.0",
4242
"webpack": "5.74.0",
4343
"webpack-cli": "4.10.0",
4444
"workbox-routing": "6.5.4",

templates/repo/diff/box.tmpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@
6363
}
6464
})();
6565
</script>
66-
<div id="diff-file-list-container"></div>
66+
<div id="diff-file-list"></div>
6767
<div id="diff-container">
68-
<div id="diff-file-tree-container"></div>
68+
<div id="diff-file-tree"></div>
6969
<div id="diff-file-boxes" class="sixteen wide column">
7070
{{range $i, $file := .Diff.Files}}
7171
{{/*notice: the index of Diff.Files should not be used for element ID, because the index will be restarted from 0 when doing load-more for PRs with a lot of files*/}}

templates/user/dashboard/repolist.tmpl

+5-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
{{end}}
2020
inline-template
2121
v-cloak
22-
>
22+
></repo-search>
23+
</div>
24+
25+
<template id="dashboard-repo-list-template">
2326
<div>
2427
<div v-if="!isOrganization" class="ui two item tabable menu">
2528
<a :class="{item: true, active: tab === 'repos'}" @click="changeTab('repos')">{{.locale.Tr "repository"}}</a>
@@ -193,5 +196,4 @@
193196
</div>
194197
</div>
195198
</div>
196-
</repo-search>
197-
</div>
199+
</template>

web_src/js/components/ActivityHeatmap.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
</div>
1616
</template>
1717
<script>
18-
import {CalendarHeatmap} from 'vue-calendar-heatmap';
18+
import {CalendarHeatmap} from 'vue3-calendar-heatmap';
1919
2020
export default {
2121
name: 'ActivityHeatmap',

web_src/js/components/ContextPopup.vue

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div>
2+
<div ref="root">
33
<div v-if="loading" class="ui active centered inline loader"/>
44
<div v-if="!loading && issue !== null">
55
<p><small>{{ issue.repository.full_name }} on {{ createdAt }}</small></p>
@@ -109,15 +109,16 @@ export default {
109109
},
110110
111111
mounted() {
112-
this.$root.$on('load-context-popup', (data, callback) => {
112+
this.$refs.root.addEventListener('us-load-context-popup', (e) => {
113+
const data = e.detail;
113114
if (!this.loading && this.issue === null) {
114-
this.load(data, callback);
115+
this.load(data);
115116
}
116117
});
117118
},
118119
119120
methods: {
120-
load(data, callback) {
121+
load(data) {
121122
this.loading = true;
122123
this.i18nErrorMessage = null;
123124
$.get(`${appSubUrl}/${data.owner}/${data.repo}/issues/${data.index}/info`).done((issue) => {
@@ -130,9 +131,6 @@ export default {
130131
}
131132
}).always(() => {
132133
this.loading = false;
133-
if (callback) {
134-
this.$nextTick(callback);
135-
}
136134
});
137135
}
138136
}

web_src/js/components/DashboardRepoList.js

+21-19
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import Vue from 'vue';
1+
import {createApp, nextTick} from 'vue';
22
import $ from 'jquery';
33
import {initVueSvg, vueDelimiters} from './VueComponentLoader.js';
44
import {initTooltip} from '../modules/tippy.js';
55

66
const {appSubUrl, assetUrlPrefix, pageData} = window.config;
77

8-
function initVueComponents() {
9-
Vue.component('repo-search', {
8+
function initVueComponents(app) {
9+
app.component('repo-search', {
1010
delimiters: vueDelimiters,
1111
props: {
1212
searchLimit: {
@@ -138,13 +138,14 @@ function initVueComponents() {
138138
},
139139

140140
mounted() {
141+
const el = document.getElementById('dashboard-repo-list');
141142
this.changeReposFilter(this.reposFilter);
142-
for (const el of this.$el.querySelectorAll('.tooltip')) {
143-
initTooltip(el);
143+
for (const elTooltip of el.querySelectorAll('.tooltip')) {
144+
initTooltip(elTooltip);
144145
}
145-
$(this.$el).find('.dropdown').dropdown();
146+
$(el).find('.dropdown').dropdown();
146147
this.setCheckboxes();
147-
Vue.nextTick(() => {
148+
nextTick(() => {
148149
this.$refs.search.focus();
149150
});
150151
},
@@ -192,7 +193,7 @@ function initVueComponents() {
192193
this.reposFilter = filter;
193194
this.repos = [];
194195
this.page = 1;
195-
Vue.set(this.counts, `${filter}:${this.archivedFilter}:${this.privateFilter}`, 0);
196+
this.counts[`${filter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
196197
this.searchRepos();
197198
},
198199

@@ -261,7 +262,7 @@ function initVueComponents() {
261262
this.page = 1;
262263
this.repos = [];
263264
this.setCheckboxes();
264-
Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
265+
this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
265266
this.searchRepos();
266267
},
267268

@@ -283,7 +284,7 @@ function initVueComponents() {
283284
this.page = 1;
284285
this.repos = [];
285286
this.setCheckboxes();
286-
Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
287+
this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
287288
this.searchRepos();
288289
},
289290

@@ -297,7 +298,7 @@ function initVueComponents() {
297298
this.page = 1;
298299
}
299300
this.repos = [];
300-
Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
301+
this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
301302
this.searchRepos();
302303
},
303304

@@ -331,7 +332,7 @@ function initVueComponents() {
331332
if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') {
332333
this.reposTotalCount = count;
333334
}
334-
Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, count);
335+
this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = count;
335336
this.finalPage = Math.ceil(count / this.searchLimit);
336337
this.updateHistory();
337338
this.isLoading = false;
@@ -352,27 +353,28 @@ function initVueComponents() {
352353
}
353354
return 'octicon-repo';
354355
}
355-
}
356+
},
357+
358+
template: document.getElementById('dashboard-repo-list-template'),
356359
});
357360
}
358361

359-
360362
export function initDashboardRepoList() {
361363
const el = document.getElementById('dashboard-repo-list');
362364
const dashboardRepoListData = pageData.dashboardRepoList || null;
363365
if (!el || !dashboardRepoListData) return;
364366

365-
initVueSvg();
366-
initVueComponents();
367-
new Vue({
368-
el,
367+
const app = createApp({
369368
delimiters: vueDelimiters,
370-
data: () => {
369+
data() {
371370
return {
372371
searchLimit: dashboardRepoListData.searchLimit || 0,
373372
subUrl: appSubUrl,
374373
uid: dashboardRepoListData.uid || 0,
375374
};
376375
},
377376
});
377+
initVueSvg(app);
378+
initVueComponents(app);
379+
app.mount(el);
378380
}

web_src/js/components/DiffFileList.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<ol class="diff-detail-box diff-stats m-0" id="diff-files" v-if="fileListIsVisible">
2+
<ol class="diff-detail-box diff-stats m-0" ref="root" v-if="fileListIsVisible">
33
<li v-for="file in files" :key="file.NameHash">
44
<div class="bold df ac pull-right">
55
<span v-if="file.IsBin" class="ml-1 mr-3">{{ binaryFileMessage }}</span>
@@ -37,7 +37,7 @@ export default {
3737
fileListIsVisible(newValue) {
3838
if (newValue === true) {
3939
this.$nextTick(() => {
40-
for (const el of this.$el.querySelectorAll('.tooltip')) {
40+
for (const el of this.$refs.root.querySelectorAll('.tooltip')) {
4141
initTooltip(el);
4242
}
4343
});

web_src/js/components/DiffFileTree.vue

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
<template>
22
<div
3-
v-show="fileTreeIsVisible"
4-
id="diff-file-tree"
3+
v-if="fileTreeIsVisible"
54
class="mr-3 mt-3 diff-detail-box"
65
>
76
<!-- only render the tree if we're visible. in many cases this is something that doesn't change very often -->
8-
<div class="ui list" v-if="fileTreeIsVisible">
7+
<div class="ui list">
98
<DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item" />
109
</div>
1110
<div v-if="isIncomplete" id="diff-too-many-files-stats" class="pt-2">
@@ -117,6 +116,9 @@ export default {
117116
const [toShow, toHide] = document.querySelectorAll('.diff-toggle-file-tree-button .icon');
118117
toShow.classList.toggle('hide', visible); // hide the toShow icon if the tree is visible
119118
toHide.classList.toggle('hide', !visible); // similarly
119+
120+
const diffTree = document.getElementById('diff-file-tree');
121+
diffTree.classList.toggle('hide', !visible);
120122
},
121123
loadMoreData() {
122124
this.isLoadingNewData = true;

web_src/js/components/RepoBranchTagDropdown.js

+24-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Vue from 'vue';
1+
import {createApp, nextTick} from 'vue';
22
import $ from 'jquery';
33
import {vueDelimiters} from './VueComponentLoader.js';
44

@@ -37,10 +37,14 @@ export function initRepoBranchTagDropdown(selector) {
3737
});
3838
});
3939
$data.remove();
40-
new Vue({
41-
el: this,
40+
41+
// eslint-disable-next-line unicorn/no-this-assignment
42+
const elRoot = this;
43+
const view = createApp({
4244
delimiters: vueDelimiters,
43-
data,
45+
data() {
46+
return data;
47+
},
4448
computed: {
4549
filteredItems() {
4650
const items = this.items.filter((item) => {
@@ -73,10 +77,10 @@ export function initRepoBranchTagDropdown(selector) {
7377
},
7478

7579
beforeMount() {
76-
this.noResults = this.$el.getAttribute('data-no-results');
77-
this.canCreateBranch = this.$el.getAttribute('data-can-create-branch') === 'true';
78-
this.branchForm = this.$el.getAttribute('data-branch-form');
79-
switch (this.$el.getAttribute('data-view-type')) {
80+
this.noResults = elRoot.getAttribute('data-no-results');
81+
this.canCreateBranch = elRoot.getAttribute('data-can-create-branch') === 'true';
82+
this.branchForm = elRoot.getAttribute('data-branch-form');
83+
switch (elRoot.getAttribute('data-view-type')) {
8084
case 'tree':
8185
this.isViewTree = true;
8286
break;
@@ -87,19 +91,19 @@ export function initRepoBranchTagDropdown(selector) {
8791
this.isViewBranch = true;
8892
break;
8993
}
90-
this.refName = this.$el.getAttribute('data-ref-name');
91-
this.branchURLPrefix = this.$el.getAttribute('data-branch-url-prefix');
92-
this.branchURLSuffix = this.$el.getAttribute('data-branch-url-suffix');
93-
this.tagURLPrefix = this.$el.getAttribute('data-tag-url-prefix');
94-
this.tagURLSuffix = this.$el.getAttribute('data-tag-url-suffix');
95-
this.setAction = this.$el.getAttribute('data-set-action') === 'true';
96-
this.submitForm = this.$el.getAttribute('data-submit-form') === 'true';
94+
this.refName = elRoot.getAttribute('data-ref-name');
95+
this.branchURLPrefix = elRoot.getAttribute('data-branch-url-prefix');
96+
this.branchURLSuffix = elRoot.getAttribute('data-branch-url-suffix');
97+
this.tagURLPrefix = elRoot.getAttribute('data-tag-url-prefix');
98+
this.tagURLSuffix = elRoot.getAttribute('data-tag-url-suffix');
99+
this.setAction = elRoot.getAttribute('data-set-action') === 'true';
100+
this.submitForm = elRoot.getAttribute('data-submit-form') === 'true';
97101

98102

99103
document.body.addEventListener('click', (event) => {
100-
if (this.$el.contains(event.target)) return;
104+
if (elRoot.contains(event.target)) return;
101105
if (this.menuVisible) {
102-
Vue.set(this, 'menuVisible', false);
106+
this.menuVisible = false;
103107
}
104108
});
105109
},
@@ -135,15 +139,15 @@ export function initRepoBranchTagDropdown(selector) {
135139
if (this.submitForm) {
136140
$(`#${this.branchForm}`).trigger('submit');
137141
}
138-
Vue.set(this, 'menuVisible', false);
142+
this.menuVisible = false;
139143
}
140144
},
141145
createNewBranch() {
142146
if (!this.showCreateNewBranch) return;
143147
$(this.$refs.newBranchForm).trigger('submit');
144148
},
145149
focusSearchField() {
146-
Vue.nextTick(() => {
150+
nextTick(() => {
147151
this.$refs.searchField.focus();
148152
});
149153
},
@@ -213,5 +217,6 @@ export function initRepoBranchTagDropdown(selector) {
213217
}
214218
}
215219
});
220+
view.mount(this);
216221
});
217222
}

web_src/js/components/VueComponentLoader.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Vue from 'vue';
1+
import {createApp} from 'vue';
22
import {svgs} from '../svg.js';
33

44
export const vueDelimiters = ['${', '}'];
@@ -8,13 +8,14 @@ export function initVueEnv() {
88
if (vueEnvInited) return;
99
vueEnvInited = true;
1010

11-
const isProd = window.config.runModeIsProd;
12-
Vue.config.productionTip = false;
13-
Vue.config.devtools = !isProd;
11+
// As far as I could tell, this is no longer possible.
12+
// But there seem not to be a guide what to do instead.
13+
// const isProd = window.config.runModeIsProd;
14+
// Vue.config.devtools = !isProd;
1415
}
1516

1617
let vueSvgInited = false;
17-
export function initVueSvg() {
18+
export function initVueSvg(app) {
1819
if (vueSvgInited) return;
1920
vueSvgInited = true;
2021

@@ -24,7 +25,7 @@ export function initVueSvg() {
2425
.replace(/height="[0-9]+"/, 'v-bind:height="size"')
2526
.replace(/width="[0-9]+"/, 'v-bind:width="size"');
2627

27-
Vue.component(name, {
28+
app.component(name, {
2829
props: {
2930
size: {
3031
type: String,
@@ -42,8 +43,7 @@ export function initVueApp(el, opts = {}) {
4243
}
4344
if (!el) return null;
4445

45-
return new Vue(Object.assign({
46-
el,
47-
delimiters: vueDelimiters,
48-
}, opts));
46+
return createApp(
47+
Object.assign({delimiters: vueDelimiters}, opts)
48+
).mount(el);
4949
}

0 commit comments

Comments
 (0)