diff options
author | Blender Defender <contact.blenderdefender@gmail.com> | 2024-12-11 14:54:30 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-11 21:54:30 +0800 |
commit | 18061af49068c8fcceb316f889d719bff6ba8155 (patch) | |
tree | 1807e097a933379a4bd513b68118815f09770b24 /web_src/js | |
parent | 8a53a39c426e95164e87a3b0857eb420711bfd85 (diff) | |
download | gitea-18061af49068c8fcceb316f889d719bff6ba8155.tar.gz gitea-18061af49068c8fcceb316f889d719bff6ba8155.zip |
Rearrange Clone Panel (#31142)
Rearrange the clone panel to use less horizontal space.
The following changes have been made to achieve this:
- Moved everything into the dropdown menu
- Moved the HTTPS/SSH Switch to a separate line
- Moved the "Clone in VS Code"-Button up and added a divider
- Named the dropdown button "Code", added appropriate icon
---------
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Diffstat (limited to 'web_src/js')
-rw-r--r-- | web_src/js/features/repo-common.ts | 69 | ||||
-rw-r--r-- | web_src/js/features/repo-legacy.ts | 4 | ||||
-rw-r--r-- | web_src/js/utils/url.test.ts | 18 | ||||
-rw-r--r-- | web_src/js/utils/url.ts | 16 | ||||
-rw-r--r-- | web_src/js/webcomponents/origin-url.test.ts | 17 | ||||
-rw-r--r-- | web_src/js/webcomponents/origin-url.ts | 17 |
6 files changed, 92 insertions, 49 deletions
diff --git a/web_src/js/features/repo-common.ts b/web_src/js/features/repo-common.ts index 5185a7ca43..336deb125f 100644 --- a/web_src/js/features/repo-common.ts +++ b/web_src/js/features/repo-common.ts @@ -5,6 +5,8 @@ import {showErrorToast} from '../modules/toast.ts'; import {sleep} from '../utils.ts'; import RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue'; import {createApp} from 'vue'; +import {toOriginUrl} from '../utils/url.ts'; +import {createTippy} from '../modules/tippy.ts'; async function onDownloadArchive(e) { e.preventDefault(); @@ -41,27 +43,68 @@ export function initRepoActivityTopAuthorsChart() { } } -export function initRepoCloneLink() { - const $repoCloneSsh = $('#repo-clone-ssh'); - const $repoCloneHttps = $('#repo-clone-https'); - const $inputLink = $('#repo-clone-url'); +function initCloneSchemeUrlSelection(parent: Element) { + const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url'); - if ((!$repoCloneSsh.length && !$repoCloneHttps.length) || !$inputLink.length) { - return; - } + const tabSsh = parent.querySelector('.repo-clone-ssh'); + const tabHttps = parent.querySelector('.repo-clone-https'); + const updateClonePanelUi = function() { + const scheme = localStorage.getItem('repo-clone-protocol') || 'https'; + const isSSH = scheme === 'ssh' && Boolean(tabSsh) || scheme !== 'ssh' && !tabHttps; + if (tabHttps) { + tabHttps.textContent = window.origin.split(':')[0].toUpperCase(); // show "HTTP" or "HTTPS" + tabHttps.classList.toggle('active', !isSSH); + } + if (tabSsh) { + tabSsh.classList.toggle('active', isSSH); + } + + const tab = isSSH ? tabSsh : tabHttps; + if (!tab) return; + const link = toOriginUrl(tab.getAttribute('data-link')); - $repoCloneSsh.on('click', () => { + for (const el of document.querySelectorAll('.js-clone-url')) { + if (el.nodeName === 'INPUT') { + (el as HTMLInputElement).value = link; + } else { + el.textContent = link; + } + } + for (const el of parent.querySelectorAll<HTMLAnchorElement>('.js-clone-url-editor')) { + el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link)); + } + }; + + updateClonePanelUi(); + + tabSsh.addEventListener('click', () => { localStorage.setItem('repo-clone-protocol', 'ssh'); - window.updateCloneStates(); + updateClonePanelUi(); }); - $repoCloneHttps.on('click', () => { + tabHttps.addEventListener('click', () => { localStorage.setItem('repo-clone-protocol', 'https'); - window.updateCloneStates(); + updateClonePanelUi(); + }); + elCloneUrlInput.addEventListener('focus', () => { + elCloneUrlInput.select(); }); +} - $inputLink.on('focus', () => { - $inputLink.trigger('select'); +function initClonePanelButton(btn: HTMLButtonElement) { + const elPanel = btn.nextElementSibling; + createTippy(btn, { + content: elPanel, + trigger: 'click', + placement: 'bottom-end', + interactive: true, + hideOnClick: true, }); + initCloneSchemeUrlSelection(elPanel); +} + +export function initRepoCloneButtons() { + queryElems(document, '.js-btn-clone-panel', initClonePanelButton); + queryElems(document, '.clone-buttons-combo', initCloneSchemeUrlSelection); } export function initRepoCommonBranchOrTagDropdown(selector: string) { diff --git a/web_src/js/features/repo-legacy.ts b/web_src/js/features/repo-legacy.ts index dfea66c7ad..2f760f1d15 100644 --- a/web_src/js/features/repo-legacy.ts +++ b/web_src/js/features/repo-legacy.ts @@ -9,7 +9,7 @@ import { import {initUnicodeEscapeButton} from './repo-unicode-escape.ts'; import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue'; import { - initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown, + initRepoCloneButtons, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown, } from './repo-common.ts'; import {initCitationFileCopyContent} from './citation.ts'; import {initCompLabelEdit} from './comp/LabelEdit.ts'; @@ -54,7 +54,7 @@ export function initRepository() { initRepoCommonFilterSearchDropdown('.choose.branch .dropdown'); } - initRepoCloneLink(); + initRepoCloneButtons(); initCitationFileCopyContent(); initRepoSettings(); diff --git a/web_src/js/utils/url.test.ts b/web_src/js/utils/url.test.ts index 25fda79b19..bb331a6b49 100644 --- a/web_src/js/utils/url.test.ts +++ b/web_src/js/utils/url.test.ts @@ -1,4 +1,4 @@ -import {pathEscapeSegments, isUrl} from './url.ts'; +import {pathEscapeSegments, isUrl, toOriginUrl} from './url.ts'; test('pathEscapeSegments', () => { expect(pathEscapeSegments('a/b/c')).toEqual('a/b/c'); @@ -11,3 +11,19 @@ test('isUrl', () => { expect(isUrl('https://example.com/index.html')).toEqual(true); expect(isUrl('/index.html')).toEqual(false); }); + +test('toOriginUrl', () => { + const oldLocation = String(window.location); + for (const origin of ['https://example.com', 'https://example.com:3000']) { + window.location.assign(`${origin}/`); + expect(toOriginUrl('/')).toEqual(`${origin}/`); + expect(toOriginUrl('/org/repo.git')).toEqual(`${origin}/org/repo.git`); + expect(toOriginUrl('https://another.com')).toEqual(`${origin}/`); + expect(toOriginUrl('https://another.com/')).toEqual(`${origin}/`); + expect(toOriginUrl('https://another.com/org/repo.git')).toEqual(`${origin}/org/repo.git`); + expect(toOriginUrl('https://another.com:4000')).toEqual(`${origin}/`); + expect(toOriginUrl('https://another.com:4000/')).toEqual(`${origin}/`); + expect(toOriginUrl('https://another.com:4000/org/repo.git')).toEqual(`${origin}/org/repo.git`); + } + window.location.assign(oldLocation); +}); diff --git a/web_src/js/utils/url.ts b/web_src/js/utils/url.ts index c5a28774a9..a7d61c5e83 100644 --- a/web_src/js/utils/url.ts +++ b/web_src/js/utils/url.ts @@ -13,3 +13,19 @@ export function isUrl(url: string): boolean { return false; } } + +// Convert an absolute or relative URL to an absolute URL with the current origin. It only +// processes absolute HTTP/HTTPS URLs or relative URLs like '/xxx' or '//host/xxx'. +export function toOriginUrl(urlStr: string) { + try { + if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) { + const {origin, protocol, hostname, port} = window.location; + const url = new URL(urlStr, origin); + url.protocol = protocol; + url.hostname = hostname; + url.port = port || (protocol === 'https:' ? '443' : '80'); + return url.toString(); + } + } catch {} + return urlStr; +} diff --git a/web_src/js/webcomponents/origin-url.test.ts b/web_src/js/webcomponents/origin-url.test.ts deleted file mode 100644 index 19cc467d7d..0000000000 --- a/web_src/js/webcomponents/origin-url.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {toOriginUrl} from './origin-url.ts'; - -test('toOriginUrl', () => { - const oldLocation = String(window.location); - for (const origin of ['https://example.com', 'https://example.com:3000']) { - window.location.assign(`${origin}/`); - expect(toOriginUrl('/')).toEqual(`${origin}/`); - expect(toOriginUrl('/org/repo.git')).toEqual(`${origin}/org/repo.git`); - expect(toOriginUrl('https://another.com')).toEqual(`${origin}/`); - expect(toOriginUrl('https://another.com/')).toEqual(`${origin}/`); - expect(toOriginUrl('https://another.com/org/repo.git')).toEqual(`${origin}/org/repo.git`); - expect(toOriginUrl('https://another.com:4000')).toEqual(`${origin}/`); - expect(toOriginUrl('https://another.com:4000/')).toEqual(`${origin}/`); - expect(toOriginUrl('https://another.com:4000/org/repo.git')).toEqual(`${origin}/org/repo.git`); - } - window.location.assign(oldLocation); -}); diff --git a/web_src/js/webcomponents/origin-url.ts b/web_src/js/webcomponents/origin-url.ts index d407fe0dff..dbb910ce6c 100644 --- a/web_src/js/webcomponents/origin-url.ts +++ b/web_src/js/webcomponents/origin-url.ts @@ -1,19 +1,4 @@ -// Convert an absolute or relative URL to an absolute URL with the current origin. It only -// processes absolute HTTP/HTTPS URLs or relative URLs like '/xxx' or '//host/xxx'. -// NOTE: Keep this function in sync with clone_script.tmpl -export function toOriginUrl(urlStr: string) { - try { - if (urlStr.startsWith('http://') || urlStr.startsWith('https://') || urlStr.startsWith('/')) { - const {origin, protocol, hostname, port} = window.location; - const url = new URL(urlStr, origin); - url.protocol = protocol; - url.hostname = hostname; - url.port = port || (protocol === 'https:' ? '443' : '80'); - return url.toString(); - } - } catch {} - return urlStr; -} +import {toOriginUrl} from '../utils/url.ts'; window.customElements.define('origin-url', class extends HTMLElement { connectedCallback() { |