From 1e29bccddbeb29eec3ceb507612851021ab4d60d Mon Sep 17 00:00:00 2001
From: Yarden Shoham <git@yardenshoham.com>
Date: Sat, 2 Mar 2024 21:31:59 +0200
Subject: [PATCH] Fix elipsis button not working if the last commit loading is
 deferred (#29544)

Before this change, if we had more than 200 entries being deferred in
loading, the entire table would get replaced thus losing any event
listeners attached to the elements within the table, such as the elipsis
button and commit list with tippy.

With this change we remove the previous javascript code that replaced
the table and use htmx to replace the table.

htmx attributes added:
- `hx-indicator="tr.notready td.message span"`: attach the loading
spinner to the files whose last commit is still being loaded
- `hx-trigger="load"` trigger the request-replace behavior as soon as
possible
- `hx-swap="morph"`: use the idiomorph morphing algorithm, this is the
thing that makes it so the elipsis button event listener is kept during
the replacement, fixing the bug because we don't actually replace the
table, only modifying it
- `hx-post="{{.LastCommitLoaderURL}}"`: make a post request to this url
to get the table with all of the commit information

As part of this change I removed the handling of partial replacement in
the case we have less than 200 "not ready" files. The first reason is
that I couldn't make htmx replace only a subset of returned elements,
the second reason is that we have a cache implemented in the backend
already so the only cost added is that we query the cache a few times
(which is sure to be populated due to the initial request), and the last
reason is that since the last refactor of this functionality that
removed jQuery we don't properly send the "not ready" entries as the
backend expects `FormData` with `f[]` and we send a JSON with `f` so we
always query for all rows anyway.

# Before

![before](https://github.com/go-gitea/gitea/assets/20454870/482ebfec-66c5-40cc-9c1e-e3b3bfe1bbc1)

# After

![after](https://github.com/go-gitea/gitea/assets/20454870/454c517e-3a4e-4006-a49f-99cc56e0fd60)

---------

Signed-off-by: Yarden Shoham <git@yardenshoham.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit 937e8b55149388840bbf6c4d7216495bc3dd2fe9)
---
 routers/web/repo/view.go           | 24 ++++++-----------
 templates/repo/view_list.tmpl      |  6 ++---
 web_src/js/features/repo-commit.js | 43 ------------------------------
 web_src/js/index.js                |  7 +----
 4 files changed, 12 insertions(+), 68 deletions(-)

diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index 36d6ed022e..27984e7621 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -35,7 +35,6 @@ import (
 	"code.gitea.io/gitea/modules/actions"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
-	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/highlight"
 	"code.gitea.io/gitea/modules/lfs"
@@ -881,25 +880,18 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
 		defer cancel()
 	}
 
-	selected := make(container.Set[string])
-	selected.AddMultiple(ctx.FormStrings("f[]")...)
-
-	entries := allEntries
-	if len(selected) > 0 {
-		entries = make(git.Entries, 0, len(selected))
-		for _, entry := range allEntries {
-			if selected.Contains(entry.Name()) {
-				entries = append(entries, entry)
-			}
-		}
-	}
-
-	var latestCommit *git.Commit
-	ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(commitInfoCtx, ctx.Repo.Commit, ctx.Repo.TreePath)
+	files, latestCommit, err := allEntries.GetCommitsInfo(commitInfoCtx, ctx.Repo.Commit, ctx.Repo.TreePath)
 	if err != nil {
 		ctx.ServerError("GetCommitsInfo", err)
 		return nil
 	}
+	ctx.Data["Files"] = files
+	for _, f := range files {
+		if f.Commit == nil {
+			ctx.Data["HasFilesWithoutLatestCommit"] = true
+			break
+		}
+	}
 
 	if !loadLatestCommitData(ctx, latestCommit) {
 		return nil
diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl
index c1ef4ff4cb..988a5ddd50 100644
--- a/templates/repo/view_list.tmpl
+++ b/templates/repo/view_list.tmpl
@@ -1,7 +1,7 @@
-<table id="repo-files-table" class="ui single line table gt-mt-0" data-last-commit-loader-url="{{.LastCommitLoaderURL}}">
+<table id="repo-files-table" class="ui single line table gt-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
 	<thead>
 		<tr class="commit-list">
-			<th colspan="2" {{if not .LatestCommit}}class="notready"{{end}}>
+			<th colspan="2">
 				{{template "repo/latest_commit" .}}
 			</th>
 			<th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Committer}}{{TimeSince .LatestCommit.Committer.When ctx.Locale}}{{end}}{{end}}</th>
@@ -55,7 +55,7 @@
 							{{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}}
 							{{RenderCommitMessageLinkSubject $.Context $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}}
 						{{else}}
-							<div class="ui active tiny slow centered inline">…</div>
+							<div class="ui active tiny slow centered inline"></div>
 						{{end}}
 					</span>
 				</td>
diff --git a/web_src/js/features/repo-commit.js b/web_src/js/features/repo-commit.js
index 7e2f6fa58e..f61ea08a42 100644
--- a/web_src/js/features/repo-commit.js
+++ b/web_src/js/features/repo-commit.js
@@ -1,7 +1,5 @@
 import {createTippy} from '../modules/tippy.js';
 import {toggleElem} from '../utils/dom.js';
-import {parseDom} from '../utils.js';
-import {POST} from '../modules/fetch.js';
 
 export function initRepoEllipsisButton() {
   for (const button of document.querySelectorAll('.js-toggle-commit-body')) {
@@ -14,47 +12,6 @@ export function initRepoEllipsisButton() {
   }
 }
 
-export async function initRepoCommitLastCommitLoader() {
-  const entryMap = {};
-
-  const entries = Array.from(document.querySelectorAll('table#repo-files-table tr.notready'), (el) => {
-    const entryName = el.getAttribute('data-entryname');
-    entryMap[entryName] = el;
-    return entryName;
-  });
-
-  if (entries.length === 0) {
-    return;
-  }
-
-  const lastCommitLoaderURL = document.querySelector('table#repo-files-table').getAttribute('data-last-commit-loader-url');
-
-  if (entries.length > 200) {
-    // For more than 200 entries, replace the entire table
-    const response = await POST(lastCommitLoaderURL);
-    const data = await response.text();
-    document.querySelector('table#repo-files-table').outerHTML = data;
-    return;
-  }
-
-  // For fewer entries, update individual rows
-  const response = await POST(lastCommitLoaderURL, {data: {'f': entries}});
-  const data = await response.text();
-  const doc = parseDom(data, 'text/html');
-  for (const row of doc.querySelectorAll('tr')) {
-    if (row.className === 'commit-list') {
-      document.querySelector('table#repo-files-table .commit-list')?.replaceWith(row);
-      continue;
-    }
-    // there are other <tr> rows in response (eg: <tr class="has-parent">)
-    // at the moment only the "data-entryname" rows should be processed
-    const entryName = row.getAttribute('data-entryname');
-    if (entryName) {
-      entryMap[entryName]?.replaceWith(row);
-    }
-  }
-}
-
 export function initCommitStatuses() {
   for (const element of document.querySelectorAll('[data-tippy="commit-statuses"]')) {
     const top = document.querySelector('.repository.file.list') || document.querySelector('.repository.diff');
diff --git a/web_src/js/index.js b/web_src/js/index.js
index b7f3ba99a0..c7eac9d242 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -33,11 +33,7 @@ import {
   initRepoPullRequestAllowMaintainerEdit,
   initRepoPullRequestReview, initRepoIssueSidebarList, initArchivedLabelHandler,
 } from './features/repo-issue.js';
-import {
-  initRepoEllipsisButton,
-  initRepoCommitLastCommitLoader,
-  initCommitStatuses,
-} from './features/repo-commit.js';
+import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.js';
 import {
   initFootLanguageMenu,
   initGlobalButtonClickOnEnter,
@@ -148,7 +144,6 @@ onDomReady(() => {
   initRepoCommentForm();
   initRepoEllipsisButton();
   initRepoDiffCommitBranchesAndTags();
-  initRepoCommitLastCommitLoader();
   initRepoEditor();
   initRepoGraphGit();
   initRepoIssueContentHistory();