Skip to content

Fix dynamic content loading init problem #33748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions templates/repo/diff/box.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{{$showFileTree := (and (not .DiffNotAvailable) (gt .Diff.NumFiles 1))}}
<div>
<div class="diff-detail-box diff-box">
<div class="diff-detail-box">
<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-ml-0.5">
{{if $showFileTree}}
<button class="diff-toggle-file-tree-button not-mobile btn interact-fg" data-show-text="{{ctx.Locale.Tr "repo.diff.show_file_tree"}}" data-hide-text="{{ctx.Locale.Tr "repo.diff.hide_file_tree"}}">
Expand Down Expand Up @@ -80,7 +80,7 @@
{{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}}
{{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}}
{{$isReviewFile := and $.IsSigned $.PageIsPullFiles (not $.Repository.IsArchived) $.IsShowingAllCommits}}
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}} tw-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}>
<div class="diff-file-box file-content {{TabSizeClass $.Editorconfig $file.Name}} tw-mt-0" id="diff-{{$file.NameHash}}" data-old-filename="{{$file.OldName}}" data-new-filename="{{$file.Name}}" {{if or ($file.ShouldBeHidden) (not $isExpandable)}}data-folded="true"{{end}}>
<h4 class="diff-file-header sticky-2nd-row ui top attached header">
<div class="diff-file-name tw-flex tw-flex-1 tw-items-center tw-gap-1 tw-flex-wrap">
<button class="fold-file btn interact-bg tw-p-1{{if not $isExpandable}} tw-invisible{{end}}">
Expand Down Expand Up @@ -209,7 +209,7 @@
{{end}}

{{if .Diff.IsIncomplete}}
<div class="diff-file-box diff-box file-content tw-mt-2" id="diff-incomplete">
<div class="diff-file-box file-content tw-mt-2" id="diff-incomplete">
<h4 class="ui top attached header tw-font-normal tw-flex tw-items-center tw-justify-between">
{{ctx.Locale.Tr "repo.diff.too_many_files"}}
<a class="ui basic tiny button" id="diff-show-more-files" data-href="?skip-to={{.Diff.End}}&file-only=true">{{ctx.Locale.Tr "repo.diff.show_more"}}</a>
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/issue/view_content/add_reaction.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<a class="muted">{{svg "octicon-smiley"}}</a>
<div class="menu">
{{range $value := AllowedReactions}}
<a class="item emoji comment-reaction-button" data-tooltip-content="{{$value}}" aria-label="{{$value}}" data-reaction-content="{{$value}}">{{ReactionToEmoji $value}}</a>
<a class="item emoji" data-tooltip-content="{{$value}}" aria-label="{{$value}}" data-reaction-content="{{$value}}" data-global-click="onCommentReactionButtonClick">{{ReactionToEmoji $value}}</a>
{{end}}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/issue/view_content/conversation.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
{{if $diff}}
{{$file := (index $diff.Files 0)}}
<div id="code-preview-{{$comment.ID}}" class="ui table segment{{if $resolved}} tw-hidden{{end}}">
<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}}">
<div class="diff-file-box file-content {{TabSizeClass $.Editorconfig $file.Name}}">
<div class="file-body file-code code-view code-diff code-diff-unified unicode-escaped">
<table>
<tbody>
Expand Down
6 changes: 3 additions & 3 deletions templates/repo/issue/view_content/reactions.tmpl
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<div class="bottom-reactions" data-action-url="{{$.ActionURL}}">
{{range $key, $value := .Reactions}}
{{$hasReacted := $value.HasUser ctx.RootData.SignedUserID}}
<a role="button" class="ui label basic{{if $hasReacted}} primary{{end}}{{if not ctx.RootData.IsSigned}} disabled{{end}} comment-reaction-button"
data-tooltip-content
title="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}"
<a role="button" class="ui label basic{{if $hasReacted}} primary{{end}}{{if not ctx.RootData.IsSigned}} disabled{{end}}"
data-global-click="onCommentReactionButtonClick"
data-tooltip-content title="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}"
aria-label="{{$value.GetFirstUsers}}{{if gt ($value.GetMoreUserCount) 0}} {{ctx.Locale.Tr "repo.reactions_more" $value.GetMoreUserCount}}{{end}}"
data-tooltip-placement="bottom-start"
data-reaction-content="{{$key}}" data-has-reacted="{{$hasReacted}}">
Expand Down
2 changes: 1 addition & 1 deletion templates/shared/search/code/results.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<div class="repository search">
{{range $result := .SearchResults}}
{{$repo := or $.Repo (index $.RepoMaps .RepoID)}}
<div class="diff-file-box diff-box file-content non-diff-file-content repo-search-result">
<div class="diff-file-box file-content non-diff-file-content repo-search-result">
<h4 class="ui top attached header tw-font-normal tw-flex tw-flex-wrap">
{{if not $.Repo}}
<span class="file tw-flex-1">
Expand Down
4 changes: 0 additions & 4 deletions web_src/css/repo.css
Original file line number Diff line number Diff line change
Expand Up @@ -1085,10 +1085,6 @@ td .commit-summary {
height: 30px;
}

.repository .diff-box .resolved-placeholder .button {
padding: 8px 12px;
}

.repository .diff-file-box .header {
background-color: var(--color-box-header);
}
Expand Down
19 changes: 12 additions & 7 deletions web_src/js/features/common-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {GET} from '../modules/fetch.ts';
import {showGlobalErrorMessage} from '../bootstrap.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {queryElems} from '../utils/dom.ts';
import {observeAddedElement} from '../modules/observer.ts';

const {appUrl} = window.config;

Expand All @@ -28,17 +29,19 @@ export function initFootLanguageMenu() {
}

export function initGlobalDropdown() {
// Semantic UI modules.
const $uiDropdowns = fomanticQuery('.ui.dropdown');

// do not init "custom" dropdowns, "custom" dropdowns are managed by their own code.
$uiDropdowns.filter(':not(.custom)').dropdown({hideDividers: 'empty'});
observeAddedElement('.ui.dropdown:not(.custom)', (el) => {
const $dropdown = fomanticQuery(el);
if ($dropdown.data('module-dropdown')) return; // do not re-init if other code has already initialized it.

$dropdown.dropdown('setting', {hideDividers: 'empty'});

if (el.classList.contains('jump')) {
// The "jump" means this dropdown is mainly used for "menu" purpose,
// clicking an item will jump to somewhere else or trigger an action/function.
// When a dropdown is used for non-refresh actions with tippy,
// it must have this "jump" class to hide the tippy when dropdown is closed.
$uiDropdowns.filter('.jump').dropdown('setting', {
$dropdown.dropdown('setting', {
action: 'hide',
onShow() {
// hide associated tooltip while dropdown is open
Expand All @@ -59,6 +62,7 @@ export function initGlobalDropdown() {
}, 2000);
},
});
}

// Special popup-directions, prevent Fomantic from guessing the popup direction.
// With default "direction: auto", if the viewport height is small, Fomantic would show the popup upward,
Expand All @@ -67,8 +71,9 @@ export function initGlobalDropdown() {
// But we can not set "direction: downward" for all dropdowns, because there is a bug in dropdown menu positioning when calculating the "left" position,
// which would make some dropdown popups slightly shift out of the right viewport edge in some cases.
// eg: the "Create New Repo" menu on the navbar.
$uiDropdowns.filter('.upward').dropdown('setting', 'direction', 'upward');
$uiDropdowns.filter('.downward').dropdown('setting', 'direction', 'downward');
if (el.classList.contains('upward')) $dropdown.dropdown('setting', 'direction', 'upward');
if (el.classList.contains('downward')) $dropdown.dropdown('setting', 'direction', 'downward');
});
}

export function initGlobalTabularMenu() {
Expand Down
12 changes: 3 additions & 9 deletions web_src/js/features/comp/ReactionSelector.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import {POST} from '../../modules/fetch.ts';
import {fomanticQuery} from '../../modules/fomantic/base.ts';
import type {DOMEvent} from '../../utils/dom.ts';
import {registerGlobalEventFunc} from '../../modules/observer.ts';

export function initCompReactionSelector(parent: ParentNode = document) {
for (const container of parent.querySelectorAll<HTMLElement>('.issue-content, .diff-file-body')) {
container.addEventListener('click', async (e: DOMEvent<MouseEvent>) => {
export function initCompReactionSelector() {
registerGlobalEventFunc('click', 'onCommentReactionButtonClick', async (target: HTMLElement, e: DOMEvent<MouseEvent>) => {
// there are 2 places for the "reaction" buttons, one is the top-right reaction menu, one is the bottom of the comment
const target = e.target.closest('.comment-reaction-button');
if (!target) return;
e.preventDefault();

if (target.classList.contains('disabled')) return;
Expand All @@ -29,9 +26,6 @@ export function initCompReactionSelector(parent: ParentNode = document) {
bottomReactions?.remove();
if (data.html) {
commentContainer.insertAdjacentHTML('beforeend', data.html);
const bottomReactionsDropdowns = commentContainer.querySelectorAll('.bottom-reactions .dropdown.select-reaction');
fomanticQuery(bottomReactionsDropdowns).dropdown(); // re-init the dropdown
}
});
}
}
15 changes: 5 additions & 10 deletions web_src/js/features/repo-diff.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {initCompReactionSelector} from './comp/ReactionSelector.ts';
import {initRepoIssueContentHistory} from './repo-issue-content.ts';
import {initDiffFileTree} from './repo-diff-filetree.ts';
import {initDiffCommitSelect} from './repo-diff-commitselect.ts';
Expand All @@ -8,17 +7,16 @@ import {initImageDiff} from './imagediff.ts';
import {showErrorToast} from '../modules/toast.ts';
import {submitEventSubmitter, queryElemSiblings, hideElem, showElem, animateOnce, addDelegatedEventListener, createElementFromHTML, queryElems} from '../utils/dom.ts';
import {POST, GET} from '../modules/fetch.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {createTippy} from '../modules/tippy.ts';
import {invertFileFolding} from './file-fold.ts';
import {parseDom} from '../utils.ts';
import {observeAddedElement} from '../modules/observer.ts';

const {i18n} = window.config;

function initRepoDiffFileViewToggle() {
function initRepoDiffFileBox(el: HTMLElement) {
// switch between "rendered" and "source", for image and CSV files
// FIXME: this event listener is not correctly added to "load more files"
queryElems(document, '.file-view-toggle', (btn) => btn.addEventListener('click', () => {
queryElems(el, '.file-view-toggle', (btn) => btn.addEventListener('click', () => {
queryElemSiblings(btn, '.file-view-toggle', (el) => el.classList.remove('active'));
btn.classList.add('active');

Expand Down Expand Up @@ -75,7 +73,6 @@ function initRepoDiffConversationForm() {
el.classList.add('tw-invisible');
}
}
fomanticQuery(newConversationHolder.querySelectorAll('.ui.dropdown')).dropdown();

// the default behavior is to add a pending review, so if no submitter, it also means "pending_review"
if (!submitter || submitter?.matches('button[name="pending_review"]')) {
Expand Down Expand Up @@ -110,8 +107,6 @@ function initRepoDiffConversationForm() {
if (elConversationHolder) {
const elNewConversation = createElementFromHTML(data);
elConversationHolder.replaceWith(elNewConversation);
queryElems(elConversationHolder, '.ui.dropdown:not(.custom)', (el) => fomanticQuery(el).dropdown());
initCompReactionSelector(elNewConversation);
} else {
window.location.reload();
}
Expand Down Expand Up @@ -149,7 +144,7 @@ function initDiffHeaderPopup() {

// Will be called when the show more (files) button has been pressed
function onShowMoreFiles() {
// FIXME: here the init calls are incomplete: at least it misses dropdown & initCompReactionSelector & initRepoDiffFileViewToggle
// TODO: replace these calls with the "observer.ts" methods
initRepoIssueContentHistory();
initViewedCheckboxListenerFor();
countAndUpdateViewedFiles();
Expand Down Expand Up @@ -255,11 +250,11 @@ export function initRepoDiffView() {
initDiffCommitSelect();
initRepoDiffShowMore();
initDiffHeaderPopup();
initRepoDiffFileViewToggle();
initViewedCheckboxListenerFor();
initExpandAndCollapseFilesButton();
initRepoDiffHashChangeListener();

observeAddedElement('#diff-file-boxes .diff-file-box', initRepoDiffFileBox);
addDelegatedEventListener(document, 'click', '.fold-file', (el) => {
invertFileFolding(el.closest('.file-content'), el);
});
Expand Down
5 changes: 3 additions & 2 deletions web_src/js/features/repo-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ export function initRepoGraphGit() {
}

const flowSelectRefsDropdown = document.querySelector('#flow-select-refs-dropdown');
fomanticQuery(flowSelectRefsDropdown).dropdown('set selected', dropdownSelected);
fomanticQuery(flowSelectRefsDropdown).dropdown({
const $dropdown = fomanticQuery(flowSelectRefsDropdown);
$dropdown.dropdown({
clearable: true,
fullTextSeach: 'exact',
onRemove(toRemove: string) {
Expand All @@ -110,6 +110,7 @@ export function initRepoGraphGit() {
updateGraph();
},
});
$dropdown.dropdown('set selected', dropdownSelected);

graphContainer.addEventListener('mouseenter', (e: DOMEvent<MouseEvent>) => {
if (e.target.matches('#rev-list li')) {
Expand Down
4 changes: 2 additions & 2 deletions web_src/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import {initRepoContributors} from './features/contributors.ts';
import {initRepoCodeFrequency} from './features/code-frequency.ts';
import {initRepoRecentCommits} from './features/recent-commits.ts';
import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.ts';
import {initDirAuto} from './modules/dirauto.ts';
import {initAddedElementObserver} from './modules/observer.ts';
import {initRepositorySearch} from './features/repo-search.ts';
import {initColorPickers} from './features/colorpicker.ts';
import {initAdminSelfCheck} from './features/admin/selfcheck.ts';
Expand All @@ -86,7 +86,7 @@ import {
} from './features/common-form.ts';

initGiteaFomantic();
initDirAuto();
initAddedElementObserver();
initSubmitEventPolyfill();

function callInitFunctions(functions: (() => any)[]) {
Expand Down
44 changes: 0 additions & 44 deletions web_src/js/modules/dirauto.ts

This file was deleted.

89 changes: 89 additions & 0 deletions web_src/js/modules/observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {isDocumentFragmentOrElementNode} from '../utils/dom.ts';

type DirElement = HTMLInputElement | HTMLTextAreaElement;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's supposed to be direction, not directory.
I was confused for a second.

Copy link
Contributor Author

@wxiaoguang wxiaoguang Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's from old "dirauto.js"

This file is a rewritten of "dirauto.js", more than about a half of code is from old code.

image


// for performance considerations, it only uses performant syntax
function attachDirAuto(el: Partial<DirElement>) {
if (el.type !== 'hidden' &&
el.type !== 'checkbox' &&
el.type !== 'radio' &&
el.type !== 'range' &&
el.type !== 'color') {
el.dir = 'auto';
}
}

type GlobalInitFunc<T extends HTMLElement> = (el: T) => void | Promise<void>;
const globalInitFuncs: Record<string, GlobalInitFunc<HTMLElement>> = {};
function attachGlobalInit(el: HTMLElement) {
const initFunc = el.getAttribute('data-global-init');
const func = globalInitFuncs[initFunc];
if (!func) throw new Error(`Global init function "${initFunc}" not found`);
func(el);
}

type GlobalEventFunc<T extends HTMLElement, E extends Event> = (el: T, e: E) => (void | Promise<void>);
const globalEventFuncs: Record<string, GlobalEventFunc<HTMLElement, Event>> = {};
export function registerGlobalEventFunc<T extends HTMLElement, E extends Event>(event: string, name: string, func: GlobalEventFunc<T, E>) {
globalEventFuncs[`${event}:${name}`] = func as any;
}

type SelectorHandler = {
selector: string,
handler: (el: HTMLElement) => void,
};

const selectorHandlers: SelectorHandler[] = [
{selector: 'input, textarea', handler: attachDirAuto},
{selector: '[data-global-init]', handler: attachGlobalInit},
];

export function observeAddedElement(selector: string, handler: (el: HTMLElement) => void) {
selectorHandlers.push({selector, handler});
const docNodes = document.querySelectorAll<HTMLElement>(selector);
for (const el of docNodes) {
handler(el);
}
}

export function initAddedElementObserver(): void {
const observer = new MutationObserver((mutationList) => {
const len = mutationList.length;
for (let i = 0; i < len; i++) {
const mutation = mutationList[i];
const len = mutation.addedNodes.length;
for (let i = 0; i < len; i++) {
const addedNode = mutation.addedNodes[i] as HTMLElement;
if (!isDocumentFragmentOrElementNode(addedNode)) continue;

for (const {selector, handler} of selectorHandlers) {
if (addedNode.matches(selector)) {
handler(addedNode);
}
const children = addedNode.querySelectorAll<HTMLElement>(selector);
for (const el of children) {
handler(el);
}
}
}
}
});

for (const {selector, handler} of selectorHandlers) {
const docNodes = document.querySelectorAll<HTMLElement>(selector);
for (const el of docNodes) {
handler(el);
}
}

observer.observe(document, {subtree: true, childList: true});

document.addEventListener('click', (e) => {
const elem = (e.target as HTMLElement).closest<HTMLElement>('[data-global-click]');
if (!elem) return;
const funcName = elem.getAttribute('data-global-click');
const func = globalEventFuncs[`click:${funcName}`];
if (!func) throw new Error(`Global event function "click:${funcName}" not found`);
func(elem, e);
});
}
Loading