Skip to content

Commit 426c8f6

Browse files
committed
Rewrite context popup to stateless
1 parent fb7b743 commit 426c8f6

File tree

4 files changed

+57
-85
lines changed

4 files changed

+57
-85
lines changed

routers/web/repo/issue.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,8 +2181,8 @@ func GetIssueInfo(ctx *context.Context) {
21812181
}
21822182

21832183
ctx.JSON(http.StatusOK, map[string]any{
2184-
"convertedIssue": convert.ToIssue(ctx, ctx.Doer, issue),
2185-
"renderedLabels": templates.RenderLabels(ctx, ctx.Locale, issue.Labels, ctx.Repo.RepoLink, issue),
2184+
"issue": convert.ToIssue(ctx, ctx.Doer, issue),
2185+
"labelsHtml": templates.RenderLabels(ctx, ctx.Locale, issue.Labels, ctx.Repo.RepoLink, issue),
21862186
})
21872187
}
21882188

templates/base/head_script.tmpl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
3636
i18n: {
3737
copy_success: {{ctx.Locale.Tr "copy_success"}},
3838
copy_error: {{ctx.Locale.Tr "copy_error"}},
39-
error_occurred: {{ctx.Locale.Tr "error.occurred"}},
4039
network_error: {{ctx.Locale.Tr "error.network_error"}},
4140
remove_label_str: {{ctx.Locale.Tr "remove_label_str"}},
4241
modal_confirm: {{ctx.Locale.Tr "modal.confirm"}},
Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
<script>
22
import {SvgIcon} from '../svg.js';
3-
import {GET} from '../modules/fetch.js';
4-
5-
const {appSubUrl, i18n} = window.config;
63
74
export default {
85
components: {SvgIcon},
9-
data: () => ({
10-
loading: false,
11-
issue: null,
12-
renderedLabels: '',
13-
i18nErrorOccurred: i18n.error_occurred,
14-
i18nErrorMessage: null,
15-
}),
6+
props: {
7+
issue: {
8+
type: Object,
9+
default: null,
10+
},
11+
labelsHtml: {
12+
type: String,
13+
default: '',
14+
},
15+
},
1616
computed: {
1717
createdAt() {
1818
return new Date(this.issue.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'});
@@ -57,41 +57,11 @@ export default {
5757
return 'red'; // Closed Issue
5858
},
5959
},
60-
mounted() {
61-
this.$refs.root.addEventListener('ce-load-context-popup', (e) => {
62-
const data = e.detail;
63-
if (!this.loading && this.issue === null) {
64-
this.load(data);
65-
}
66-
});
67-
},
68-
methods: {
69-
async load(data) {
70-
this.loading = true;
71-
this.i18nErrorMessage = null;
72-
73-
try {
74-
const response = await GET(`${appSubUrl}/${data.owner}/${data.repo}/issues/${data.index}/info`); // backend: GetIssueInfo
75-
const respJson = await response.json();
76-
if (!response.ok) {
77-
this.i18nErrorMessage = respJson.message ?? i18n.network_error;
78-
return;
79-
}
80-
this.issue = respJson.convertedIssue;
81-
this.renderedLabels = respJson.renderedLabels;
82-
} catch {
83-
this.i18nErrorMessage = i18n.network_error;
84-
} finally {
85-
this.loading = false;
86-
}
87-
},
88-
},
8960
};
9061
</script>
9162
<template>
9263
<div ref="root">
93-
<div v-if="loading" class="tw-h-12 tw-w-12 is-loading"/>
94-
<div v-if="!loading && issue !== null" class="tw-flex tw-flex-col tw-gap-2">
64+
<div v-if="issue !== null" class="tw-p-3 tw-flex tw-flex-col tw-gap-2">
9565
<div class="tw-text-12">{{ issue.repository.full_name }} on {{ createdAt }}</div>
9666
<div class="flex-text-block">
9767
<svg-icon :name="icon" :class="['text', color]"/>
@@ -102,11 +72,7 @@ export default {
10272
</div>
10373
<div v-if="body">{{ body }}</div>
10474
<!-- eslint-disable-next-line vue/no-v-html -->
105-
<div v-if="issue.labels.length" v-html="renderedLabels"/>
106-
</div>
107-
<div class="tw-flex tw-flex-col tw-gap-2" v-if="!loading && issue === null">
108-
<div class="tw-text-12">{{ i18nErrorOccurred }}</div>
109-
<div>{{ i18nErrorMessage }}</div>
75+
<div v-if="issue.labels.length" v-html="labelsHtml"/>
11076
</div>
11177
</div>
11278
</template>

web_src/js/features/contextpopup.js

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,51 @@ import {createApp} from 'vue';
22
import ContextPopup from '../components/ContextPopup.vue';
33
import {parseIssueHref} from '../utils.js';
44
import {createTippy} from '../modules/tippy.js';
5+
import {GET} from '../modules/fetch.js';
56

6-
export function initContextPopups() {
7-
const refIssues = document.querySelectorAll('.ref-issue');
8-
attachRefIssueContextPopup(refIssues);
7+
const {appSubUrl} = window.config;
8+
9+
async function show(e) {
10+
const link = e.currentTarget;
11+
const {owner, repo, index} = parseIssueHref(link.getAttribute('href'));
12+
if (!owner) return;
13+
14+
const res = await GET(`${appSubUrl}/${owner}/${repo}/issues/${index}/info`); // backend: GetIssueInfo
15+
if (!res.ok) return;
16+
17+
let issue, labelsHtml;
18+
try {
19+
({issue, labelsHtml} = await res.json());
20+
} catch {
21+
return;
22+
}
23+
24+
const popup = document.createElement('div');
25+
const view = createApp(ContextPopup, {issue, labelsHtml});
26+
try {
27+
view.mount(popup);
28+
} catch (err) {
29+
console.error(err);
30+
return;
31+
}
32+
33+
const tippy = createTippy(link, {
34+
theme: 'default',
35+
trigger: 'mouseenter focus',
36+
content: popup,
37+
placement: 'top-start',
38+
interactive: true,
39+
role: 'dialog',
40+
interactiveBorder: 15,
41+
});
42+
43+
// show immediately because this runs during mouseenter and focus
44+
tippy.show();
945
}
1046

11-
export function attachRefIssueContextPopup(refIssues) {
12-
for (const refIssue of refIssues) {
13-
if (refIssue.classList.contains('ref-external-issue')) {
14-
return;
15-
}
16-
17-
const {owner, repo, index} = parseIssueHref(refIssue.getAttribute('href'));
18-
if (!owner) return;
19-
20-
const el = document.createElement('div');
21-
el.classList.add('tw-p-3');
22-
refIssue.parentNode.insertBefore(el, refIssue.nextSibling);
23-
24-
const view = createApp(ContextPopup);
25-
26-
try {
27-
view.mount(el);
28-
} catch (err) {
29-
console.error(err);
30-
el.textContent = 'ContextPopup failed to load';
31-
}
32-
33-
createTippy(refIssue, {
34-
theme: 'default',
35-
content: el,
36-
placement: 'top-start',
37-
interactive: true,
38-
role: 'dialog',
39-
interactiveBorder: 5,
40-
onShow: () => {
41-
el.firstChild.dispatchEvent(new CustomEvent('ce-load-context-popup', {detail: {owner, repo, index}}));
42-
},
43-
});
47+
export function initContextPopups() {
48+
for (const link of document.querySelectorAll('.ref-issue:not(.ref-external-issue)')) {
49+
link.addEventListener('mouseenter', show);
50+
link.addEventListener('focus', show);
4451
}
4552
}

0 commit comments

Comments
 (0)