aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--routers/web/repo/view_home.go4
-rw-r--r--templates/repo/clone_buttons.tmpl26
-rw-r--r--templates/repo/clone_panel.tmpl44
-rw-r--r--templates/repo/clone_script.tmpl50
-rw-r--r--templates/repo/empty.tmpl5
-rw-r--r--templates/repo/home.tmpl18
-rw-r--r--templates/repo/wiki/revision.tmpl5
-rw-r--r--templates/repo/wiki/view.tmpl5
-rw-r--r--tests/integration/repo_test.go8
-rw-r--r--web_src/css/index.css1
-rw-r--r--web_src/css/repo.css44
-rw-r--r--web_src/css/repo/clone.css32
-rw-r--r--web_src/css/repo/wiki.css3
-rw-r--r--web_src/js/features/repo-common.ts69
-rw-r--r--web_src/js/features/repo-legacy.ts4
-rw-r--r--web_src/js/utils/url.test.ts18
-rw-r--r--web_src/js/utils/url.ts16
-rw-r--r--web_src/js/webcomponents/origin-url.test.ts17
-rw-r--r--web_src/js/webcomponents/origin-url.ts17
19 files changed, 191 insertions, 195 deletions
diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go
index e0539f53b0..b318c4a621 100644
--- a/routers/web/repo/view_home.go
+++ b/routers/web/repo/view_home.go
@@ -74,9 +74,9 @@ func prepareOpenWithEditorApps(ctx *context.Context) {
schema, _, _ := strings.Cut(app.OpenURL, ":")
var iconHTML template.HTML
if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
- iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16, "tw-mr-2")
+ iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16)
} else {
- iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future
+ iconHTML = svg.RenderHTML("gitea-git", 16) // TODO: it could support user's customized icon in the future
}
tmplApps = append(tmplApps, map[string]any{
"DisplayName": app.DisplayName,
diff --git a/templates/repo/clone_buttons.tmpl b/templates/repo/clone_buttons.tmpl
index 91952c8a06..03b7a561da 100644
--- a/templates/repo/clone_buttons.tmpl
+++ b/templates/repo/clone_buttons.tmpl
@@ -1,15 +1,13 @@
-<!-- there is always at least one button (by context/repo.go) -->
-{{if $.CloneButtonShowHTTPS}}
- <button class="ui small button" id="repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">
- HTTPS
+<!-- there is always at least one button (guaranteed by context/repo.go) -->
+<div class="ui action small input clone-buttons-combo">
+ {{if $.CloneButtonShowHTTPS}}
+ <button class="ui small button repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">HTTPS</button>
+ {{end}}
+ {{if $.CloneButtonShowSSH}}
+ <button class="ui small button repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
+ {{end}}
+ <input size="10" class="repo-clone-url js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
+ <button class="ui small icon button" data-clipboard-target=".repo-clone-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
+ {{svg "octicon-copy" 14}}
</button>
-{{end}}
-{{if $.CloneButtonShowSSH}}
- <button class="ui small button" id="repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">
- SSH
- </button>
-{{end}}
-<input id="repo-clone-url" size="10" class="js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
-<button class="ui small icon button" id="clipboard-btn" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}" data-clipboard-target="#repo-clone-url" aria-label="{{ctx.Locale.Tr "copy_url"}}">
- {{svg "octicon-copy" 14}}
-</button>
+</div>
diff --git a/templates/repo/clone_panel.tmpl b/templates/repo/clone_panel.tmpl
new file mode 100644
index 0000000000..8cbeda132d
--- /dev/null
+++ b/templates/repo/clone_panel.tmpl
@@ -0,0 +1,44 @@
+<button class="ui green button js-btn-clone-panel">
+ <span>{{svg "octicon-code" 16}} Code</span>
+ {{svg "octicon-triangle-down" 14 "dropdown icon"}}
+</button>
+<div class="clone-panel-popup tippy-target">
+ <div class="flex-text-block clone-panel-field">{{svg "octicon-terminal"}} Clone</div>
+
+ <div class="clone-panel-tab">
+ <!-- there is always at least one button (guaranteed by context/repo.go) -->
+ {{if $.CloneButtonShowHTTPS}}
+ <button class="item repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">HTTPS</button>
+ {{end}}
+ {{if $.CloneButtonShowSSH}}
+ <button class="item repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
+ {{end}}
+ </div>
+ <div class="divider"></div>
+
+ <div class="clone-panel-field">
+ <div class="ui input tiny action">
+ <input size="30" class="repo-clone-url js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
+ <div class="ui small compact icon button" data-clipboard-target=".js-clone-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
+ {{svg "octicon-copy" 14}}
+ </div>
+ </div>
+ </div>
+
+ {{if not .PageIsWiki}}
+ <div class="flex-items-block clone-panel-list">
+ {{range .OpenWithEditorApps}}
+ <a class="item muted js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
+ {{end}}
+ </div>
+
+ {{if and (not $.DisableDownloadSourceArchives) $.RefName}}
+ <div class="divider"></div>
+ <div class="flex-items-block clone-panel-list">
+ <a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_zip"}}</a>
+ <a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}} {{ctx.Locale.Tr "repo.download_tar"}}</a>
+ <a class="item muted archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package"}} {{ctx.Locale.Tr "repo.download_bundle"}}</a>
+ </div>
+ {{end}}
+ {{end}}
+</div>
diff --git a/templates/repo/clone_script.tmpl b/templates/repo/clone_script.tmpl
deleted file mode 100644
index 40dae76dc7..0000000000
--- a/templates/repo/clone_script.tmpl
+++ /dev/null
@@ -1,50 +0,0 @@
-<script>
- // synchronously set clone button states and urls here to avoid flickering
- // on page load. initRepoCloneLink calls this when proto changes.
- // this applies the protocol-dependant clone url to all elements with the
- // `js-clone-url` and `js-clone-url-vsc` classes.
- // TODO: This localStorage setting should be moved to backend user config
- // so it's available during rendering, then this inline script can be removed.
- (window.updateCloneStates = function() {
- const httpsBtn = document.getElementById('repo-clone-https');
- const sshBtn = document.getElementById('repo-clone-ssh');
- const value = localStorage.getItem('repo-clone-protocol') || 'https';
- const isSSH = value === 'ssh' && sshBtn || value !== 'ssh' && !httpsBtn;
-
- if (httpsBtn) {
- httpsBtn.textContent = window.origin.split(':')[0].toUpperCase();
- httpsBtn.classList.toggle('primary', !isSSH);
- httpsBtn.classList.toggle('basic', isSSH);
- }
- if (sshBtn) {
- sshBtn.classList.toggle('primary', isSSH);
- sshBtn.classList.toggle('basic', !isSSH);
- }
-
- const btn = isSSH ? sshBtn : httpsBtn;
- if (!btn) return;
-
- // NOTE: Keep this function in sync with the one in the js folder
- function toOriginUrl(urlStr) {
- 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;
- }
- const link = toOriginUrl(btn.getAttribute('data-link'));
-
- for (const el of document.getElementsByClassName('js-clone-url')) {
- el[el.nodeName === 'INPUT' ? 'value' : 'textContent'] = link;
- }
- for (const el of document.getElementsByClassName('js-clone-url-editor')) {
- el.href = el.getAttribute('data-href-template').replace('{url}', encodeURIComponent(link));
- }
- })();
-</script>
diff --git a/templates/repo/empty.tmpl b/templates/repo/empty.tmpl
index d3a81bc51d..7170fe3602 100644
--- a/templates/repo/empty.tmpl
+++ b/templates/repo/empty.tmpl
@@ -37,9 +37,7 @@
</a>
{{end}}
{{end}}
- <div class="clone-panel ui action small input tw-flex-1">
- {{template "repo/clone_buttons" .}}
- </div>
+ {{template "repo/clone_buttons" .}}
</div>
</div>
@@ -73,7 +71,6 @@ git push -u origin {{.Repository.DefaultBranch}}</code></pre>
{{ctx.Locale.Tr "repo.empty_message"}}
</div>
{{end}}
- {{template "repo/clone_script" .}}
</div>
</div>
</div>
diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl
index 46d0398c21..cc36fa4eea 100644
--- a/templates/repo/home.tmpl
+++ b/templates/repo/home.tmpl
@@ -106,23 +106,7 @@
<div class="repo-button-row-right {{if not $isTreePathRoot}}tw-flex-grow-0{{end}}">
<!-- Only show clone panel in repository home page -->
{{if $isTreePathRoot}}
- <div class="clone-panel ui action tiny input">
- {{template "repo/clone_buttons" .}}
- <button class="ui small jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
- {{svg "octicon-kebab-horizontal"}}
- <div class="menu">
- {{if not $.DisableDownloadSourceArchives}}
- <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
- <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
- <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_bundle"}}</a>
- {{end}}
- {{range .OpenWithEditorApps}}
- <a class="item js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
- {{end}}
- </div>
- </button>
- {{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}}
- </div>
+ {{template "repo/clone_panel" .}}
{{end}}
{{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
diff --git a/templates/repo/wiki/revision.tmpl b/templates/repo/wiki/revision.tmpl
index 045cc41d81..ca8954928d 100644
--- a/templates/repo/wiki/revision.tmpl
+++ b/templates/repo/wiki/revision.tmpl
@@ -15,10 +15,7 @@
</div>
</div>
<div class="ui eight wide column text right">
- <div class="clone-panel ui action small input">
- {{template "repo/clone_buttons" .}}
- {{template "repo/clone_script" .}}
- </div>
+ {{template "repo/clone_panel" .}}
</div>
</div>
<h2 class="ui top header">{{ctx.Locale.Tr "repo.wiki.wiki_page_revisions"}}</h2>
diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl
index c8e0b4254c..68933b0bcf 100644
--- a/templates/repo/wiki/view.tmpl
+++ b/templates/repo/wiki/view.tmpl
@@ -28,10 +28,7 @@
</div>
</div>
</div>
- <div class="clone-panel ui action small input">
- {{template "repo/clone_buttons" .}}
- {{template "repo/clone_script" .}}
- </div>
+ {{template "repo/clone_panel" .}}
</div>
<div class="ui dividing header">
<div class="flex-text-block tw-flex-wrap tw-justify-end">
diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go
index b967ccad1e..1b9f6887fd 100644
--- a/tests/integration/repo_test.go
+++ b/tests/integration/repo_test.go
@@ -127,10 +127,10 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) {
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
- link, exists := htmlDoc.doc.Find("#repo-clone-https").Attr("data-link")
+ link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
assert.True(t, exists, "The template has changed")
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
- _, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link")
+ _, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
assert.False(t, exists)
}
@@ -143,10 +143,10 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
- link, exists := htmlDoc.doc.Find("#repo-clone-https").Attr("data-link")
+ link, exists := htmlDoc.doc.Find(".repo-clone-https").Attr("data-link")
assert.True(t, exists, "The template has changed")
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
- link, exists = htmlDoc.doc.Find("#repo-clone-ssh").Attr("data-link")
+ link, exists = htmlDoc.doc.Find(".repo-clone-ssh").Attr("data-link")
assert.True(t, exists, "The template has changed")
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port)
assert.Equal(t, sshURL, link)
diff --git a/web_src/css/index.css b/web_src/css/index.css
index 158ae42d3e..43648268c5 100644
--- a/web_src/css/index.css
+++ b/web_src/css/index.css
@@ -67,6 +67,7 @@
@import "./repo/header.css";
@import "./repo/home.css";
@import "./repo/reactions.css";
+@import "./repo/clone.css";
@import "./editor/fileeditor.css";
@import "./editor/combomarkdowneditor.css";
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index f5785c41a7..cf637e1c48 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -101,42 +101,6 @@
margin-bottom: 12px;
}
-.repository .clone-panel {
- display: flex;
- flex: 1;
-}
-
-.repository.wiki .clone-panel {
- flex: 0;
-}
-
-.repository.wiki .clone-panel input {
- width: 20ch;
-}
-
-.repository .clone-panel #repo-clone-url {
- border-radius: 0;
- flex: 1;
-}
-
-.repository .ui.action.input.clone-panel > button + button,
-.repository .ui.action.input.clone-panel > button + input {
- margin-left: -1px; /* make the borders overlap to avoid double borders */
-}
-
-.repository .clone-panel > button:first-of-type {
- border-radius: var(--border-radius) 0 0 var(--border-radius) !important;
-}
-
-.repository .clone-panel > button:last-of-type {
- border-radius: 0 var(--border-radius) var(--border-radius) 0 !important;
-}
-
-.repository .clone-panel .dropdown .menu {
- right: 0 !important;
- left: auto !important;
-}
-
.repository .repo-description {
font-size: 16px;
margin-bottom: 5px;
@@ -1615,14 +1579,6 @@ td .commit-summary {
font-weight: var(--font-weight-normal);
}
-.repository.quickstart .guide #repo-clone-url {
- border-radius: 0;
- padding: 5px 10px;
- font-size: 1.2em;
- line-height: 1.4;
- flex: 1
-}
-
.empty-placeholder {
display: flex;
flex-direction: column;
diff --git a/web_src/css/repo/clone.css b/web_src/css/repo/clone.css
new file mode 100644
index 0000000000..15709a78f6
--- /dev/null
+++ b/web_src/css/repo/clone.css
@@ -0,0 +1,32 @@
+/* only used by "repo/empty.tmpl" */
+.clone-buttons-combo {
+ flex: 1;
+}
+
+.clone-buttons-combo input {
+ border-left: none !important;
+ border-radius: 0 !important;
+}
+
+/* used by the clone-panel popup */
+.clone-panel-field,
+.clone-panel-list {
+ margin: 10px;
+}
+
+.clone-panel-tab .item {
+ padding: 5px 10px;
+ background: none;
+}
+
+.clone-panel-tab .item.active {
+ border-bottom: 3px solid var(--color-secondary);
+}
+
+.clone-panel-tab + .divider {
+ margin: -1px 0 0;
+}
+
+.clone-panel-list .item {
+ margin: 5px 0;
+}
diff --git a/web_src/css/repo/wiki.css b/web_src/css/repo/wiki.css
index ba502d3216..ca59dadb9c 100644
--- a/web_src/css/repo/wiki.css
+++ b/web_src/css/repo/wiki.css
@@ -59,9 +59,6 @@
}
@media (max-width: 767.98px) {
- .repository.wiki .clone-panel #repo-clone-url {
- width: 160px;
- }
.repository.wiki .wiki-content-main.with-sidebar,
.repository.wiki .wiki-content-sidebar {
float: none;
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() {