From d67e9b9629557df1e355f0a864792c194b3e8848 Mon Sep 17 00:00:00 2001
From: John Olheiser <42128690+jolheiser@users.noreply.github.com>
Date: Tue, 11 Feb 2020 19:53:18 -0600
Subject: [PATCH] SVG Octicon fixes (#10237)

* SVG fixes

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Colorize span->svg only

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* @silverwind suggestions

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Alphabetical

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Convert suburl and staticPrefix to window.config

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* De-structure

Signed-off-by: jolheiser <john.olheiser@gmail.com>

Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com>
---
 .eslintrc                            |  1 +
 templates/base/head.tmpl             |  4 +-
 templates/repo/issue/view_title.tmpl |  2 +-
 web_src/js/features/contextPopup.js  | 31 +++++++++------
 web_src/js/index.js                  | 57 +++++++++++++---------------
 web_src/js/utils.js                  |  5 +++
 web_src/less/_base.less              |  6 +--
 web_src/less/_repository.less        | 26 ++++++-------
 8 files changed, 72 insertions(+), 60 deletions(-)
 create mode 100644 web_src/js/utils.js

diff --git a/.eslintrc b/.eslintrc
index 3a8dc93fc6a..e8c63a6827f 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -33,6 +33,7 @@ rules:
   default-case: [0]
   func-names: [0]
   import/extensions: [0]
+  import/prefer-default-export: [0]
   max-len: [0]
   newline-per-chained-call: [0]
   no-alert: [0]
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl
index c9ae07f6329..f1558f94843 100644
--- a/templates/base/head.tmpl
+++ b/templates/base/head.tmpl
@@ -36,8 +36,6 @@
 	<meta name="keywords" content="{{MetaKeywords}}">
 	<meta name="referrer" content="no-referrer" />
 	<meta name="_csrf" content="{{.CsrfToken}}" />
-	<meta name="_suburl" content="{{AppSubUrl}}" />
-	<meta name="_staticprefix" content="{{StaticUrlPrefix}}" />
 	{{if .IsSigned}}
 		<meta name="_uid" content="{{.SignedUser.ID}}" />
 	{{end}}
@@ -86,6 +84,8 @@
 	</script>
 	<script>
 		window.config = {
+			AppSubUrl: '{{AppSubUrl}}',
+			StaticUrlPrefix: '{{StaticUrlPrefix}}',
 			Datetimepicker: {{if .RequireDatetimepicker}}true{{else}}false{{end}},
 			Dropzone: {{if .RequireDropzone}}true{{else}}false{{end}},
 			HighlightJS: {{if .RequireHighlightJS}}true{{else}}false{{end}},
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl
index 21ebf62129c..e2a3e8f93ae 100644
--- a/templates/repo/issue/view_title.tmpl
+++ b/templates/repo/issue/view_title.tmpl
@@ -17,7 +17,7 @@
 		{{end}}
 	</div>
 	{{if .HasMerged}}
-		<div class="ui purple large label">{{svg "octicon-gt-pull-request" 16}} {{.i18n.Tr "repo.pulls.merged"}}</div>
+		<div class="ui purple large label">{{svg "octicon-git-pull-request" 16}} {{.i18n.Tr "repo.pulls.merged"}}</div>
 	{{else if .Issue.IsClosed}}
 		<div class="ui red large label">{{svg "octicon-issue-closed" 16}} {{.i18n.Tr "repo.issues.closed_title"}}</div>
 	{{else if .Issue.IsPull}}
diff --git a/web_src/js/features/contextPopup.js b/web_src/js/features/contextPopup.js
index 34b6d503574..5acfa9c2932 100644
--- a/web_src/js/features/contextPopup.js
+++ b/web_src/js/features/contextPopup.js
@@ -1,15 +1,19 @@
-export default function initContextPopups(suburl) {
+import { svg } from '../utils.js';
+
+const { AppSubUrl } = window.config;
+
+export default function initContextPopups() {
   const refIssues = $('.ref-issue');
   if (!refIssues.length) return;
 
   refIssues.each(function () {
     const [index, _issues, repo, owner] = $(this).attr('href').replace(/[#?].*$/, '').split('/').reverse();
-    issuePopup(suburl, owner, repo, index, $(this));
+    issuePopup(owner, repo, index, $(this));
   });
 }
 
-function issuePopup(suburl, owner, repo, index, $element) {
-  $.get(`${suburl}/api/v1/repos/${owner}/${repo}/issues/${index}`, (issue) => {
+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' });
 
     let body = issue.body.replace(/\n+/g, ' ');
@@ -34,19 +38,24 @@ function issuePopup(suburl, owner, repo, index, $element) {
       labels = `<p>${labels}</p>`;
     }
 
-    let octicon;
+    let octicon, color;
     if (issue.pull_request !== null) {
       if (issue.state === 'open') {
-        octicon = 'green octicon-git-pull-request'; // Open PR
+        color = 'green';
+        octicon = 'octicon-git-pull-request'; // Open PR
       } else if (issue.pull_request.merged === true) {
-        octicon = 'purple octicon-git-merge'; // Merged PR
+        color = 'purple';
+        octicon = 'octicon-git-merge'; // Merged PR
       } else {
-        octicon = 'red octicon-git-pull-request'; // Closed PR
+        color = 'red';
+        octicon = 'octicon-git-pull-request'; // Closed PR
       }
     } else if (issue.state === 'open') {
-      octicon = 'green octicon-issue-opened'; // Open Issue
+      color = 'green';
+      octicon = 'octicon-issue-opened'; // Open Issue
     } else {
-      octicon = 'red octicon-issue-closed'; // Closed Issue
+      color = 'red';
+      octicon = 'octicon-issue-closed'; // Closed Issue
     }
 
     $element.popup({
@@ -57,7 +66,7 @@ function issuePopup(suburl, owner, repo, index, $element) {
       html: `
 <div>
   <p><small>${issue.repository.full_name} on ${createdAt}</small></p>
-  <p><i class="octicon ${octicon}"></i> <strong>${issue.title}</strong> #${index}</p>
+  <p><span class="${color}">${svg(octicon, 16)}</span> <strong>${issue.title}</strong> #${index}</p>
   <p>${body}</p>
   ${labels}
 </div>
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 5d195774ddc..75fe28b3539 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -6,6 +6,7 @@ import 'jquery.are-you-sure';
 import './publicPath.js';
 import './polyfills.js';
 import './vendor/semanticDropdown.js';
+import { svg } from './utils.js';
 
 import initContextPopups from './features/contextPopup.js';
 import initHighlight from './features/highlight.js';
@@ -14,17 +15,13 @@ import initClipboard from './features/clipboard.js';
 
 import ActivityTopAuthors from './components/ActivityTopAuthors.vue';
 
+const { AppSubUrl, StaticUrlPrefix } = window.config;
+
 function htmlEncode(text) {
   return jQuery('<div />').text(text).html();
 }
 
-function svg(name, size) {
-  return `<svg class="svg ${name}" width="${size}" height="${size}" aria-hidden="true"><use xlink:href="${staticPrefix}/img/svg/icons.svg#${name}"/></svg>`;
-}
-
 let csrf;
-let suburl;
-let staticPrefix;
 let previewFileModes;
 let simpleMDEditor;
 const commentMDEditors = {};
@@ -157,7 +154,7 @@ function initRepoStatusChecker() {
     }
     $.ajax({
       type: 'GET',
-      url: `${suburl}/${repo_name}/status`,
+      url: `${AppSubUrl}/${repo_name}/status`,
       data: {
         _csrf: csrf,
       },
@@ -293,7 +290,7 @@ function uploadFile(file, callback) {
     }
   };
 
-  xhr.open('post', `${suburl}/attachments`, true);
+  xhr.open('post', `${AppSubUrl}/attachments`, true);
   xhr.setRequestHeader('X-Csrf-Token', csrf);
   const formData = new FormData();
   formData.append('file', file, file.name);
@@ -313,7 +310,7 @@ function initImagePaste(target) {
         insertAtCursor(field, `![${name}]()`);
         uploadFile(img, (res) => {
           const data = JSON.parse(res);
-          replaceAndKeepCursor(field, `![${name}]()`, `![${name}](${suburl}/attachments/${data.uuid})`);
+          replaceAndKeepCursor(field, `![${name}]()`, `![${name}](${AppSubUrl}/attachments/${data.uuid})`);
           const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
           $('.files').append(input);
         });
@@ -329,7 +326,7 @@ function initSimpleMDEImagePaste(simplemde, files) {
       uploadFile(img, (res) => {
         const data = JSON.parse(res);
         const pos = simplemde.codemirror.getCursor();
-        simplemde.codemirror.replaceRange(`![${name}](${suburl}/attachments/${data.uuid})`, pos);
+        simplemde.codemirror.replaceRange(`![${name}](${AppSubUrl}/attachments/${data.uuid})`, pos);
         const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
         files.append(input);
       });
@@ -2059,7 +2056,7 @@ function searchUsers() {
   $searchUserBox.search({
     minCharacters: 2,
     apiSettings: {
-      url: `${suburl}/api/v1/users/search?q={query}`,
+      url: `${AppSubUrl}/api/v1/users/search?q={query}`,
       onResponse(response) {
         const items = [];
         $.each(response.data, (_i, item) => {
@@ -2086,7 +2083,7 @@ function searchTeams() {
   $searchTeamBox.search({
     minCharacters: 2,
     apiSettings: {
-      url: `${suburl}/api/v1/orgs/${$searchTeamBox.data('org')}/teams/search?q={query}`,
+      url: `${AppSubUrl}/api/v1/orgs/${$searchTeamBox.data('org')}/teams/search?q={query}`,
       headers: { 'X-Csrf-Token': csrf },
       onResponse(response) {
         const items = [];
@@ -2110,7 +2107,7 @@ function searchRepositories() {
   $searchRepoBox.search({
     minCharacters: 2,
     apiSettings: {
-      url: `${suburl}/api/v1/repos/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
+      url: `${AppSubUrl}/api/v1/repos/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
       onResponse(response) {
         const items = [];
         $.each(response.data, (_i, item) => {
@@ -2180,7 +2177,7 @@ function initU2FAuth() {
   }
   u2fApi.ensureSupport()
     .then(() => {
-      $.getJSON(`${suburl}/user/u2f/challenge`).success((req) => {
+      $.getJSON(`${AppSubUrl}/user/u2f/challenge`).success((req) => {
         u2fApi.sign(req.appId, req.challenge, req.registeredKeys, 30)
           .then(u2fSigned)
           .catch((err) => {
@@ -2193,12 +2190,12 @@ function initU2FAuth() {
       });
     }).catch(() => {
       // Fallback in case browser do not support U2F
-      window.location.href = `${suburl}/user/two_factor`;
+      window.location.href = `${AppSubUrl}/user/two_factor`;
     });
 }
 function u2fSigned(resp) {
   $.ajax({
-    url: `${suburl}/user/u2f/sign`,
+    url: `${AppSubUrl}/user/u2f/sign`,
     type: 'POST',
     headers: { 'X-Csrf-Token': csrf },
     data: JSON.stringify(resp),
@@ -2215,7 +2212,7 @@ function u2fRegistered(resp) {
     return;
   }
   $.ajax({
-    url: `${suburl}/user/settings/security/u2f/register`,
+    url: `${AppSubUrl}/user/settings/security/u2f/register`,
     type: 'POST',
     headers: { 'X-Csrf-Token': csrf },
     data: JSON.stringify(resp),
@@ -2274,7 +2271,7 @@ function initU2FRegister() {
 }
 
 function u2fRegisterRequest() {
-  $.post(`${suburl}/user/settings/security/u2f/request_register`, {
+  $.post(`${AppSubUrl}/user/settings/security/u2f/request_register`, {
     _csrf: csrf,
     name: $('#nickname').val()
   }).success((req) => {
@@ -2337,7 +2334,7 @@ function initTemplateSearch() {
     $('#repo_template_search')
       .dropdown({
         apiSettings: {
-          url: `${suburl}/api/v1/repos/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`,
+          url: `${AppSubUrl}/api/v1/repos/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`,
           onResponse(response) {
             const filteredResponse = { success: true, results: [] };
             filteredResponse.results.push({
@@ -2365,8 +2362,6 @@ function initTemplateSearch() {
 
 $(document).ready(async () => {
   csrf = $('meta[name=_csrf]').attr('content');
-  suburl = $('meta[name=_suburl]').attr('content');
-  staticPrefix = $('meta[name=_staticprefix]').attr('content');
 
   // Show exact time
   $('.time-since').each(function () {
@@ -2455,7 +2450,7 @@ $(document).ready(async () => {
 
   // Emojify
   emojify.setConfig({
-    img_dir: `${suburl}/vendor/plugins/emojify/images`,
+    img_dir: `${AppSubUrl}/vendor/plugins/emojify/images`,
     ignore_emoticons: true
   });
   const hasEmoji = document.getElementsByClassName('has-emoji');
@@ -2575,7 +2570,7 @@ $(document).ready(async () => {
   initPullRequestReview();
   initRepoStatusChecker();
   initTemplateSearch();
-  initContextPopups(suburl);
+  initContextPopups();
 
   // Repo clone url.
   if ($('#repo-clone-url').length > 0) {
@@ -2785,7 +2780,7 @@ function initVueComponents() {
         reposFilter: 'all',
         searchQuery: '',
         isLoading: false,
-        staticPrefix,
+        staticPrefix: StaticUrlPrefix,
         repoTypes: {
           all: {
             count: 0,
@@ -2891,6 +2886,8 @@ function initVueComponents() {
           return 'octicon-repo-forked';
         } if (repo.mirror) {
           return 'octicon-repo-clone';
+        } if (repo.template) {
+          return `octicon-repo-template${repo.private ? '-private' : ''}`;
         } if (repo.private) {
           return 'octicon-lock';
         }
@@ -2921,7 +2918,7 @@ function initVueApp() {
     el,
     data: {
       searchLimit: (document.querySelector('meta[name=_search_limit]') || {}).content,
-      suburl: document.querySelector('meta[name=_suburl]').content,
+      suburl: AppSubUrl,
       uid: Number((document.querySelector('meta[name=_context_uid]') || {}).content),
       activityTopAuthors: window.ActivityTopAuthors || [],
     },
@@ -3037,7 +3034,7 @@ window.initHeatmap = function (appElementId, heatmapUser, locale) {
     el,
 
     data: {
-      suburl: document.querySelector('meta[name=_suburl]').content,
+      suburl: AppSubUrl,
       heatmapUser,
       locale
     },
@@ -3283,7 +3280,7 @@ function initTopicbar() {
           const last = viewDiv.children('a').last();
           for (let i = 0; i < topicArray.length; i++) {
             const link = $('<a class="ui repo-topic small label topic"></a>');
-            link.attr('href', `${suburl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`);
+            link.attr('href', `${AppSubUrl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`);
             link.text(topicArray[i]);
             link.insertBefore(last);
           }
@@ -3331,7 +3328,7 @@ function initTopicbar() {
       label: 'ui small label'
     },
     apiSettings: {
-      url: `${suburl}/api/v1/topics/search?q={query}`,
+      url: `${AppSubUrl}/api/v1/topics/search?q={query}`,
       throttle: 500,
       cache: false,
       onResponse(res) {
@@ -3488,9 +3485,9 @@ function initIssueList() {
   const repoId = $('#repoId').val();
   const crossRepoSearch = $('#crossRepoSearch').val();
   const tp = $('#type').val();
-  let issueSearchUrl = `${suburl}/api/v1/repos/${repolink}/issues?q={query}&type=${tp}`;
+  let issueSearchUrl = `${AppSubUrl}/api/v1/repos/${repolink}/issues?q={query}&type=${tp}`;
   if (crossRepoSearch === 'true') {
-    issueSearchUrl = `${suburl}/api/v1/repos/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`;
+    issueSearchUrl = `${AppSubUrl}/api/v1/repos/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`;
   }
   $('#new-dependency-drop-list')
     .dropdown({
diff --git a/web_src/js/utils.js b/web_src/js/utils.js
new file mode 100644
index 00000000000..2effddd685e
--- /dev/null
+++ b/web_src/js/utils.js
@@ -0,0 +1,5 @@
+const { StaticUrlPrefix } = window.config;
+
+export function svg(name, size) {
+  return `<svg class="svg ${name}" width="${size}" height="${size}" aria-hidden="true"><use xlink:href="${StaticUrlPrefix}/img/svg/icons.svg#${name}"/></svg>`;
+}
diff --git a/web_src/less/_base.less b/web_src/less/_base.less
index 9627ba79142..97fbe048362 100644
--- a/web_src/less/_base.less
+++ b/web_src/less/_base.less
@@ -1168,13 +1168,13 @@ i.icon.centerlock {
 }
 
 .svg {
-    &.green {
+    span.green & {
         color: #21ba45;
     }
-    &.red {
+    span.red & {
         color: #db2828;
     }
-    &.purple {
+    span.purple & {
         color: #a333c8;
     }
 }
diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less
index 81264ccff0e..6bbb0ea3a78 100644
--- a/web_src/less/_repository.less
+++ b/web_src/less/_repository.less
@@ -35,7 +35,7 @@
 
         .svg.octicon-repo-forked {
             margin-top: -1px;
-            font-size: 15px;
+            height: 15px;
         }
 
         .button {
@@ -670,7 +670,7 @@
 
                     .svg {
                         width: 23px;
-                        font-size: 23px;
+                        height: 23px;
                         margin-top: 0.45em;
                     }
                 }
@@ -935,36 +935,36 @@
 
                     &.octicon-circle-slash {
                         margin-top: 5px;
-                        margin-left: -34.5px;
-                        font-size: 20px;
+                        margin-left: -35.5px;
+                        height: 20px;
                         color: #bd2c00;
                     }
 
                     &.octicon-primitive-dot {
                         margin-top: -1px;
-                        margin-left: -28.5px;
+                        margin-left: -35.5px;
                         margin-right: -1px;
-                        font-size: 30px;
+                        height: 30px;
                         color: #6cc644;
                     }
 
                     &.octicon-bookmark {
                         margin-top: 2px;
-                        margin-left: -31px;
+                        margin-left: -35.5px;
                         margin-right: -1px;
-                        font-size: 25px;
+                        height: 25px;
                     }
 
                     &.octicon-eye {
                         margin-top: 3px;
-                        margin-left: -36px;
+                        margin-left: -35.5px;
                         margin-right: 0;
-                        font-size: 22px;
+                        height: 22px;
                     }
 
                     &.octicon-x {
-                        margin-left: -33px;
-                        font-size: 25px;
+                        margin-left: -35.5px;
+                        height: 25px;
                     }
                 }
 
@@ -1724,7 +1724,7 @@
                 padding-bottom: 100px;
 
                 .svg {
-                    font-size: 48px;
+                    height: 48px;
                 }
             }
         }