]> source.dussan.org Git - gitea.git/commitdiff
Load issue/PR context popup data only when needed (#15955)
authorLauris BH <lauris@nix.lv>
Tue, 13 Jul 2021 18:09:19 +0000 (21:09 +0300)
committerGitHub <noreply@github.com>
Tue, 13 Jul 2021 18:09:19 +0000 (20:09 +0200)
* Load issue/PR context popup data only when needed

* Add SVG icon Vue component

* Remove unneeded check

web_src/js/components/ContextPopup.vue [new file with mode: 0644]
web_src/js/features/contextpopup.js
web_src/js/svg.js

diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue
new file mode 100644 (file)
index 0000000..df04f0d
--- /dev/null
@@ -0,0 +1,112 @@
+<template>
+  <div>
+    <div v-if="loading" class="ui active centered inline loader"/>
+    <div v-if="!loading && issue !== null">
+      <p><small>{{ issue.repository.full_name }} on {{ createdAt }}</small></p>
+      <p><svg-icon :name="icon" :class="[color]" /> <strong>{{ issue.title }}</strong> #{{ issue.number }}</p>
+      <p>{{ body }}</p>
+      <div>
+        <div
+          v-for="label in labels"
+          :key="label.name"
+          class="ui label"
+          :style="{ color: label.textColor, backgroundColor: label.color }"
+        >
+          {{ label.name }}
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {SvgIcon} from '../svg.js';
+
+const {AppSubUrl} = window.config;
+
+export default {
+  name: 'ContextPopup',
+
+  components: {
+    SvgIcon,
+  },
+
+  data: () => ({
+    loading: false,
+    issue: null
+  }),
+
+  computed: {
+    createdAt() {
+      return new Date(this.issue.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'});
+    },
+
+    body() {
+      const body = this.issue.body.replace(/\n+/g, ' ');
+      if (body.length > 85) {
+        return `${body.substring(0, 85)}…`;
+      }
+      return body;
+    },
+
+    icon() {
+      if (this.issue.pull_request !== null) {
+        if (this.issue.state === 'open') {
+          return 'octicon-git-pull-request'; // Open PR
+        } else if (this.issue.pull_request.merged === true) {
+          return 'octicon-git-merge'; // Merged PR
+        }
+        return 'octicon-git-pull-request'; // Closed PR
+      } else if (this.issue.state === 'open') {
+        return 'octicon-issue-opened'; // Open Issue
+      }
+      return 'octicon-issue-closed'; // Closed Issue
+    },
+
+    color() {
+      if (this.issue.state === 'open') {
+        return 'green';
+      } else if (this.issue.pull_request !== null && this.issue.pull_request.merged === true) {
+        return 'purple';
+      }
+      return 'red';
+    },
+
+    labels() {
+      return this.issue.labels.map((label) => {
+        const red = parseInt(label.color.substring(0, 2), 16);
+        const green = parseInt(label.color.substring(2, 4), 16);
+        const blue = parseInt(label.color.substring(4, 6), 16);
+        let color = '#ffffff';
+        if ((red * 0.299 + green * 0.587 + blue * 0.114) > 125) {
+          color = '#000000';
+        }
+        return {name: label.name, color: `#${label.color}`, textColor: color};
+      });
+    }
+  },
+
+  mounted() {
+    this.$root.$on('load-context-popup', (data, callback) => {
+      if (!this.loading && this.issue === null) {
+        this.load(data, callback);
+      }
+    });
+  },
+
+  methods: {
+    load(data, callback) {
+      this.loading = true;
+      $.get(`${AppSubUrl}/api/v1/repos/${data.owner}/${data.repo}/issues/${data.index}`, (issue) => {
+        this.issue = issue;
+        this.loading = false;
+        this.$nextTick(() => {
+          if (callback) {
+            callback();
+          }
+        });
+      });
+    }
+  }
+};
+</script>
index c16820cf1f7404637fa7a60cb4a8861533ebe1cd..8583c6253cd9f99526bf30fd902471b4a3d75c7a 100644 (file)
@@ -1,7 +1,6 @@
-import {htmlEscape} from 'escape-goat';
-import {svg} from '../svg.js';
+import Vue from 'vue';
 
-const {AppSubUrl} = window.config;
+import ContextPopup from '../components/ContextPopup.vue';
 
 export default function initContextPopups() {
   const refIssues = $('.ref-issue');
@@ -9,68 +8,36 @@ export default function initContextPopups() {
 
   refIssues.each(function () {
     const [index, _issues, repo, owner] = $(this).attr('href').replace(/[#?].*$/, '').split('/').reverse();
-    issuePopup(owner, repo, index, $(this));
-  });
-}
 
-function issuePopup(owner, repo, index, $element) {
-  $.get(`${AppSubUrl}/api/v1/repos/${owner}/${repo}/issues/${index}`, (issue) => {
-    const createdAt = new Date(issue.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'});
+    const el = document.createElement('div');
+    el.className = 'ui custom popup hidden';
+    el.innerHTML = '<div></div>';
+    this.parentNode.insertBefore(el, this.nextSibling);
 
-    let body = issue.body.replace(/\n+/g, ' ');
-    if (body.length > 85) {
-      body = `${body.substring(0, 85)}...`;
-    }
+    const View = Vue.extend({
+      render: (createElement) => createElement(ContextPopup),
+    });
 
-    let labels = '';
-    for (let i = 0; i < issue.labels.length; i++) {
-      const label = issue.labels[i];
-      const red = parseInt(label.color.substring(0, 2), 16);
-      const green = parseInt(label.color.substring(2, 4), 16);
-      const blue = parseInt(label.color.substring(4, 6), 16);
-      let color = '#ffffff';
-      if ((red * 0.299 + green * 0.587 + blue * 0.114) > 125) {
-        color = '#000000';
-      }
-      labels += `<div class="ui label" style="color: ${color}; background-color:#${label.color};">${htmlEscape(label.name)}</div>`;
-    }
-    if (labels.length > 0) {
-      labels = `<p>${labels}</p>`;
-    }
+    const view = new View();
 
-    let octicon, color;
-    if (issue.pull_request !== null) {
-      if (issue.state === 'open') {
-        color = 'green';
-        octicon = 'octicon-git-pull-request'; // Open PR
-      } else if (issue.pull_request.merged === true) {
-        color = 'purple';
-        octicon = 'octicon-git-merge'; // Merged PR
-      } else {
-        color = 'red';
-        octicon = 'octicon-git-pull-request'; // Closed PR
-      }
-    } else if (issue.state === 'open') {
-      color = 'green';
-      octicon = 'octicon-issue-opened'; // Open Issue
-    } else {
-      color = 'red';
-      octicon = 'octicon-issue-closed'; // Closed Issue
+    try {
+      view.$mount(el.firstChild);
+    } catch (err) {
+      console.error(err);
+      el.textContent = 'ContextPopup failed to load';
     }
 
-    $element.popup({
+    $(this).popup({
       variation: 'wide',
       delay: {
         show: 250
       },
-      html: `
-<div>
-  <p><small>${htmlEscape(issue.repository.full_name)} on ${createdAt}</small></p>
-  <p><span class="${color}">${svg(octicon)}</span> <strong>${htmlEscape(issue.title)}</strong> #${index}</p>
-  <p>${htmlEscape(body)}</p>
-  ${labels}
-</div>
-`
+      onShow: () => {
+        view.$emit('load-context-popup', {owner, repo, index}, () => {
+          $(this).popup('reposition');
+        });
+      },
+      popup: $(el),
     });
   });
 }
index 0960256e2121cf666435404c1cea4022e8308c99..185c23c245498414e734e1efc9119bda26baefc4 100644 (file)
@@ -14,6 +14,8 @@ import octiconRepo from '../../public/img/svg/octicon-repo.svg';
 import octiconRepoForked from '../../public/img/svg/octicon-repo-forked.svg';
 import octiconRepoTemplate from '../../public/img/svg/octicon-repo-template.svg';
 
+import Vue from 'vue';
+
 export const svgs = {
   'octicon-chevron-down': octiconChevronDown,
   'octicon-chevron-right': octiconChevronRight,
@@ -47,3 +49,19 @@ export function svg(name, size = 16, className = '') {
   if (className) svgNode.classList.add(...className.split(/\s+/));
   return serializer.serializeToString(svgNode);
 }
+
+export const SvgIcon = Vue.component('SvgIcon', {
+  props: {
+    name: {type: String, required: true},
+    size: {type: Number, default: 16},
+    className: {type: String, default: ''},
+  },
+
+  computed: {
+    svg() {
+      return svg(this.name, this.size, this.className);
+    },
+  },
+
+  template: `<span v-html="svg" />`
+});