diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2024-04-30 10:36:32 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-30 02:36:32 +0000 |
commit | 61b495e5ab604a26c867433e5c5ae5b07267e30f (patch) | |
tree | 064b1f0b63f2c65ef5df5d63bf4f128da0e0e112 | |
parent | a3d9f0d9151dbdcd77bf68f70b8e9497da5f2d3f (diff) | |
download | gitea-61b495e5ab604a26c867433e5c5ae5b07267e30f.tar.gz gitea-61b495e5ab604a26c867433e5c5ae5b07267e30f.zip |
Fix issue label rendering in the issue popup (#30763)
-rw-r--r-- | modules/templates/util_render.go | 39 | ||||
-rw-r--r-- | routers/web/repo/issue.go | 5 | ||||
-rw-r--r-- | tests/integration/issue_test.go | 11 | ||||
-rw-r--r-- | web_src/js/components/ContextPopup.vue | 27 | ||||
-rw-r--r-- | web_src/js/features/common-issue-list.js | 2 |
5 files changed, 36 insertions, 48 deletions
diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index 659422aee7..b15de6521d 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -121,29 +121,25 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string) // RenderLabel renders a label // locale is needed due to an import cycle with our context providing the `Tr` function func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML { - var ( - archivedCSSClass string - textColor = util.ContrastColor(label.Color) - labelScope = label.ExclusiveScope() - ) - - description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description)) + var extraCSSClasses string + textColor := util.ContrastColor(label.Color) + labelScope := label.ExclusiveScope() + descriptionText := emoji.ReplaceAliases(label.Description) if label.IsArchived() { - archivedCSSClass = "archived-label" - description = fmt.Sprintf("(%s) %s", locale.TrString("archived"), description) + extraCSSClasses = "archived-label" + descriptionText = fmt.Sprintf("(%s) %s", locale.TrString("archived"), descriptionText) } if labelScope == "" { // Regular label - s := fmt.Sprintf("<div class='ui label %s' style='color: %s !important; background-color: %s !important;' data-tooltip-content title='%s'>%s</div>", - archivedCSSClass, textColor, label.Color, description, RenderEmoji(ctx, label.Name)) - return template.HTML(s) + return HTMLFormat(`<div class="ui label %s" style="color: %s !important; background-color: %s !important;" data-tooltip-content title="%s">%s</div>`, + extraCSSClasses, textColor, label.Color, descriptionText, RenderEmoji(ctx, label.Name)) } // Scoped label - scopeText := RenderEmoji(ctx, labelScope) - itemText := RenderEmoji(ctx, label.Name[len(labelScope)+1:]) + scopeHTML := RenderEmoji(ctx, labelScope) + itemHTML := RenderEmoji(ctx, label.Name[len(labelScope)+1:]) // Make scope and item background colors slightly darker and lighter respectively. // More contrast needed with higher luminance, empirically tweaked. @@ -171,14 +167,13 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m itemColor := "#" + hex.EncodeToString(itemBytes) scopeColor := "#" + hex.EncodeToString(scopeBytes) - s := fmt.Sprintf("<span class='ui label %s scope-parent' data-tooltip-content title='%s'>"+ - "<div class='ui label scope-left' style='color: %s !important; background-color: %s !important'>%s</div>"+ - "<div class='ui label scope-right' style='color: %s !important; background-color: %s !important'>%s</div>"+ - "</span>", - archivedCSSClass, description, - textColor, scopeColor, scopeText, - textColor, itemColor, itemText) - return template.HTML(s) + return HTMLFormat(`<span class="ui label %s scope-parent" data-tooltip-content title="%s">`+ + `<div class="ui label scope-left" style="color: %s !important; background-color: %s !important">%s</div>`+ + `<div class="ui label scope-right" style="color: %s !important; background-color: %s !important">%s</div>`+ + `</span>`, + extraCSSClasses, descriptionText, + textColor, scopeColor, scopeHTML, + textColor, itemColor, itemHTML) } // RenderEmoji renders html text with emoji post processors diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index de6ef9e93b..0c8363a168 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2177,7 +2177,10 @@ func GetIssueInfo(ctx *context.Context) { } } - ctx.JSON(http.StatusOK, convert.ToIssue(ctx, ctx.Doer, issue)) + ctx.JSON(http.StatusOK, map[string]any{ + "convertedIssue": convert.ToIssue(ctx, ctx.Doer, issue), + "renderedLabels": templates.RenderLabels(ctx, ctx.Locale, issue.Labels, ctx.Repo.RepoLink, issue), + }) } // UpdateIssueTitle change issue's title diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 44d362d9c7..b7952b0879 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -6,6 +6,7 @@ package integration import ( "context" "fmt" + "html/template" "net/http" "net/url" "path" @@ -573,10 +574,14 @@ func TestGetIssueInfo(t *testing.T) { urlStr := fmt.Sprintf("/%s/%s/issues/%d/info", owner.Name, repo.Name, issue.Index) req := NewRequest(t, "GET", urlStr) resp := session.MakeRequest(t, req, http.StatusOK) - var apiIssue api.Issue - DecodeJSON(t, resp, &apiIssue) + var respStruct struct { + ConvertedIssue api.Issue + RenderedLabels template.HTML + } + DecodeJSON(t, resp, &respStruct) - assert.EqualValues(t, issue.ID, apiIssue.ID) + assert.EqualValues(t, issue.ID, respStruct.ConvertedIssue.ID) + assert.Contains(t, string(respStruct.RenderedLabels), `"labels-list"`) } func TestUpdateIssueDeadline(t *testing.T) { diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue index 65a6089522..e4e8bce184 100644 --- a/web_src/js/components/ContextPopup.vue +++ b/web_src/js/components/ContextPopup.vue @@ -1,6 +1,5 @@ <script> import {SvgIcon} from '../svg.js'; -import {contrastColor} from '../utils/color.js'; import {GET} from '../modules/fetch.js'; const {appSubUrl, i18n} = window.config; @@ -10,6 +9,7 @@ export default { data: () => ({ loading: false, issue: null, + renderedLabels: '', i18nErrorOccurred: i18n.error_occurred, i18nErrorMessage: null, }), @@ -56,14 +56,6 @@ export default { } return 'red'; // Closed Issue }, - - labels() { - return this.issue.labels.map((label) => ({ - name: label.name, - color: `#${label.color}`, - textColor: contrastColor(`#${label.color}`), - })); - }, }, mounted() { this.$refs.root.addEventListener('ce-load-context-popup', (e) => { @@ -79,13 +71,14 @@ export default { this.i18nErrorMessage = null; try { - const response = await GET(`${appSubUrl}/${data.owner}/${data.repo}/issues/${data.index}/info`); + const response = await GET(`${appSubUrl}/${data.owner}/${data.repo}/issues/${data.index}/info`); // backend: GetIssueInfo const respJson = await response.json(); if (!response.ok) { this.i18nErrorMessage = respJson.message ?? i18n.network_error; return; } - this.issue = respJson; + this.issue = respJson.convertedIssue; + this.renderedLabels = respJson.renderedLabels; } catch { this.i18nErrorMessage = i18n.network_error; } finally { @@ -102,16 +95,8 @@ export default { <p><small>{{ issue.repository.full_name }} on {{ createdAt }}</small></p> <p><svg-icon :name="icon" :class="['text', color]"/> <strong>{{ issue.title }}</strong> #{{ issue.number }}</p> <p>{{ body }}</p> - <div class="labels-list"> - <div - v-for="label in labels" - :key="label.name" - class="ui label" - :style="{ color: label.textColor, backgroundColor: label.color }" - > - {{ label.name }} - </div> - </div> + <!-- eslint-disable-next-line vue/no-v-html --> + <div v-html="renderedLabels"/> </div> <div v-if="!loading && issue === null"> <p><small>{{ i18nErrorOccurred }}</small></p> diff --git a/web_src/js/features/common-issue-list.js b/web_src/js/features/common-issue-list.js index 0c0f6c563d..219a8a9c9a 100644 --- a/web_src/js/features/common-issue-list.js +++ b/web_src/js/features/common-issue-list.js @@ -53,7 +53,7 @@ export function initCommonIssueListQuickGoto() { // try to check whether the parsed goto link is valid let targetUrl = parseIssueListQuickGotoLink(repoLink, searchText); if (targetUrl) { - const res = await GET(`${targetUrl}/info`); + const res = await GET(`${targetUrl}/info`); // backend: GetIssueInfo, it only checks whether the issue exists by status code if (res.status !== 200) targetUrl = ''; } // if the input value has changed, then ignore the result |