mirror of
https://github.com/go-gitea/gitea
synced 2024-12-22 16:44:26 +00:00
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>
This commit is contained in:
parent
8a53a39c42
commit
18061af490
@ -74,9 +74,9 @@ func prepareOpenWithEditorApps(ctx *context.Context) {
|
|||||||
schema, _, _ := strings.Cut(app.OpenURL, ":")
|
schema, _, _ := strings.Cut(app.OpenURL, ":")
|
||||||
var iconHTML template.HTML
|
var iconHTML template.HTML
|
||||||
if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
|
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 {
|
} 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{
|
tmplApps = append(tmplApps, map[string]any{
|
||||||
"DisplayName": app.DisplayName,
|
"DisplayName": app.DisplayName,
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
<!-- there is always at least one button (by context/repo.go) -->
|
<!-- there is always at least one button (guaranteed by context/repo.go) -->
|
||||||
|
<div class="ui action small input clone-buttons-combo">
|
||||||
{{if $.CloneButtonShowHTTPS}}
|
{{if $.CloneButtonShowHTTPS}}
|
||||||
<button class="ui small button" id="repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">
|
<button class="ui small button repo-clone-https" data-link="{{$.CloneButtonOriginLink.HTTPS}}">HTTPS</button>
|
||||||
HTTPS
|
|
||||||
</button>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if $.CloneButtonShowSSH}}
|
{{if $.CloneButtonShowSSH}}
|
||||||
<button class="ui small button" id="repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">
|
<button class="ui small button repo-clone-ssh" data-link="{{$.CloneButtonOriginLink.SSH}}">SSH</button>
|
||||||
SSH
|
|
||||||
</button>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
<input id="repo-clone-url" size="10" class="js-clone-url" value="{{$.CloneButtonOriginLink.HTTPS}}" readonly>
|
<input size="10" class="repo-clone-url 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"}}">
|
<button class="ui small icon button" data-clipboard-target=".repo-clone-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
|
||||||
{{svg "octicon-copy" 14}}
|
{{svg "octicon-copy" 14}}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
44
templates/repo/clone_panel.tmpl
Normal file
44
templates/repo/clone_panel.tmpl
Normal file
@ -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>
|
@ -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>
|
|
@ -37,11 +37,9 @@
|
|||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="clone-panel ui action small input tw-flex-1">
|
|
||||||
{{template "repo/clone_buttons" .}}
|
{{template "repo/clone_buttons" .}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{{if not .Repository.IsArchived}}
|
{{if not .Repository.IsArchived}}
|
||||||
<div class="divider tw-my-0"></div>
|
<div class="divider tw-my-0"></div>
|
||||||
@ -73,7 +71,6 @@ git push -u origin {{.Repository.DefaultBranch}}</code></pre>
|
|||||||
{{ctx.Locale.Tr "repo.empty_message"}}
|
{{ctx.Locale.Tr "repo.empty_message"}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{template "repo/clone_script" .}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,23 +106,7 @@
|
|||||||
<div class="repo-button-row-right {{if not $isTreePathRoot}}tw-flex-grow-0{{end}}">
|
<div class="repo-button-row-right {{if not $isTreePathRoot}}tw-flex-grow-0{{end}}">
|
||||||
<!-- Only show clone panel in repository home page -->
|
<!-- Only show clone panel in repository home page -->
|
||||||
{{if $isTreePathRoot}}
|
{{if $isTreePathRoot}}
|
||||||
<div class="clone-panel ui action tiny input">
|
{{template "repo/clone_panel" .}}
|
||||||
{{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>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
|
{{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}}">
|
<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
|
||||||
|
@ -15,10 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui eight wide column text right">
|
<div class="ui eight wide column text right">
|
||||||
<div class="clone-panel ui action small input">
|
{{template "repo/clone_panel" .}}
|
||||||
{{template "repo/clone_buttons" .}}
|
|
||||||
{{template "repo/clone_script" .}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="ui top header">{{ctx.Locale.Tr "repo.wiki.wiki_page_revisions"}}</h2>
|
<h2 class="ui top header">{{ctx.Locale.Tr "repo.wiki.wiki_page_revisions"}}</h2>
|
||||||
|
@ -28,10 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="clone-panel ui action small input">
|
{{template "repo/clone_panel" .}}
|
||||||
{{template "repo/clone_buttons" .}}
|
|
||||||
{{template "repo/clone_script" .}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ui dividing header">
|
<div class="ui dividing header">
|
||||||
<div class="flex-text-block tw-flex-wrap tw-justify-end">
|
<div class="flex-text-block tw-flex-wrap tw-justify-end">
|
||||||
|
@ -127,10 +127,10 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) {
|
|||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
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.True(t, exists, "The template has changed")
|
||||||
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
|
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)
|
assert.False(t, exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,10 +143,10 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
|
|||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
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.True(t, exists, "The template has changed")
|
||||||
assert.Equal(t, setting.AppURL+"user2/repo1.git", link)
|
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")
|
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)
|
sshURL := fmt.Sprintf("ssh://%s@%s:%d/user2/repo1.git", setting.SSH.User, setting.SSH.Domain, setting.SSH.Port)
|
||||||
assert.Equal(t, sshURL, link)
|
assert.Equal(t, sshURL, link)
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
@import "./repo/header.css";
|
@import "./repo/header.css";
|
||||||
@import "./repo/home.css";
|
@import "./repo/home.css";
|
||||||
@import "./repo/reactions.css";
|
@import "./repo/reactions.css";
|
||||||
|
@import "./repo/clone.css";
|
||||||
|
|
||||||
@import "./editor/fileeditor.css";
|
@import "./editor/fileeditor.css";
|
||||||
@import "./editor/combomarkdowneditor.css";
|
@import "./editor/combomarkdowneditor.css";
|
||||||
|
@ -101,42 +101,6 @@
|
|||||||
margin-bottom: 12px;
|
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 {
|
.repository .repo-description {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
@ -1615,14 +1579,6 @@ td .commit-summary {
|
|||||||
font-weight: var(--font-weight-normal);
|
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 {
|
.empty-placeholder {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
32
web_src/css/repo/clone.css
Normal file
32
web_src/css/repo/clone.css
Normal file
@ -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;
|
||||||
|
}
|
@ -59,9 +59,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
@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-main.with-sidebar,
|
||||||
.repository.wiki .wiki-content-sidebar {
|
.repository.wiki .wiki-content-sidebar {
|
||||||
float: none;
|
float: none;
|
||||||
|
@ -5,6 +5,8 @@ import {showErrorToast} from '../modules/toast.ts';
|
|||||||
import {sleep} from '../utils.ts';
|
import {sleep} from '../utils.ts';
|
||||||
import RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue';
|
import RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue';
|
||||||
import {createApp} from 'vue';
|
import {createApp} from 'vue';
|
||||||
|
import {toOriginUrl} from '../utils/url.ts';
|
||||||
|
import {createTippy} from '../modules/tippy.ts';
|
||||||
|
|
||||||
async function onDownloadArchive(e) {
|
async function onDownloadArchive(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -41,27 +43,68 @@ export function initRepoActivityTopAuthorsChart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoCloneLink() {
|
function initCloneSchemeUrlSelection(parent: Element) {
|
||||||
const $repoCloneSsh = $('#repo-clone-ssh');
|
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
|
||||||
const $repoCloneHttps = $('#repo-clone-https');
|
|
||||||
const $inputLink = $('#repo-clone-url');
|
|
||||||
|
|
||||||
if ((!$repoCloneSsh.length && !$repoCloneHttps.length) || !$inputLink.length) {
|
const tabSsh = parent.querySelector('.repo-clone-ssh');
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
$repoCloneSsh.on('click', () => {
|
const tab = isSSH ? tabSsh : tabHttps;
|
||||||
localStorage.setItem('repo-clone-protocol', 'ssh');
|
if (!tab) return;
|
||||||
window.updateCloneStates();
|
const link = toOriginUrl(tab.getAttribute('data-link'));
|
||||||
});
|
|
||||||
$repoCloneHttps.on('click', () => {
|
|
||||||
localStorage.setItem('repo-clone-protocol', 'https');
|
|
||||||
window.updateCloneStates();
|
|
||||||
});
|
|
||||||
|
|
||||||
$inputLink.on('focus', () => {
|
for (const el of document.querySelectorAll('.js-clone-url')) {
|
||||||
$inputLink.trigger('select');
|
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');
|
||||||
|
updateClonePanelUi();
|
||||||
});
|
});
|
||||||
|
tabHttps.addEventListener('click', () => {
|
||||||
|
localStorage.setItem('repo-clone-protocol', 'https');
|
||||||
|
updateClonePanelUi();
|
||||||
|
});
|
||||||
|
elCloneUrlInput.addEventListener('focus', () => {
|
||||||
|
elCloneUrlInput.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) {
|
export function initRepoCommonBranchOrTagDropdown(selector: string) {
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
import {initUnicodeEscapeButton} from './repo-unicode-escape.ts';
|
import {initUnicodeEscapeButton} from './repo-unicode-escape.ts';
|
||||||
import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue';
|
import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue';
|
||||||
import {
|
import {
|
||||||
initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown,
|
initRepoCloneButtons, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown,
|
||||||
} from './repo-common.ts';
|
} from './repo-common.ts';
|
||||||
import {initCitationFileCopyContent} from './citation.ts';
|
import {initCitationFileCopyContent} from './citation.ts';
|
||||||
import {initCompLabelEdit} from './comp/LabelEdit.ts';
|
import {initCompLabelEdit} from './comp/LabelEdit.ts';
|
||||||
@ -54,7 +54,7 @@ export function initRepository() {
|
|||||||
initRepoCommonFilterSearchDropdown('.choose.branch .dropdown');
|
initRepoCommonFilterSearchDropdown('.choose.branch .dropdown');
|
||||||
}
|
}
|
||||||
|
|
||||||
initRepoCloneLink();
|
initRepoCloneButtons();
|
||||||
initCitationFileCopyContent();
|
initCitationFileCopyContent();
|
||||||
initRepoSettings();
|
initRepoSettings();
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {pathEscapeSegments, isUrl} from './url.ts';
|
import {pathEscapeSegments, isUrl, toOriginUrl} from './url.ts';
|
||||||
|
|
||||||
test('pathEscapeSegments', () => {
|
test('pathEscapeSegments', () => {
|
||||||
expect(pathEscapeSegments('a/b/c')).toEqual('a/b/c');
|
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('https://example.com/index.html')).toEqual(true);
|
||||||
expect(isUrl('/index.html')).toEqual(false);
|
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);
|
||||||
|
});
|
||||||
|
@ -13,3 +13,19 @@ export function isUrl(url: string): boolean {
|
|||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
@ -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);
|
|
||||||
});
|
|
@ -1,19 +1,4 @@
|
|||||||
// Convert an absolute or relative URL to an absolute URL with the current origin. It only
|
import {toOriginUrl} from '../utils/url.ts';
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.customElements.define('origin-url', class extends HTMLElement {
|
window.customElements.define('origin-url', class extends HTMLElement {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
Loading…
Reference in New Issue
Block a user