mirror of
https://github.com/veeso/termscp.git
synced 2026-07-20 14:54:10 +02:00
ci(site): add format/lint/test/build workflow for astro site
Add Site GitHub Actions workflow running prettier format check, astro check, tests, and build on changes under site/. Wire prettier into the site package with config, ignore, and scripts, and format existing sources.
This commit is contained in:
@@ -5,6 +5,7 @@ interface Props {
|
||||
}
|
||||
const { text, class: className } = Astro.props;
|
||||
---
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class:list={[
|
||||
@@ -15,11 +16,29 @@ const { text, class: className } = Astro.props;
|
||||
aria-label="Copy command to clipboard"
|
||||
title="Copy"
|
||||
>
|
||||
<svg class="copy-icon h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<svg
|
||||
class="copy-icon h-4 w-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
<svg class="check-icon hidden h-4 w-4 text-green" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<svg
|
||||
class="check-icon hidden h-4 w-4 text-green"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M20 6 9 17l-5-5"></path>
|
||||
</svg>
|
||||
</button>
|
||||
@@ -46,7 +65,9 @@ const { text, class: className } = Astro.props;
|
||||
};
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const button = (event.target as HTMLElement)?.closest<HTMLElement>(".copy-btn");
|
||||
const button = (event.target as HTMLElement)?.closest<HTMLElement>(
|
||||
".copy-btn",
|
||||
);
|
||||
if (button) {
|
||||
handleCopy(button);
|
||||
}
|
||||
|
||||
@@ -17,44 +17,98 @@ const remote = [
|
||||
];
|
||||
const status = "↹ switch · ↑↓ move · ↵ open · SPACE transfer";
|
||||
---
|
||||
|
||||
<section class="mx-auto max-w-5xl px-4 pt-12">
|
||||
<p class="text-green text-sm tracking-widest uppercase">// terminal file transfer</p>
|
||||
<div class="mt-4 overflow-hidden rounded-lg border border-line bg-mantle shadow-2xl">
|
||||
<p class="text-green text-sm tracking-widest uppercase">
|
||||
// terminal file transfer
|
||||
</p>
|
||||
<div
|
||||
class="mt-4 overflow-hidden rounded-lg border border-line bg-mantle shadow-2xl"
|
||||
>
|
||||
<div class="border-b border-line bg-crust px-3 py-2 text-xs text-overlay">
|
||||
termscp — 192.168.1.10 (sftp) connected<span class="blink ml-1"></span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 bg-base">
|
||||
<div>
|
||||
<div class="border-b border-line bg-mantle px-3 py-1.5 text-xs text-teal">/home/veeso (local)</div>
|
||||
{local.map((r) => (
|
||||
<div class={`flex justify-between gap-2 px-3 py-0.5 text-sm ${r.kind === "dir" ? "text-blue" : "text-text"}`}>
|
||||
<span class="truncate min-w-0">{r.name}</span><span class="text-overlay shrink-0">{r.size}</span>
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
class="border-b border-line bg-mantle px-3 py-1.5 text-xs text-teal"
|
||||
>
|
||||
/home/veeso (local)
|
||||
</div>
|
||||
{
|
||||
local.map((r) => (
|
||||
<div
|
||||
class={`flex justify-between gap-2 px-3 py-0.5 text-sm ${r.kind === "dir" ? "text-blue" : "text-text"}`}
|
||||
>
|
||||
<>
|
||||
<span class="truncate min-w-0">{r.name}</span>
|
||||
<span class="text-overlay shrink-0">{r.size}</span>
|
||||
</>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div class="border-l border-line">
|
||||
<div class="border-b border-line bg-mantle px-3 py-1.5 text-xs text-mauve">/var/www (remote)</div>
|
||||
{remote.map((r) => (
|
||||
<div class={`flex justify-between gap-2 px-3 py-0.5 text-sm ${
|
||||
r.kind === "sel" ? "bg-blue text-base font-bold" : r.kind === "dir" ? "text-blue" : "text-text"
|
||||
}`}>
|
||||
<span class="truncate min-w-0">{r.name}</span>
|
||||
<span class={`shrink-0 ${r.kind === "sel" ? "text-base" : "text-overlay"}`}>{r.size}</span>
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
class="border-b border-line bg-mantle px-3 py-1.5 text-xs text-mauve"
|
||||
>
|
||||
/var/www (remote)
|
||||
</div>
|
||||
{
|
||||
remote.map((r) => (
|
||||
<div
|
||||
class={`flex justify-between gap-2 px-3 py-0.5 text-sm ${
|
||||
r.kind === "sel"
|
||||
? "bg-blue text-base font-bold"
|
||||
: r.kind === "dir"
|
||||
? "text-blue"
|
||||
: "text-text"
|
||||
}`}
|
||||
>
|
||||
<span class="truncate min-w-0">{r.name}</span>
|
||||
<span
|
||||
class={`shrink-0 ${r.kind === "sel" ? "text-base" : "text-overlay"}`}
|
||||
>
|
||||
{r.size}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{/* set:html is safe: status is a static author-controlled string, never user input. */}
|
||||
<div class="flex justify-between border-t border-line bg-crust px-3 py-1.5 text-xs text-overlay">
|
||||
<span set:html={status.replace(/(↹|↑↓|↵|SPACE)/g, '<span class="text-yellow">$1</span>')}></span>
|
||||
{
|
||||
/* set:html is safe: status is a static author-controlled string, never user input. */
|
||||
}
|
||||
<div
|
||||
class="flex justify-between border-t border-line bg-crust px-3 py-1.5 text-xs text-overlay"
|
||||
>
|
||||
<span
|
||||
set:html={status.replace(
|
||||
/(↹|↑↓|↵|SPACE)/g,
|
||||
'<span class="text-yellow">$1</span>',
|
||||
)}
|
||||
/>
|
||||
<span>4 files · 1.2 GB</span>
|
||||
</div>
|
||||
<div class="bg-base px-6 py-6 text-center">
|
||||
<h1 class="text-2xl font-bold text-text">The dual-pane explorer for your terminal</h1>
|
||||
<p class="mx-auto mt-2 max-w-xl text-subtext">Local left, remote right. Transfer, sync, edit — never leave the keyboard.</p>
|
||||
<h1 class="text-2xl font-bold text-text">
|
||||
The dual-pane explorer for your terminal
|
||||
</h1>
|
||||
<p class="mx-auto mt-2 max-w-xl text-subtext">
|
||||
Local left, remote right. Transfer, sync, edit — never leave the
|
||||
keyboard.
|
||||
</p>
|
||||
<div class="mt-5 flex flex-wrap justify-center gap-3">
|
||||
<a href="/install" class="rounded-lg bg-green px-5 py-2.5 font-bold text-crust">Install termscp →</a>
|
||||
<a href={DOCS_URL} class="rounded-lg border border-line px-5 py-2.5 text-text">User manual</a>
|
||||
<a
|
||||
href="/install"
|
||||
class="rounded-lg bg-green px-5 py-2.5 font-bold text-crust"
|
||||
>Install termscp →</a
|
||||
>
|
||||
<a
|
||||
href={DOCS_URL}
|
||||
class="rounded-lg border border-line px-5 py-2.5 text-text"
|
||||
>User manual</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
---
|
||||
interface Props { title: string; body: string; }
|
||||
interface Props {
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
const { title, body } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="rounded-lg border border-line bg-mantle p-5">
|
||||
<h3 class="text-green">{title}</h3>
|
||||
<p class="mt-2 text-sm text-subtext">{body}</p>
|
||||
|
||||
@@ -2,13 +2,20 @@
|
||||
import { DOCS_URL, GITHUB_URL, VERSION } from "../consts";
|
||||
const year = new Date().getFullYear();
|
||||
---
|
||||
|
||||
<footer class="mt-auto border-t border-line bg-mantle">
|
||||
<div class="mx-auto flex max-w-5xl flex-col items-center gap-2 px-4 py-8 text-sm text-overlay">
|
||||
<div
|
||||
class="mx-auto flex max-w-5xl flex-col items-center gap-2 px-4 py-8 text-sm text-overlay"
|
||||
>
|
||||
<div class="flex gap-5">
|
||||
<a href={GITHUB_URL} class="hover:text-text">GitHub</a>
|
||||
<a href="https://crates.io/crates/termscp" class="hover:text-text">crates.io</a>
|
||||
<a href="https://crates.io/crates/termscp" class="hover:text-text"
|
||||
>crates.io</a
|
||||
>
|
||||
<a href={DOCS_URL} class="hover:text-text">User manual</a>
|
||||
</div>
|
||||
<p>termscp v{VERSION} · © {year} Christian Visintin · Released under the MIT license.</p>
|
||||
<p>
|
||||
termscp v{VERSION} · © {year} Christian Visintin · Released under the MIT license.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,35 +1,52 @@
|
||||
---
|
||||
import CopyButton from "./CopyButton.astro";
|
||||
|
||||
interface Method { id: string; label: string; cmd: string; }
|
||||
interface Props { methods: Method[]; }
|
||||
interface Method {
|
||||
id: string;
|
||||
label: string;
|
||||
cmd: string;
|
||||
}
|
||||
interface Props {
|
||||
methods: Method[];
|
||||
}
|
||||
const { methods } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="rounded-lg border border-line bg-mantle">
|
||||
<div class="flex flex-wrap gap-1 border-b border-line p-2" role="tablist">
|
||||
{methods.map((m, i) => (
|
||||
<button
|
||||
class="install-tab rounded px-3 py-1 text-sm text-overlay data-[active]:bg-base data-[active]:text-green"
|
||||
data-target={m.id}
|
||||
data-active={i === 0 ? "" : undefined}
|
||||
>{m.label}</button>
|
||||
))}
|
||||
{
|
||||
methods.map((m, i) => (
|
||||
<button
|
||||
class="install-tab rounded px-3 py-1 text-sm text-overlay data-[active]:bg-base data-[active]:text-green"
|
||||
data-target={m.id}
|
||||
data-active={i === 0 ? "" : undefined}
|
||||
>
|
||||
{m.label}
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{methods.map((m, i) => (
|
||||
<div class="install-panel relative" data-id={m.id} hidden={i !== 0}>
|
||||
<pre class="m-0 overflow-auto bg-base px-4 py-4 pr-12 text-sm text-text"><code>{m.cmd}</code></pre>
|
||||
<CopyButton text={m.cmd} class="absolute right-2 top-2" />
|
||||
</div>
|
||||
))}
|
||||
{
|
||||
methods.map((m, i) => (
|
||||
<div class="install-panel relative" data-id={m.id} hidden={i !== 0}>
|
||||
<pre class="m-0 overflow-auto bg-base px-4 py-4 pr-12 text-sm text-text">
|
||||
<code>{m.cmd}</code>
|
||||
</pre>
|
||||
<CopyButton text={m.cmd} class="absolute right-2 top-2" />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<script is:inline>
|
||||
document.querySelectorAll(".install-tab").forEach((tab) => {
|
||||
tab.addEventListener("click", () => {
|
||||
const id = tab.getAttribute("data-target");
|
||||
document.querySelectorAll(".install-tab").forEach((t) =>
|
||||
t.toggleAttribute("data-active", t === tab));
|
||||
document.querySelectorAll(".install-panel").forEach((p) =>
|
||||
(p.hidden = p.getAttribute("data-id") !== id));
|
||||
document
|
||||
.querySelectorAll(".install-tab")
|
||||
.forEach((t) => t.toggleAttribute("data-active", t === tab));
|
||||
document
|
||||
.querySelectorAll(".install-panel")
|
||||
.forEach((p) => (p.hidden = p.getAttribute("data-id") !== id));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
import ThemeToggle from "./ThemeToggle.astro";
|
||||
import { DOCS_URL, GITHUB_URL } from "../consts";
|
||||
---
|
||||
<header class="sticky top-0 z-50 border-b border-line bg-mantle/90 backdrop-blur">
|
||||
|
||||
<header
|
||||
class="sticky top-0 z-50 border-b border-line bg-mantle/90 backdrop-blur"
|
||||
>
|
||||
<nav class="mx-auto flex max-w-5xl items-center justify-between px-4 py-3">
|
||||
<a href="/" class="flex items-center gap-2 text-text">
|
||||
<img src="/assets/images/termscp.webp" alt="termscp" class="h-7 w-7" />
|
||||
@@ -19,8 +22,13 @@ import { DOCS_URL, GITHUB_URL } from "../consts";
|
||||
|
||||
<!-- Mobile -->
|
||||
<details class="relative sm:hidden">
|
||||
<summary class="cursor-pointer list-none rounded p-2 text-text hover:bg-mantle" aria-label="Menu">☰</summary>
|
||||
<div class="absolute right-0 mt-2 flex w-48 flex-col gap-3 rounded-lg border border-line bg-mantle p-4 text-sm shadow-xl">
|
||||
<summary
|
||||
class="cursor-pointer list-none rounded p-2 text-text hover:bg-mantle"
|
||||
aria-label="Menu">☰</summary
|
||||
>
|
||||
<div
|
||||
class="absolute right-0 mt-2 flex w-48 flex-col gap-3 rounded-lg border border-line bg-mantle p-4 text-sm shadow-xl"
|
||||
>
|
||||
<a href="/install" class="text-overlay hover:text-text">Install</a>
|
||||
<a href={DOCS_URL} class="text-overlay hover:text-text">User manual</a>
|
||||
<a href={GITHUB_URL} class="text-overlay hover:text-text">GitHub</a>
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
---
|
||||
const protocols = ["SCP", "SFTP", "FTP/S", "S3", "Kube", "SMB", "WebDAV"];
|
||||
---
|
||||
|
||||
<section class="mx-auto max-w-5xl px-4 py-12 text-center">
|
||||
<h2 class="text-overlay text-sm uppercase tracking-widest">Speaks every protocol</h2>
|
||||
<h2 class="text-overlay text-sm uppercase tracking-widest">
|
||||
Speaks every protocol
|
||||
</h2>
|
||||
<div class="mt-4 flex flex-wrap justify-center gap-2">
|
||||
{protocols.map((proto) => (
|
||||
<span class="rounded-full border border-line px-4 py-1 text-sm text-text">{proto}</span>
|
||||
))}
|
||||
{
|
||||
protocols.map((proto) => (
|
||||
<span class="rounded-full border border-line px-4 py-1 text-sm text-text">
|
||||
{proto}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
// Theme toggle. Contract: localStorage 'theme' = "frappe" | "latte". Safe to render multiple times.
|
||||
---
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-theme-toggle
|
||||
@@ -17,15 +18,22 @@
|
||||
window.__themeToggleInit = true;
|
||||
const root = document.documentElement;
|
||||
const btns = document.querySelectorAll("[data-theme-toggle]");
|
||||
const setPressed = () => btns.forEach((b) =>
|
||||
b.setAttribute("aria-pressed", String(root.getAttribute("data-theme") === "latte")));
|
||||
const setPressed = () =>
|
||||
btns.forEach((b) =>
|
||||
b.setAttribute(
|
||||
"aria-pressed",
|
||||
String(root.getAttribute("data-theme") === "latte"),
|
||||
),
|
||||
);
|
||||
btns.forEach((b) =>
|
||||
b.addEventListener("click", () => {
|
||||
const next = root.getAttribute("data-theme") === "frappe" ? "latte" : "frappe";
|
||||
const next =
|
||||
root.getAttribute("data-theme") === "frappe" ? "latte" : "frappe";
|
||||
root.setAttribute("data-theme", next);
|
||||
localStorage.setItem("theme", next);
|
||||
setPressed();
|
||||
}));
|
||||
}),
|
||||
);
|
||||
setPressed();
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -30,7 +30,10 @@ const ogImage = new URL("/assets/images/og_preview.jpg", site).href;
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:url" content={canonical} />
|
||||
<meta property="og:image" content={ogImage} />
|
||||
<meta property="og:image:alt" content="termscp — terminal file transfer and explorer" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="termscp — terminal file transfer and explorer"
|
||||
/>
|
||||
<meta property="og:image:width" content="1280" />
|
||||
<meta property="og:image:height" content="640" />
|
||||
|
||||
@@ -41,24 +44,51 @@ const ogImage = new URL("/assets/images/og_preview.jpg", site).href;
|
||||
<meta name="twitter:image" content={ogImage} />
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/assets/images/favicon-96x96.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="96x96"
|
||||
href="/assets/images/favicon-96x96.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/assets/images/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/assets/images/favicon-16x16.png"
|
||||
/>
|
||||
|
||||
<!-- Font preload -->
|
||||
<link rel="preload" href="/fonts/jetbrains-mono-400.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/jetbrains-mono-400.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin
|
||||
/>
|
||||
|
||||
<!-- No-flash theme bootstrap. Contract: localStorage 'theme' is "frappe" | "latte"; ThemeToggle writes the same. -->
|
||||
<script is:inline>
|
||||
(() => {
|
||||
const t = localStorage.getItem("theme") ||
|
||||
(matchMedia("(prefers-color-scheme: light)").matches ? "latte" : "frappe");
|
||||
const t =
|
||||
localStorage.getItem("theme") ||
|
||||
(matchMedia("(prefers-color-scheme: light)").matches
|
||||
? "latte"
|
||||
: "frappe");
|
||||
document.documentElement.setAttribute("data-theme", t);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Umami analytics -->
|
||||
<script defer src="https://cloud.umami.is/script.js" data-website-id="ad2d5621-a893-488c-baa6-d39c306374cf"></script>
|
||||
<script
|
||||
defer
|
||||
src="https://cloud.umami.is/script.js"
|
||||
data-website-id="ad2d5621-a893-488c-baa6-d39c306374cf"></script>
|
||||
</head>
|
||||
<body class="bg-base text-text font-mono min-h-screen flex flex-col">
|
||||
<slot />
|
||||
|
||||
@@ -7,14 +7,30 @@ import ProtocolStrip from "../components/ProtocolStrip.astro";
|
||||
import FeatureCard from "../components/FeatureCard.astro";
|
||||
|
||||
const features = [
|
||||
{ title: "Handy UI", body: "Explore and operate on the remote and local file system with a handy UI." },
|
||||
{
|
||||
title: "Handy UI",
|
||||
body: "Explore and operate on the remote and local file system with a handy UI.",
|
||||
},
|
||||
{ title: "Cross platform", body: "Runs on Windows, macOS, Linux and BSD." },
|
||||
{ title: "Customizable", body: "Customize the explorer, the text editor and default options." },
|
||||
{ title: "Bookmarks", body: "Connect to favourite hosts via bookmarks and recent connections." },
|
||||
{ title: "Security first", body: "Store passwords in your operating system key vault." },
|
||||
{ title: "Eye on performance", body: "Built with an eye on performance to keep CPU usage low." },
|
||||
{
|
||||
title: "Customizable",
|
||||
body: "Customize the explorer, the text editor and default options.",
|
||||
},
|
||||
{
|
||||
title: "Bookmarks",
|
||||
body: "Connect to favourite hosts via bookmarks and recent connections.",
|
||||
},
|
||||
{
|
||||
title: "Security first",
|
||||
body: "Store passwords in your operating system key vault.",
|
||||
},
|
||||
{
|
||||
title: "Eye on performance",
|
||||
body: "Built with an eye on performance to keep CPU usage low.",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<Base
|
||||
title="termscp — terminal file transfer & explorer for SCP/SFTP/FTP/S3/Kube/SMB/WebDAV"
|
||||
description="A feature-rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV/SMB."
|
||||
@@ -26,9 +42,7 @@ const features = [
|
||||
<section class="mx-auto max-w-5xl px-4 py-12">
|
||||
<h2 class="mb-6 text-center text-xl text-text">Why termscp</h2>
|
||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{features.map((f) => (
|
||||
<FeatureCard title={f.title} body={f.body} />
|
||||
))}
|
||||
{features.map((f) => <FeatureCard title={f.title} body={f.body} />)}
|
||||
</div>
|
||||
</section>
|
||||
<ProtocolStrip />
|
||||
|
||||
@@ -6,7 +6,8 @@ import InstallTabs from "../components/InstallTabs.astro";
|
||||
import CopyButton from "../components/CopyButton.astro";
|
||||
import { DOCS_URL } from "../consts";
|
||||
|
||||
const script = "curl --proto '=https' --tlsv1.2 -sSLf https://termscp.rs/install.sh | sh";
|
||||
const script =
|
||||
"curl --proto '=https' --tlsv1.2 -sSLf https://termscp.rs/install.sh | sh";
|
||||
const psScript = "irm https://termscp.rs/install.ps1 | iex";
|
||||
|
||||
const methods = [
|
||||
@@ -16,6 +17,7 @@ const methods = [
|
||||
{ id: "choco", label: "Windows (Chocolatey)", cmd: "choco install termscp" },
|
||||
];
|
||||
---
|
||||
|
||||
<Base
|
||||
title="Install termscp"
|
||||
description="Install termscp on Linux, macOS, BSD and Windows — one shell command, or via Cargo, pacman, pkgin and Chocolatey."
|
||||
@@ -24,37 +26,58 @@ const methods = [
|
||||
<Nav />
|
||||
<main class="mx-auto w-full max-w-3xl flex-1 px-4 py-12">
|
||||
<h1 class="text-2xl font-bold text-text">Install termscp</h1>
|
||||
<p class="mt-2 text-subtext">Pick your platform. One command and you're in.</p>
|
||||
<p class="mt-2 text-subtext">
|
||||
Pick your platform. One command and you're in.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 rounded-lg border border-line bg-mantle p-1">
|
||||
<div class="flex items-center gap-2 border-b border-line px-3 py-2 text-xs text-overlay">
|
||||
<div
|
||||
class="flex items-center gap-2 border-b border-line px-3 py-2 text-xs text-overlay"
|
||||
>
|
||||
<span class="text-green">$</span> Linux · macOS · BSD
|
||||
</div>
|
||||
<div class="relative">
|
||||
<pre class="m-0 overflow-auto bg-base px-4 py-4 pr-12 text-sm text-text"><code>{script}</code></pre>
|
||||
<pre
|
||||
class="m-0 overflow-auto bg-base px-4 py-4 pr-12 text-sm text-text"><code>{script}</code></pre>
|
||||
<CopyButton text={script} class="absolute right-2 top-2" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-overlay">Requires <span class="text-text">curl</span>. macOS uses Homebrew if available, otherwise builds from source.</p>
|
||||
<p class="mt-2 text-xs text-overlay">
|
||||
Requires <span class="text-text">curl</span>. macOS uses Homebrew if
|
||||
available, otherwise builds from source.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 rounded-lg border border-line bg-mantle p-1">
|
||||
<div class="flex items-center gap-2 border-b border-line px-3 py-2 text-xs text-overlay">
|
||||
<div
|
||||
class="flex items-center gap-2 border-b border-line px-3 py-2 text-xs text-overlay"
|
||||
>
|
||||
<span class="text-green">></span> Windows (PowerShell)
|
||||
</div>
|
||||
<div class="relative">
|
||||
<pre class="m-0 overflow-auto bg-base px-4 py-4 pr-12 text-sm text-text"><code>{psScript}</code></pre>
|
||||
<pre
|
||||
class="m-0 overflow-auto bg-base px-4 py-4 pr-12 text-sm text-text"><code>{psScript}</code></pre>
|
||||
<CopyButton text={psScript} class="absolute right-2 top-2" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-overlay">Downloads the latest release and adds <span class="text-text">termscp</span> to your user PATH.</p>
|
||||
<p class="mt-2 text-xs text-overlay">
|
||||
Downloads the latest release and adds <span class="text-text"
|
||||
>termscp</span
|
||||
> to your user PATH.
|
||||
</p>
|
||||
|
||||
<h2 class="mt-10 mb-3 text-lg text-text">Package managers</h2>
|
||||
<InstallTabs methods={methods} />
|
||||
|
||||
<p class="mt-8 text-sm text-overlay">Update anytime with <span class="text-green">termscp --update</span>.</p>
|
||||
<p class="mt-8 text-sm text-overlay">
|
||||
Update anytime with <span class="text-green">termscp --update</span>.
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-overlay">
|
||||
Linux build deps: <span class="text-text">libdbus-1</span>, <span class="text-text">pkg-config</span>, <span class="text-text">libsmbclient</span>.
|
||||
More details in the <a href={DOCS_URL} class="text-blue hover:underline">docs</a>.
|
||||
Linux build deps: <span class="text-text">libdbus-1</span>, <span
|
||||
class="text-text">pkg-config</span
|
||||
>, <span class="text-text">libsmbclient</span>. More details in the <a
|
||||
href={DOCS_URL}
|
||||
class="text-blue hover:underline">docs</a
|
||||
>.
|
||||
</p>
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
@@ -80,9 +80,15 @@ html {
|
||||
}
|
||||
|
||||
/* Theme toggle icons: shown based on active theme, set before paint by the head bootstrap */
|
||||
[data-icon] { display: none; }
|
||||
[data-theme="frappe"] [data-icon="frappe"] { display: inline; }
|
||||
[data-theme="latte"] [data-icon="latte"] { display: inline; }
|
||||
[data-icon] {
|
||||
display: none;
|
||||
}
|
||||
[data-theme="frappe"] [data-icon="frappe"] {
|
||||
display: inline;
|
||||
}
|
||||
[data-theme="latte"] [data-icon="latte"] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.blink {
|
||||
display: inline-block;
|
||||
@@ -92,4 +98,8 @@ html {
|
||||
vertical-align: -1px;
|
||||
animation: blink 1s steps(1) infinite;
|
||||
}
|
||||
@keyframes blink { 50% { opacity: 0; } }
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user