mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-22 09:17:28 +02:00
Merge branch 'main' into puni9869/ISSUE-36299
This commit is contained in:
@@ -4,8 +4,10 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"gitea.dev/modules/setting"
|
||||
@@ -14,61 +16,34 @@ import (
|
||||
"xorm.io/xorm/dialects"
|
||||
)
|
||||
|
||||
var registerOnce sync.Once
|
||||
type postgresSchemaDriver struct{}
|
||||
|
||||
func registerPostgresSchemaDriver() {
|
||||
registerOnce.Do(func() {
|
||||
sql.Register(sqlDriverPostgresSchema, &postgresSchemaDriver{})
|
||||
dialects.RegisterDriver(sqlDriverPostgresSchema, dialects.QueryDriver("postgres"))
|
||||
})
|
||||
}
|
||||
var registerPostgresSchemaDriver = sync.OnceFunc(func() {
|
||||
sql.Register(sqlDriverPostgresSchema, &postgresSchemaDriver{})
|
||||
dialects.RegisterDriver(sqlDriverPostgresSchema, dialects.QueryDriver("postgres"))
|
||||
})
|
||||
|
||||
type postgresSchemaDriver struct {
|
||||
pq.Driver
|
||||
}
|
||||
|
||||
// Open opens a new connection to the database. name is a connection string.
|
||||
// This function opens the postgres connection in the default manner but immediately
|
||||
// runs set_config to set the search_path appropriately
|
||||
func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
|
||||
conn, err := d.Driver.Open(name)
|
||||
// Open opens the postgres connection in the default manner with default schema support.
|
||||
// It immediately runs "set_config" to set the search_path appropriately.
|
||||
func (*postgresSchemaDriver) Open(connStr string) (driver.Conn, error) {
|
||||
conn, err := pq.Driver{}.Open(connStr)
|
||||
if err != nil {
|
||||
return conn, err
|
||||
return nil, err
|
||||
}
|
||||
schemaValue, _ := driver.String.ConvertValue(setting.Database.Schema)
|
||||
|
||||
// golangci lint is incorrect here - there is no benefit to using driver.ExecerContext here
|
||||
// and in any case pq does not implement it
|
||||
if execer, ok := conn.(driver.Execer); ok { //nolint:staticcheck // see above
|
||||
_, err := execer.Exec(`SELECT set_config(
|
||||
connExec, ok := conn.(driver.ExecerContext)
|
||||
if !ok {
|
||||
return nil, errors.New("postgres driver does not implement ExecerContext interface")
|
||||
}
|
||||
_, err = connExec.ExecContext(context.Background(), `SELECT set_config(
|
||||
'search_path',
|
||||
$1 || ',' || current_setting('search_path'),
|
||||
false)`, []driver.Value{schemaValue})
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
stmt, err := conn.Prepare(`SELECT set_config(
|
||||
'search_path',
|
||||
$1 || ',' || current_setting('search_path'),
|
||||
false)`)
|
||||
false)`,
|
||||
[]driver.NamedValue{{Ordinal: 1, Value: setting.Database.Schema}},
|
||||
)
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
// driver.String.ConvertValue will never return err for string
|
||||
|
||||
// golangci lint is incorrect here - there is no benefit to using stmt.ExecWithContext here
|
||||
_, err = stmt.Exec([]driver.Value{schemaValue}) //nolint:staticcheck // see above
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
@@ -80,6 +80,10 @@
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
.ui.dropdown .menu .hidden { /* for hidden items and dividers */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ui.dropdown .menu > .header {
|
||||
margin: 1rem 0 0.75rem;
|
||||
padding: 0 1.14285714rem;
|
||||
|
||||
@@ -308,12 +308,12 @@ $.fn.dropdown = function(parameters) {
|
||||
firstUnfiltered: function() {
|
||||
module.verbose('Selecting first non-filtered element');
|
||||
module.remove.selectedItem();
|
||||
$item
|
||||
const $selectable = $item
|
||||
.not(selector.unselectable)
|
||||
.not(selector.addition + selector.hidden)
|
||||
.eq(0)
|
||||
.addClass(className.selected)
|
||||
;
|
||||
.not(selector.addition + selector.hidden);
|
||||
let $selectedItem = $selectable.filter(`[data-value="${CSS.escape($input.val())}"]`); // GITEA-PATCH: try to re-select the last selected item for single selection
|
||||
if (!$selectedItem.length) $selectedItem = $item.eq(0);
|
||||
$selectedItem.addClass(className.selected);
|
||||
},
|
||||
nextAvailable: function($selected) {
|
||||
$selected = $selected.eq(0);
|
||||
@@ -772,11 +772,13 @@ $.fn.dropdown = function(parameters) {
|
||||
if(!Array.isArray(preSelected)) {
|
||||
preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : [];
|
||||
}
|
||||
$.each(preSelected,function(index,value){
|
||||
$item.filter('[data-value="'+CSS.escape(value)+'"]') // GITEA-PATCH: use "CSS.escape" for query selector
|
||||
if (module.is.multiple()) { // GITEA-PATCH: only hide selected items when the dropdown is "multiple selection"
|
||||
$.each(preSelected, function (index, value) {
|
||||
$item.filter('[data-value="' + CSS.escape(value) + '"]') // GITEA-PATCH: use "CSS.escape" for query selector
|
||||
.addClass(className.filtered)
|
||||
;
|
||||
});
|
||||
;
|
||||
});
|
||||
}
|
||||
afterFiltered();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ function initRepoNewTemplateSearch(form: HTMLFormElement) {
|
||||
value: String(tmplRepo.repository.id),
|
||||
});
|
||||
}
|
||||
$repoTemplateDropdown.fomanticExt.onResponseKeepSelectedItem($repoTemplateDropdown, inputRepoTemplate.value);
|
||||
return {results};
|
||||
},
|
||||
cache: false,
|
||||
|
||||
@@ -13,13 +13,13 @@ test('hideScopedEmptyDividers-simple', () => {
|
||||
</div>`);
|
||||
hideScopedEmptyDividers(container);
|
||||
expect(container.innerHTML).toEqual(`
|
||||
<div class="divider hidden transition"></div>
|
||||
<div class="divider hidden"></div>
|
||||
<div class="item">a</div>
|
||||
<div class="divider hidden transition"></div>
|
||||
<div class="divider hidden transition"></div>
|
||||
<div class="divider hidden"></div>
|
||||
<div class="divider hidden"></div>
|
||||
<div class="divider"></div>
|
||||
<div class="item">b</div>
|
||||
<div class="divider hidden transition"></div>
|
||||
<div class="divider hidden"></div>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ test('hideScopedEmptyDividers-items-all-filtered', () => {
|
||||
hideScopedEmptyDividers(container);
|
||||
expect(container.innerHTML).toEqual(`
|
||||
<div class="any"></div>
|
||||
<div class="divider hidden transition"></div>
|
||||
<div class="divider hidden"></div>
|
||||
<div class="item filtered">a</div>
|
||||
<div class="item filtered">b</div>
|
||||
<div class="divider"></div>
|
||||
@@ -52,7 +52,7 @@ test('hideScopedEmptyDividers-hide-last', () => {
|
||||
hideScopedEmptyDividers(container);
|
||||
expect(container.innerHTML).toEqual(`
|
||||
<div class="item">a</div>
|
||||
<div class="divider hidden transition" data-scope="b"></div>
|
||||
<div class="divider hidden" data-scope="b"></div>
|
||||
<div class="item tw-hidden" data-scope="b">b</div>
|
||||
`);
|
||||
});
|
||||
@@ -68,9 +68,9 @@ test('hideScopedEmptyDividers-scoped-items', () => {
|
||||
hideScopedEmptyDividers(container);
|
||||
expect(container.innerHTML).toEqual(`
|
||||
<div class="item" data-scope="">a</div>
|
||||
<div class="divider hidden transition" data-scope="b"></div>
|
||||
<div class="divider hidden" data-scope="b"></div>
|
||||
<div class="item tw-hidden" data-scope="b">b</div>
|
||||
<div class="divider hidden transition" data-scope=""></div>
|
||||
<div class="divider hidden" data-scope=""></div>
|
||||
<div class="item" data-scope="">c</div>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@ const fomanticDropdownFn = $.fn.dropdown;
|
||||
export function initAriaDropdownPatch() {
|
||||
if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once');
|
||||
$.fn.dropdown = ariaDropdownFn;
|
||||
$.fn.fomanticExt.onResponseKeepSelectedItem = onResponseKeepSelectedItem;
|
||||
$.fn.fomanticExt.onDropdownAfterFiltered = onDropdownAfterFiltered;
|
||||
(ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings;
|
||||
}
|
||||
@@ -296,8 +295,8 @@ export function hideScopedEmptyDividers(container: Element) {
|
||||
let curScope: string = '', lastVisibleScope: string = '';
|
||||
const isDivider = (item: Element) => item.classList.contains('divider');
|
||||
const isScopedDivider = (item: Element) => isDivider(item) && item.hasAttribute('data-scope');
|
||||
const hideDivider = (item: Element) => item.classList.add('hidden', 'transition'); // dropdown has its own classes to hide items
|
||||
const showDivider = (item: Element) => item.classList.remove('hidden', 'transition');
|
||||
const hideDivider = (item: Element) => item.classList.add('hidden'); // dropdown has its own classes to hide items
|
||||
const showDivider = (item: Element) => item.classList.remove('hidden');
|
||||
const isHidden = (item: Element) => item.classList.contains('hidden') || item.classList.contains('filtered') || item.classList.contains('tw-hidden');
|
||||
const handleScopeSwitch = (itemScope: string) => {
|
||||
if (curScopeVisibleItems.length === 1 && isScopedDivider(curScopeVisibleItems[0])) {
|
||||
@@ -347,19 +346,3 @@ export function hideScopedEmptyDividers(container: Element) {
|
||||
if (visibleItems[i + 1].matches('.divider')) hideDivider(visibleItems[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function onResponseKeepSelectedItem(dropdown: typeof $ | HTMLElement, selectedValue: string) {
|
||||
// There is a bug in fomantic dropdown when using "apiSettings" to fetch data
|
||||
// * when there is a selected item, the dropdown insists on hiding the selected one from the list:
|
||||
// * in the "filter" function: ('[data-value="'+value+'"]').addClass(className.filtered)
|
||||
//
|
||||
// When user selects one item, and click the dropdown again,
|
||||
// then the dropdown only shows other items and will select another (wrong) one.
|
||||
// It can't be easily fix by using setTimeout(patch, 0) in `onResponse` because the `onResponse` is called before another `setTimeout(..., timeLeft)`
|
||||
// Fortunately, the "timeLeft" is controlled by "loadingDuration" which is always zero at the moment, so we can use `setTimeout(..., 10)`
|
||||
const elDropdown = (dropdown instanceof HTMLElement) ? dropdown : (dropdown as any)[0];
|
||||
setTimeout(() => {
|
||||
queryElems(elDropdown, `.menu .item[data-value="${CSS.escape(selectedValue)}"].filtered`, (el) => el.classList.remove('filtered'));
|
||||
$(elDropdown).dropdown('set selected', selectedValue ?? '');
|
||||
}, 10);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user