diff --git a/web_src/js/features/repo-new.ts b/web_src/js/features/repo-new.ts index 101545735f..8a77a77b4a 100644 --- a/web_src/js/features/repo-new.ts +++ b/web_src/js/features/repo-new.ts @@ -1,10 +1,53 @@ -import {hideElem, showElem} from '../utils/dom.ts'; +import {hideElem, showElem, toggleElem} from '../utils/dom.ts'; +import {htmlEscape} from 'escape-goat'; +import {fomanticQuery} from '../modules/fomantic/base.ts'; + +const {appSubUrl} = window.config; + +function initRepoNewTemplateSearch(form: HTMLFormElement) { + const inputRepoOwnerUid = form.querySelector('#uid'); + const elRepoTemplateDropdown = form.querySelector('#repo_template_search'); + const inputRepoTemplate = form.querySelector('#repo_template'); + const elTemplateUnits = form.querySelector('#template_units'); + const elNonTemplate = form.querySelector('#non_template'); + const checkTemplate = function () { + const hasSelectedTemplate = inputRepoTemplate.value !== '' && inputRepoTemplate.value !== '0'; + toggleElem(elTemplateUnits, hasSelectedTemplate); + toggleElem(elNonTemplate, !hasSelectedTemplate); + }; + inputRepoTemplate.addEventListener('change', checkTemplate); + checkTemplate(); + + const $dropdown = fomanticQuery(elRepoTemplateDropdown); + const onChangeOwner = function () { + $dropdown.dropdown('setting', { + apiSettings: { + url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${inputRepoOwnerUid.value}`, + onResponse(response) { + const results = []; + results.push({name: '', value: ''}); // empty item means not using template + for (const tmplRepo of response.data) { + results.push({ + name: htmlEscape(tmplRepo.repository.full_name), + value: String(tmplRepo.repository.id), + }); + } + $dropdown.fomanticExt.onResponseKeepSelectedItem($dropdown, inputRepoTemplate.value); + return {results}; + }, + cache: false, + }, + }); + }; + inputRepoOwnerUid.addEventListener('change', onChangeOwner); + onChangeOwner(); +} export function initRepoNew() { const pageContent = document.querySelector('.page-content.repository.new-repo'); if (!pageContent) return; - const form = document.querySelector('.new-repo-form'); + const form = document.querySelector('.new-repo-form'); const inputGitIgnores = form.querySelector('input[name="gitignores"]'); const inputLicense = form.querySelector('input[name="license"]'); const inputAutoInit = form.querySelector('input[name="auto_init"]'); @@ -32,4 +75,6 @@ export function initRepoNew() { }; inputRepoName.addEventListener('input', updateUiRepoName); updateUiRepoName(); + + initRepoNewTemplateSearch(form); } diff --git a/web_src/js/features/repo-template.ts b/web_src/js/features/repo-template.ts deleted file mode 100644 index fbd7b656ed..0000000000 --- a/web_src/js/features/repo-template.ts +++ /dev/null @@ -1,51 +0,0 @@ -import $ from 'jquery'; -import {htmlEscape} from 'escape-goat'; -import {hideElem, showElem} from '../utils/dom.ts'; - -const {appSubUrl} = window.config; - -export function initRepoTemplateSearch() { - const $repoTemplate = $('#repo_template'); - const checkTemplate = function () { - const $templateUnits = $('#template_units'); - const $nonTemplate = $('#non_template'); - if ($repoTemplate.val() !== '' && $repoTemplate.val() !== '0') { - showElem($templateUnits); - hideElem($nonTemplate); - } else { - hideElem($templateUnits); - showElem($nonTemplate); - } - }; - $repoTemplate.on('change', checkTemplate); - checkTemplate(); - - const changeOwner = function () { - $('#repo_template_search') - .dropdown({ - apiSettings: { - url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`, - onResponse(response) { - const filteredResponse = {success: true, results: []}; - filteredResponse.results.push({ - name: '', - value: '', - }); - // Parse the response from the api to work with our dropdown - $.each(response.data, (_r, repo) => { - filteredResponse.results.push({ - name: htmlEscape(repo.repository.full_name), - value: repo.repository.id, - }); - }); - return filteredResponse; - }, - cache: false, - }, - - fullTextSearch: true, - }); - }; - $('#uid').on('change', changeOwner); - changeOwner(); -} diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts index c08ff9976b..0c540ac296 100644 --- a/web_src/js/globals.d.ts +++ b/web_src/js/globals.d.ts @@ -36,8 +36,9 @@ declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' { } interface JQuery { - api: any, // fomantic areYouSure: any, // jquery.are-you-sure + fomanticExt: any; // fomantic extension + api: any, // fomantic dimmer: any, // fomantic dropdown: any; // fomantic modal: any; // fomantic diff --git a/web_src/js/index.ts b/web_src/js/index.ts index 51d8c96fbd..4d400d3b8f 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -34,7 +34,6 @@ import { import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.ts'; import {initRepoTopicBar} from './features/repo-home.ts'; import {initAdminCommon} from './features/admin/common.ts'; -import {initRepoTemplateSearch} from './features/repo-template.ts'; import {initRepoCodeView} from './features/repo-code.ts'; import {initSshKeyFormParser} from './features/sshkey-helper.ts'; import {initUserSettings} from './features/user-settings.ts'; @@ -193,7 +192,6 @@ onDomReady(() => { initRepoPullRequestReview, initRepoRelease, initRepoReleaseNew, - initRepoTemplateSearch, initRepoTopicBar, initRepoWikiForm, initRepository, diff --git a/web_src/js/modules/fomantic.ts b/web_src/js/modules/fomantic.ts index af47c8fb51..18a3c18c9c 100644 --- a/web_src/js/modules/fomantic.ts +++ b/web_src/js/modules/fomantic.ts @@ -11,9 +11,10 @@ import {svg} from '../svg.ts'; export const fomanticMobileScreen = window.matchMedia('only screen and (max-width: 767.98px)'); export function initGiteaFomantic() { + // our extensions + $.fn.fomanticExt = {}; // Silence fomantic's error logging when tabs are used without a target content element $.fn.tab.settings.silent = true; - // By default, use "exact match" for full text search $.fn.dropdown.settings.fullTextSearch = 'exact'; // Do not use "cursor: pointer" for dropdown labels diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts index 6d0f12cb43..9bdc9bfc33 100644 --- a/web_src/js/modules/fomantic/dropdown.ts +++ b/web_src/js/modules/fomantic/dropdown.ts @@ -1,6 +1,7 @@ import $ from 'jquery'; import {generateAriaId} from './base.ts'; import type {FomanticInitFunction} from '../../types.ts'; +import {queryElems} from '../../utils/dom.ts'; const ariaPatchKey = '_giteaAriaPatchDropdown'; const fomanticDropdownFn = $.fn.dropdown; @@ -9,6 +10,7 @@ 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; (ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings; } @@ -351,3 +353,19 @@ export function hideScopedEmptyDividers(container: Element) { if (item.nextElementSibling?.matches('.divider')) hideDivider(item); } } + +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[0]; + setTimeout(() => { + queryElems(elDropdown, `.menu .item[data-value="${CSS.escape(selectedValue)}"].filtered`, (el) => el.classList.remove('filtered')); + $(elDropdown).dropdown('set selected', selectedValue ?? ''); + }, 10); +}