diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue
new file mode 100644
index 0000000000..df04f0d8d8
--- /dev/null
+++ b/web_src/js/components/ContextPopup.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
{{ issue.repository.full_name }} on {{ createdAt }}
+
{{ issue.title }} #{{ issue.number }}
+
{{ body }}
+
+
+
+
+
+
diff --git a/web_src/js/features/contextpopup.js b/web_src/js/features/contextpopup.js
index c16820cf1f..8583c6253c 100644
--- a/web_src/js/features/contextpopup.js
+++ b/web_src/js/features/contextpopup.js
@@ -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 = '
';
+ 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),
+ });
+
+ const view = new View();
+
+ try {
+ view.$mount(el.firstChild);
+ } catch (err) {
+ console.error(err);
+ el.textContent = 'ContextPopup failed to load';
}
- 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 += `${htmlEscape(label.name)}
`;
- }
- if (labels.length > 0) {
- labels = `${labels}
`;
- }
-
- 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
- }
-
- $element.popup({
+ $(this).popup({
variation: 'wide',
delay: {
show: 250
},
- html: `
-
-
${htmlEscape(issue.repository.full_name)} on ${createdAt}
-
${svg(octicon)} ${htmlEscape(issue.title)} #${index}
-
${htmlEscape(body)}
- ${labels}
-
-`
+ onShow: () => {
+ view.$emit('load-context-popup', {owner, repo, index}, () => {
+ $(this).popup('reposition');
+ });
+ },
+ popup: $(el),
});
});
}
diff --git a/web_src/js/svg.js b/web_src/js/svg.js
index 0960256e21..185c23c245 100644
--- a/web_src/js/svg.js
+++ b/web_src/js/svg.js
@@ -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: ``
+});