diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl
index 26737f110e..20e0c9db66 100644
--- a/templates/repo/diff/box.tmpl
+++ b/templates/repo/diff/box.tmpl
@@ -164,24 +164,22 @@
{{ctx.Locale.Tr "repo.pulls.has_viewed_file"}}
{{end}}
-
- {{svg "octicon-kebab-horizontal" 18 "icon tw-mx-2"}}
-
diff --git a/tests/integration/pull_compare_test.go b/tests/integration/pull_compare_test.go
index def6506253..ad0be72dcb 100644
--- a/tests/integration/pull_compare_test.go
+++ b/tests/integration/pull_compare_test.go
@@ -97,7 +97,7 @@ func TestPullCompare_EnableAllowEditsFromMaintainer(t *testing.T) {
user2Session := loginUser(t, "user2")
resp = user2Session.MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("%s/files", prURL)), http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
- nodes := htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .dropdown .menu a")
+ nodes := htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .tippy-target a")
if assert.Equal(t, 1, nodes.Length()) {
// there is only "View File" button, no "Edit File" button
assert.Equal(t, "View File", nodes.First().Text())
@@ -121,7 +121,7 @@ func TestPullCompare_EnableAllowEditsFromMaintainer(t *testing.T) {
// user2 (admin of repo3) goes to the PR files page again
resp = user2Session.MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("%s/files", prURL)), http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
- nodes = htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .dropdown .menu a")
+ nodes = htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .tippy-target a")
if assert.Equal(t, 2, nodes.Length()) {
// there are "View File" button and "Edit File" button
assert.Equal(t, "View File", nodes.First().Text())
diff --git a/web_src/css/modules/tippy.css b/web_src/css/modules/tippy.css
index 53c3d5aaea..55b9751cc6 100644
--- a/web_src/css/modules/tippy.css
+++ b/web_src/css/modules/tippy.css
@@ -77,8 +77,10 @@
align-items: center;
padding: 9px 18px;
color: inherit;
+ background: inherit;
text-decoration: none;
gap: 10px;
+ width: 100%;
}
.tippy-box[data-theme="menu"] .item:hover {
diff --git a/web_src/js/features/repo-diff.ts b/web_src/js/features/repo-diff.ts
index 0d489665a2..f39de96f5b 100644
--- a/web_src/js/features/repo-diff.ts
+++ b/web_src/js/features/repo-diff.ts
@@ -18,6 +18,7 @@ import {
} from '../utils/dom.ts';
import {POST, GET} from '../modules/fetch.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
+import {createTippy} from '../modules/tippy.ts';
const {pageData, i18n} = window.config;
@@ -140,12 +141,22 @@ export function initRepoDiffConversationNav() {
});
}
+function initDiffHeaderPopup() {
+ for (const btn of document.querySelectorAll('.diff-header-popup-btn:not([data-header-popup-initialized])')) {
+ btn.setAttribute('data-header-popup-initialized', '');
+ const popup = btn.nextElementSibling;
+ if (!popup?.matches('.tippy-target')) throw new Error('Popup element not found');
+ createTippy(btn, {content: popup, theme: 'menu', placement: 'bottom', trigger: 'click', interactive: true, hideOnClick: true});
+ }
+}
+
// Will be called when the show more (files) button has been pressed
function onShowMoreFiles() {
initRepoIssueContentHistory();
initViewedCheckboxListenerFor();
countAndUpdateViewedFiles();
initImageDiff();
+ initDiffHeaderPopup();
}
export async function loadMoreFiles(url) {
@@ -221,6 +232,7 @@ export function initRepoDiffView() {
initDiffFileList();
initDiffCommitSelect();
initRepoDiffShowMore();
+ initDiffHeaderPopup();
initRepoDiffFileViewToggle();
initViewedCheckboxListenerFor();
initExpandAndCollapseFilesButton();
diff --git a/web_src/js/features/repo-unicode-escape.ts b/web_src/js/features/repo-unicode-escape.ts
index 7a9bca7a37..0c7d2e8592 100644
--- a/web_src/js/features/repo-unicode-escape.ts
+++ b/web_src/js/features/repo-unicode-escape.ts
@@ -1,13 +1,13 @@
-import {hideElem, queryElemSiblings, showElem, toggleElem} from '../utils/dom.ts';
+import {addDelegatedEventListener, hideElem, queryElemSiblings, showElem, toggleElem} from '../utils/dom.ts';
export function initUnicodeEscapeButton() {
- document.addEventListener('click', (e) => {
- const btn = e.target.closest('.escape-button, .unescape-button, .toggle-escape-button');
- if (!btn) return;
-
+ addDelegatedEventListener(document, 'click', '.escape-button, .unescape-button, .toggle-escape-button', (btn, e) => {
e.preventDefault();
- const fileContent = btn.closest('.file-content, .non-diff-file-content');
+ const fileContentElemId = btn.getAttribute('data-file-content-elem-id');
+ const fileContent = fileContentElemId ?
+ document.querySelector(`#${fileContentElemId}`) :
+ btn.closest('.file-content, .non-diff-file-content');
const fileView = fileContent?.querySelectorAll('.file-code, .file-view');
if (btn.matches('.escape-button')) {
for (const el of fileView) el.classList.add('unicode-escaped');
diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts
index 4bbb0c414a..a4c7c0e4c6 100644
--- a/web_src/js/utils/dom.ts
+++ b/web_src/js/utils/dom.ts
@@ -2,10 +2,10 @@ import {debounce} from 'throttle-debounce';
import type {Promisable} from 'type-fest';
import type $ from 'jquery';
-type ElementArg = Element | string | NodeListOf | Array | ReturnType;
+type ArrayLikeIterable = ArrayLike & Iterable; // for NodeListOf and Array
+type ElementArg = Element | string | ArrayLikeIterable | ReturnType;
type ElementsCallback = (el: T) => Promisable;
type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable;
-type ArrayLikeIterable = ArrayLike & Iterable; // for NodeListOf and Array
function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) {
if (typeof el === 'string' || el instanceof String) {