Skip to content

Commit 03101ba

Browse files
authored
Merge branch 'main' into missing-lfs-fixes
2 parents f1e4b64 + d28a7f9 commit 03101ba

File tree

7 files changed

+67
-50
lines changed

7 files changed

+67
-50
lines changed

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,12 +410,12 @@ watch-backend: go-check ## watch backend files and continuously rebuild
410410
test: test-frontend test-backend ## test everything
411411

412412
.PHONY: test-backend
413-
test-backend: ## test frontend files
413+
test-backend: ## test backend files
414414
@echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..."
415415
@$(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES)
416416

417417
.PHONY: test-frontend
418-
test-frontend: node_modules ## test backend files
418+
test-frontend: node_modules ## test frontend files
419419
npx vitest
420420

421421
.PHONY: test-check
@@ -737,7 +737,7 @@ generate-go: $(TAGS_PREREQ)
737737

738738
.PHONY: security-check
739739
security-check:
740-
go run $(GOVULNCHECK_PACKAGE) ./...
740+
go run $(GOVULNCHECK_PACKAGE) -show color ./...
741741

742742
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
743743
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@

modules/templates/util_avatar.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ func AvatarHTML(src string, size int, class, name string) template.HTML {
3434
name = "avatar"
3535
}
3636

37-
return template.HTML(`<img loading="lazy" class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
37+
// use empty alt, otherwise if the image fails to load, the width will follow the "alt" text's width
38+
return template.HTML(`<img loading="lazy" alt="" class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
3839
}
3940

4041
// Avatar renders user avatars. args: user, size (int), class (string)

templates/repo/icon.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{$avatarLink := (.RelAvatarLink ctx)}}
22
{{if $avatarLink}}
3-
<img class="ui avatar tw-align-middle" src="{{$avatarLink}}" width="24" height="24" alt="{{.FullName}}">
3+
<img class="ui avatar tw-align-middle" src="{{$avatarLink}}" width="24" height="24" alt="" aria-hidden="true">
44
{{else if $.IsMirror}}
55
{{svg "octicon-mirror" 24}}
66
{{else if $.IsFork}}

web_src/fomantic/build/components/dropdown.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,7 @@ $.fn.dropdown = function(parameters) {
752752
if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
753753
module.show();
754754
}
755+
settings.onAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items
755756
}
756757
;
757758
if(settings.useLabels && module.has.maxSelections()) {
@@ -3992,6 +3993,8 @@ $.fn.dropdown.settings = {
39923993
onShow : function(){},
39933994
onHide : function(){},
39943995

3996+
onAfterFiltered: function(){}, // GITEA-PATCH: callback to correctly handle the filtered items
3997+
39953998
/* Component */
39963999
name : 'Dropdown',
39974000
namespace : 'dropdown',

web_src/js/modules/fomantic/dropdown.test.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,27 @@ test('hideScopedEmptyDividers-simple', () => {
2323
`);
2424
});
2525

26-
test('hideScopedEmptyDividers-hidden1', () => {
26+
test('hideScopedEmptyDividers-items-all-filtered', () => {
27+
const container = createElementFromHTML(`<div>
28+
<div class="any"></div>
29+
<div class="divider"></div>
30+
<div class="item filtered">a</div>
31+
<div class="item filtered">b</div>
32+
<div class="divider"></div>
33+
<div class="any"></div>
34+
</div>`);
35+
hideScopedEmptyDividers(container);
36+
expect(container.innerHTML).toEqual(`
37+
<div class="any"></div>
38+
<div class="divider hidden transition"></div>
39+
<div class="item filtered">a</div>
40+
<div class="item filtered">b</div>
41+
<div class="divider"></div>
42+
<div class="any"></div>
43+
`);
44+
});
45+
46+
test('hideScopedEmptyDividers-hide-last', () => {
2747
const container = createElementFromHTML(`<div>
2848
<div class="item">a</div>
2949
<div class="divider" data-scope="b"></div>
@@ -37,7 +57,7 @@ test('hideScopedEmptyDividers-hidden1', () => {
3757
`);
3858
});
3959

40-
test('hideScopedEmptyDividers-hidden2', () => {
60+
test('hideScopedEmptyDividers-scoped-items', () => {
4161
const container = createElementFromHTML(`<div>
4262
<div class="item" data-scope="">a</div>
4363
<div class="divider" data-scope="b"></div>

web_src/js/modules/fomantic/dropdown.ts

Lines changed: 33 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,33 @@ const fomanticDropdownFn = $.fn.dropdown;
99
// use our own `$().dropdown` function to patch Fomantic's dropdown module
1010
export function initAriaDropdownPatch() {
1111
if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once');
12+
$.fn.dropdown.settings.onAfterFiltered = onAfterFiltered;
1213
$.fn.dropdown = ariaDropdownFn;
1314
$.fn.fomanticExt.onResponseKeepSelectedItem = onResponseKeepSelectedItem;
1415
(ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings;
1516
}
1617

1718
// the patched `$.fn.dropdown` function, it passes the arguments to Fomantic's `$.fn.dropdown` function, and:
18-
// * it does the one-time attaching on the first call
19-
// * it delegates the `onLabelCreate` to the patched `onLabelCreate` to add necessary aria attributes
19+
// * it does the one-time element event attaching on the first call
20+
// * it delegates the module internal functions like `onLabelCreate` to the patched functions to add more features.
2021
function ariaDropdownFn(this: any, ...args: Parameters<FomanticInitFunction>) {
2122
const ret = fomanticDropdownFn.apply(this, args);
2223

23-
// if the `$().dropdown()` call is without arguments, or it has non-string (object) argument,
24-
// it means that this call will reset the dropdown internal settings, then we need to re-delegate the callbacks.
25-
const needDelegate = (!args.length || typeof args[0] !== 'string');
2624
for (const el of this) {
2725
if (!el[ariaPatchKey]) {
28-
attachInit(el);
26+
// the elements don't belong to the dropdown "module" and won't be reset
27+
// so we only need to initialize them once.
28+
attachInitElements(el);
2929
}
30-
if (needDelegate) {
31-
delegateOne($(el));
30+
31+
// if the `$().dropdown()` call is without arguments, or it has non-string (object) argument,
32+
// it means that such call will reset the dropdown "module" including internal settings,
33+
// then we need to re-delegate the callbacks.
34+
const $dropdown = $(el);
35+
const dropdownModule = $dropdown.data('module-dropdown');
36+
if (!dropdownModule.giteaDelegated) {
37+
dropdownModule.giteaDelegated = true;
38+
delegateDropdownModule($dropdown);
3239
}
3340
}
3441
return ret;
@@ -61,37 +68,17 @@ function updateSelectionLabel(label: HTMLElement) {
6168
}
6269
}
6370

64-
function processMenuItems($dropdown: any, dropdownCall: any) {
65-
const hideEmptyDividers = dropdownCall('setting', 'hideDividers') === 'empty';
71+
function onAfterFiltered(this: any) {
72+
const $dropdown = $(this);
73+
const hideEmptyDividers = $dropdown.dropdown('setting', 'hideDividers') === 'empty';
6674
const itemsMenu = $dropdown[0].querySelector('.scrolling.menu') || $dropdown[0].querySelector('.menu');
6775
if (hideEmptyDividers) hideScopedEmptyDividers(itemsMenu);
6876
}
6977

7078
// delegate the dropdown's template functions and callback functions to add aria attributes.
71-
function delegateOne($dropdown: any) {
79+
function delegateDropdownModule($dropdown: any) {
7280
const dropdownCall = fomanticDropdownFn.bind($dropdown);
7381

74-
// If there is a "search input" in the "menu", Fomantic will only "focus the input" but not "toggle the menu" when the "dropdown icon" is clicked.
75-
// Actually, Fomantic UI doesn't support such layout/usage. It needs to patch the "focusSearch" / "blurSearch" functions to make sure it toggles the menu.
76-
const oldFocusSearch = dropdownCall('internal', 'focusSearch');
77-
const oldBlurSearch = dropdownCall('internal', 'blurSearch');
78-
// * If the "dropdown icon" is clicked, Fomantic calls "focusSearch", so show the menu
79-
dropdownCall('internal', 'focusSearch', function (this: any) { dropdownCall('show'); oldFocusSearch.call(this) });
80-
// * If the "dropdown icon" is clicked again when the menu is visible, Fomantic calls "blurSearch", so hide the menu
81-
dropdownCall('internal', 'blurSearch', function (this: any) { oldBlurSearch.call(this); dropdownCall('hide') });
82-
83-
const oldFilterItems = dropdownCall('internal', 'filterItems');
84-
dropdownCall('internal', 'filterItems', function (this: any, ...args: any[]) {
85-
oldFilterItems.call(this, ...args);
86-
processMenuItems($dropdown, dropdownCall);
87-
});
88-
89-
const oldShow = dropdownCall('internal', 'show');
90-
dropdownCall('internal', 'show', function (this: any, ...args: any[]) {
91-
oldShow.call(this, ...args);
92-
processMenuItems($dropdown, dropdownCall);
93-
});
94-
9582
// the "template" functions are used for dynamic creation (eg: AJAX)
9683
const dropdownTemplates = {...dropdownCall('setting', 'templates'), t: performance.now()};
9784
const dropdownTemplatesMenuOld = dropdownTemplates.menu;
@@ -163,9 +150,8 @@ function attachStaticElements(dropdown: HTMLElement, focusable: HTMLElement, men
163150
}
164151
}
165152

166-
function attachInit(dropdown: HTMLElement) {
153+
function attachInitElements(dropdown: HTMLElement) {
167154
(dropdown as any)[ariaPatchKey] = {};
168-
if (dropdown.classList.contains('custom')) return;
169155

170156
// Dropdown has 2 different focusing behaviors
171157
// * with search input: the input is focused, and it works with aria-activedescendant pointing another sibling element.
@@ -305,9 +291,11 @@ export function hideScopedEmptyDividers(container: Element) {
305291
const visibleItems: Element[] = [];
306292
const curScopeVisibleItems: Element[] = [];
307293
let curScope: string = '', lastVisibleScope: string = '';
308-
const isScopedDivider = (item: Element) => item.matches('.divider') && item.hasAttribute('data-scope');
294+
const isDivider = (item: Element) => item.classList.contains('divider');
295+
const isScopedDivider = (item: Element) => isDivider(item) && item.hasAttribute('data-scope');
309296
const hideDivider = (item: Element) => item.classList.add('hidden', 'transition'); // dropdown has its own classes to hide items
310-
297+
const showDivider = (item: Element) => item.classList.remove('hidden', 'transition');
298+
const isHidden = (item: Element) => item.classList.contains('hidden') || item.classList.contains('filtered') || item.classList.contains('tw-hidden');
311299
const handleScopeSwitch = (itemScope: string) => {
312300
if (curScopeVisibleItems.length === 1 && isScopedDivider(curScopeVisibleItems[0])) {
313301
hideDivider(curScopeVisibleItems[0]);
@@ -323,34 +311,37 @@ export function hideScopedEmptyDividers(container: Element) {
323311
curScopeVisibleItems.length = 0;
324312
};
325313

314+
// reset hidden dividers
315+
queryElems(container, '.divider', showDivider);
316+
326317
// hide the scope dividers if the scope items are empty
327318
for (const item of container.children) {
328319
const itemScope = item.getAttribute('data-scope') || '';
329320
if (itemScope !== curScope) {
330321
handleScopeSwitch(itemScope);
331322
}
332-
if (!item.classList.contains('filtered') && !item.classList.contains('tw-hidden')) {
323+
if (!isHidden(item)) {
333324
curScopeVisibleItems.push(item as HTMLElement);
334325
}
335326
}
336327
handleScopeSwitch('');
337328

338329
// hide all leading and trailing dividers
339330
while (visibleItems.length) {
340-
if (!visibleItems[0].matches('.divider')) break;
331+
if (!isDivider(visibleItems[0])) break;
341332
hideDivider(visibleItems[0]);
342333
visibleItems.shift();
343334
}
344335
while (visibleItems.length) {
345-
if (!visibleItems[visibleItems.length - 1].matches('.divider')) break;
336+
if (!isDivider(visibleItems[visibleItems.length - 1])) break;
346337
hideDivider(visibleItems[visibleItems.length - 1]);
347338
visibleItems.pop();
348339
}
349340
// hide all duplicate dividers, hide current divider if next sibling is still divider
350341
// no need to update "visibleItems" array since this is the last loop
351-
for (const item of visibleItems) {
352-
if (!item.matches('.divider')) continue;
353-
if (item.nextElementSibling?.matches('.divider')) hideDivider(item);
342+
for (let i = 0; i < visibleItems.length - 1; i++) {
343+
if (!visibleItems[i].matches('.divider')) continue;
344+
if (visibleItems[i + 1].matches('.divider')) hideDivider(visibleItems[i]);
354345
}
355346
}
356347

web_src/js/modules/observer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@ function callGlobalInitFunc(el: HTMLElement) {
4646
const func = globalInitFuncs[initFunc];
4747
if (!func) throw new Error(`Global init function "${initFunc}" not found`);
4848

49+
// when an element node is removed and added again, it should not be re-initialized again.
4950
type GiteaGlobalInitElement = Partial<HTMLElement> & {_giteaGlobalInited: boolean};
50-
if ((el as GiteaGlobalInitElement)._giteaGlobalInited) throw new Error(`Global init function "${initFunc}" already executed`);
51+
if ((el as GiteaGlobalInitElement)._giteaGlobalInited) return;
5152
(el as GiteaGlobalInitElement)._giteaGlobalInited = true;
53+
5254
func(el);
5355
}
5456

0 commit comments

Comments
 (0)