added git ssh support and ablity to download repo via zip, tar.gz, and bundle
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { z } from 'zod'
|
||||
import { api } from '../client'
|
||||
|
||||
export interface InstanceConfig {
|
||||
sshHost: string
|
||||
sshPort: string
|
||||
instanceName: string
|
||||
}
|
||||
|
||||
const instanceSchema = z.object({
|
||||
sshHost: z.string(),
|
||||
sshPort: z.string(),
|
||||
instanceName: z.string(),
|
||||
})
|
||||
|
||||
export function useInstance() {
|
||||
return useQuery<InstanceConfig>({
|
||||
queryKey: ['instance'],
|
||||
queryFn: () => api.get<InstanceConfig>('/api/v1/instance', instanceSchema),
|
||||
staleTime: Infinity,
|
||||
})
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { useRepo, useRepoTree, useRepoBlob, useRepoBranches } from '../api/queries/repos'
|
||||
import { useEnvironments } from '../api/queries/environments'
|
||||
import { useInstance } from '../api/queries/instance'
|
||||
import { TreeBrowser } from '../components/repos/TreeBrowser'
|
||||
import { RepoListSkeleton } from '../ui/Skeleton'
|
||||
import { RepoAvatar } from '../ui/RepoAvatar'
|
||||
@@ -14,6 +15,7 @@ export default function RepoPage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const [showBranches, setShowBranches] = useState(false)
|
||||
const [showClone, setShowClone] = useState(false)
|
||||
const [cloneTab, setCloneTab] = useState<'https' | 'ssh'>('https')
|
||||
const branchRef = useRef<HTMLDivElement>(null)
|
||||
const cloneRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
@@ -23,6 +25,7 @@ export default function RepoPage() {
|
||||
const { data: repo, isLoading, isError } = useRepo(owner, repoName)
|
||||
const { data: branches } = useRepoBranches(owner, repoName)
|
||||
const { data: environments } = useEnvironments(owner, repoName)
|
||||
const { data: instance } = useInstance()
|
||||
const { track } = useRecentRepos()
|
||||
useEffect(() => { if (owner && repoName) track(owner, repoName) }, [owner, repoName])
|
||||
|
||||
@@ -42,6 +45,14 @@ export default function RepoPage() {
|
||||
const branch = ref || repo.defaultBranch
|
||||
const cloneUrl = `${window.location.origin}/${owner}/${repoName}.git`
|
||||
|
||||
const sshHost = instance?.sshHost ?? window.location.hostname
|
||||
const sshPort = instance?.sshPort ?? '2222'
|
||||
const sshUrl = sshPort === '22'
|
||||
? `git@${sshHost}:${owner}/${repoName}.git`
|
||||
: `ssh://git@${sshHost}:${sshPort}/${owner}/${repoName}.git`
|
||||
|
||||
const archiveBase = `/api/v1/repos/${owner}/${repoName}/archive?ref=${encodeURIComponent(branch)}`
|
||||
|
||||
function switchBranch(b: string) {
|
||||
setSearchParams({ ref: b, ...(path ? { path } : {}) })
|
||||
setShowBranches(false)
|
||||
@@ -123,17 +134,64 @@ export default function RepoPage() {
|
||||
</svg>
|
||||
</button>
|
||||
{showClone && (
|
||||
<div className="absolute right-0 top-full mt-1 w-80 bg-[var(--c-surface)] border border-[var(--c-border)] rounded-lg shadow-xl z-50 p-4">
|
||||
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-2">Clone over HTTP</p>
|
||||
<div className="flex items-center gap-2 bg-[var(--c-surface-muted)] border border-[var(--c-border)] rounded px-3 py-2">
|
||||
<code className="text-xs text-[var(--c-text)] flex-1 truncate">{cloneUrl}</code>
|
||||
<button
|
||||
onClick={() => navigator.clipboard.writeText(cloneUrl)}
|
||||
className="text-[10px] text-[var(--c-brand)] hover:underline shrink-0"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<div className="absolute right-0 top-full mt-1 w-96 bg-[var(--c-surface)] border border-[var(--c-border)] rounded-lg shadow-xl z-50 p-4 space-y-3">
|
||||
|
||||
{/* Clone URL tabs */}
|
||||
<div>
|
||||
<div className="flex gap-1 mb-2">
|
||||
{(['https', 'ssh'] as const).map(tab => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setCloneTab(tab)}
|
||||
className={`px-2.5 py-1 rounded text-xs font-medium transition-colors ${
|
||||
cloneTab === tab
|
||||
? 'bg-[var(--c-brand)] text-white'
|
||||
: 'text-[var(--c-muted)] hover:text-[var(--c-text)] hover:bg-[var(--c-surface-muted)]'
|
||||
}`}
|
||||
>
|
||||
{tab.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-[var(--c-surface-muted)] border border-[var(--c-border)] rounded px-3 py-2">
|
||||
<code className="text-xs text-[var(--c-text)] flex-1 truncate">
|
||||
{cloneTab === 'https' ? cloneUrl : sshUrl}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => navigator.clipboard.writeText(cloneTab === 'https' ? cloneUrl : sshUrl)}
|
||||
className="text-[10px] text-[var(--c-brand)] hover:underline shrink-0"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
{cloneTab === 'ssh' && (
|
||||
<p className="text-[10px] text-[var(--c-muted)] mt-1.5">
|
||||
Requires an SSH key added to your account settings.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Archive download */}
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-1.5">Download</p>
|
||||
<div className="flex gap-2">
|
||||
{[
|
||||
{ label: 'ZIP', format: 'zip' },
|
||||
{ label: 'tar.gz', format: 'tar.gz' },
|
||||
{ label: 'Bundle', format: 'bundle' },
|
||||
].map(({ label, format }) => (
|
||||
<a
|
||||
key={format}
|
||||
href={`${archiveBase}&format=${format}`}
|
||||
download
|
||||
className="flex-1 text-center px-2 py-1.5 text-xs font-medium border border-[var(--c-border)] rounded hover:bg-[var(--c-surface-muted)] text-[var(--c-text)] transition-colors"
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -141,7 +199,7 @@ export default function RepoPage() {
|
||||
</div>
|
||||
|
||||
{repo.isEmpty ? (
|
||||
<GettingStarted repoName={repoName} branch={branch} cloneUrl={cloneUrl} />
|
||||
<GettingStarted repoName={repoName} branch={branch} cloneUrl={cloneUrl} sshUrl={sshUrl} />
|
||||
) : (
|
||||
<>
|
||||
{/* Branch selector */}
|
||||
@@ -231,8 +289,8 @@ function ReadmePreview({ owner, repo, ref }: { owner: string; repo: string; ref:
|
||||
)
|
||||
}
|
||||
|
||||
function GettingStarted({ repoName, branch, cloneUrl }: {
|
||||
repoName: string; branch: string; cloneUrl: string
|
||||
function GettingStarted({ repoName, branch, cloneUrl, sshUrl }: {
|
||||
repoName: string; branch: string; cloneUrl: string; sshUrl: string
|
||||
}) {
|
||||
return (
|
||||
<div className="border border-[var(--c-border)] rounded-lg overflow-hidden">
|
||||
@@ -241,9 +299,15 @@ function GettingStarted({ repoName, branch, cloneUrl }: {
|
||||
<p className="text-xs text-[var(--c-muted)] mt-0.5">Push your first commit to get started.</p>
|
||||
</div>
|
||||
<div className="px-5 py-5 space-y-6 text-sm">
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-2">Clone over HTTP</p>
|
||||
<CopyBlock value={cloneUrl} />
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-2">Clone over HTTPS</p>
|
||||
<CopyBlock value={cloneUrl} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-2">Clone over SSH</p>
|
||||
<CopyBlock value={sshUrl} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-[var(--c-muted)] uppercase tracking-wide mb-2">…or push an existing repository</p>
|
||||
|
||||
Reference in New Issue
Block a user