]> source.dussan.org Git - gitea.git/commitdiff
Fix issue label rendering in the issue popup (#30763) (#30773)
authorGiteabot <teabot@gitea.io>
Tue, 30 Apr 2024 09:40:47 +0000 (17:40 +0800)
committerGitHub <noreply@github.com>
Tue, 30 Apr 2024 09:40:47 +0000 (09:40 +0000)
Backport #30763 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
modules/templates/util_render.go
routers/web/repo/issue.go
tests/integration/issue_test.go
web_src/js/components/ContextPopup.vue
web_src/js/features/common-issue-list.js

index 659422aee79895b6ff542cdba267200ea55a047b..b15de6521df68a05f3f7c4ddabca0e4ffea55e14 100644 (file)
@@ -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
index de6ef9e93bcdb85c54bb374107ef3066f3ff6852..0c8363a1681e5fc2e59ee4695558b38dbff9759e 100644 (file)
@@ -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
index 44d362d9c76af979a86c75bd604291a747233689..b7952b0879a1b62c7ab5374c06075c1b437a7ec6 100644 (file)
@@ -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) {
index 65a6089522184c2f9e6147801ee4f273598bc339..e4e8bce184639a1299021a8f219b1f9c2ce5aeba 100644 (file)
@@ -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>
index 0c0f6c563d3635139f313a4d0500a8f900be3429..219a8a9c9a09b8c09f47a27ab655966c94489bf4 100644 (file)
@@ -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