From 7dd3c01606925eefe99fdcb5d543aa5197cde79f Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Fri, 19 Apr 2024 00:45:50 +0800
Subject: [PATCH] Refactor and fix archive link bug (#30535)

Regression of #29920
Fixes: #30569

Also this is a rewriting to eliminate the remaining jQuery usages from code.

Co-authored-by: Giteabot <teabot@gitea.io>
(cherry picked from commit d0e07083559180b124a08359fcc72f9ef695e723)

Conflicts:
	- web_src/js/features/repo-common.js
	  Conflict resolved in favour of Gitea.
---
 web_src/js/features/repo-common.js | 54 ++++++++++++------------------
 web_src/js/utils/dom.js            |  4 +++
 2 files changed, 26 insertions(+), 32 deletions(-)

diff --git a/web_src/js/features/repo-common.js b/web_src/js/features/repo-common.js
index d6c47d8512..88aa93d850 100644
--- a/web_src/js/features/repo-common.js
+++ b/web_src/js/features/repo-common.js
@@ -1,45 +1,35 @@
 import $ from 'jquery';
-import {hideElem, showElem} from '../utils/dom.js';
+import {hideElem, queryElems, showElem} from '../utils/dom.js';
 import {POST} from '../modules/fetch.js';
+import {showErrorToast} from '../modules/toast.js';
+import {sleep} from '../utils.js';
 
-async function getArchive($target, url, first) {
-  const dropdownBtn = $target[0].closest('.ui.dropdown.button') ?? $target[0].closest('.ui.dropdown.btn') ?? $target[0].closest('details.download');
-
+async function onDownloadArchive(e) {
+  e.preventDefault();
+  // there are many places using the "archive-link", eg: the dropdown on the repo code page, the release list
+  const el = e.target.closest('a.archive-link[href]');
+  const targetLoading = el.closest('.ui.dropdown') ?? el;
+  targetLoading.classList.add('is-loading', 'loading-icon-2px');
   try {
-    dropdownBtn.classList.add('is-loading');
-    const response = await POST(url);
-    if (response.status === 200) {
-      const data = await response.json();
-      if (!data) {
-        // XXX Shouldn't happen?
-        dropdownBtn.classList.remove('is-loading');
-        return;
-      }
+    for (let tryCount = 0; ;tryCount++) {
+      const response = await POST(el.href);
+      if (!response.ok) throw new Error(`Invalid server response: ${response.status}`);
 
-      if (!data.complete) {
-        // Wait for only three quarters of a second initially, in case it's
-        // quickly archived.
-        setTimeout(() => {
-          getArchive($target, url, false);
-        }, first ? 750 : 2000);
-      } else {
-        // We don't need to continue checking.
-        dropdownBtn.classList.remove('is-loading');
-        window.location.href = url;
-      }
+      const data = await response.json();
+      if (data.complete) break;
+      await sleep(Math.min((tryCount + 1) * 750, 2000));
     }
-  } catch {
-    dropdownBtn.classList.remove('is-loading');
+    window.location.href = el.href; // the archive is ready, start real downloading
+  } catch (e) {
+    console.error(e);
+    showErrorToast(`Failed to download the archive: ${e}`, {duration: 2500});
+  } finally {
+    targetLoading.classList.remove('is-loading', 'loading-icon-2px');
   }
 }
 
 export function initRepoArchiveLinks() {
-  $('.archive-link').on('click', function (event) {
-    event.preventDefault();
-    const url = this.getAttribute('href');
-    if (!url) return;
-    getArchive($(event.target), url, true);
-  });
+  queryElems('a.archive-link[href]', (el) => el.addEventListener('click', onDownloadArchive));
 }
 
 export function initRepoCloneLink() {
diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js
index 82993e897c..a2a79e91e2 100644
--- a/web_src/js/utils/dom.js
+++ b/web_src/js/utils/dom.js
@@ -69,6 +69,10 @@ export function queryElemChildren(parent, selector = '*', fn) {
   return applyElemsCallback(parent.querySelectorAll(`:scope > ${selector}`), fn);
 }
 
+export function queryElems(selector, fn) {
+  return applyElemsCallback(document.querySelectorAll(selector), fn);
+}
+
 export function onDomReady(cb) {
   if (document.readyState === 'loading') {
     document.addEventListener('DOMContentLoaded', cb);