diff --git a/index.html b/index.html index 6894507..a917f5d 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@ property="og:description" content="Decentralized GitHub alternative over Nostr" /> - + - + - - - - + + + + - + + +
@@ -119,7 +125,7 @@
; } +/** Turn an eager node identity choice into GitWorkshop's extension login. */ +function ArchipelagoIdentityLogin() { + const login = useLoginActions(); + const loginRef = useRef(login.extension); + const loginRunning = useRef(false); + loginRef.current = login.extension; + + useEffect(() => { + const bridge = ( + window as Window & { + archipelagoNostr?: { + onIdentitySelected?: ( + callback: (identity: { nostr_pubkey: string }) => void, + ) => () => void; + }; + } + ).archipelagoNostr; + if (!bridge?.onIdentitySelected) return; + + return bridge.onIdentitySelected(() => { + if (accounts.getActive() || loginRunning.current) return; + loginRunning.current = true; + void loginRef + .current() + .catch((error) => { + console.error("Archipelago automatic login failed:", error); + }) + .finally(() => { + loginRunning.current = false; + }); + }); + }, []); + + return null; +} + function AppRouter() { return ( - + + diff --git a/src/components/AppFooter.tsx b/src/components/AppFooter.tsx index 3eb76d2..22b079b 100644 --- a/src/components/AppFooter.tsx +++ b/src/components/AppFooter.tsx @@ -62,7 +62,7 @@ export function AppFooter() { className="flex items-center gap-2 hover:opacity-80 transition-opacity w-fit" > GitWorkshop diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx index b31aa79..cec9d39 100644 --- a/src/components/AppHeader.tsx +++ b/src/components/AppHeader.tsx @@ -155,7 +155,11 @@ export function AppHeader() { to="/" className="group transition-opacity hover:opacity-80 shrink-0" > - GitWorkshop + GitWorkshop
diff --git a/src/main.tsx b/src/main.tsx index 1e4fead..ae3a045 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -10,9 +10,11 @@ import "@fontsource-variable/inter"; // itself, so subsequent loads are fully uncontrolled. Capacitor packages local // assets and does not use this web-deployment cleanup worker. if (!Capacitor.isNativePlatform() && "serviceWorker" in navigator) { - navigator.serviceWorker.register("/sw.js").catch(() => { - /* ignore — browser may block in certain envs */ - }); + navigator.serviceWorker + .register(`${import.meta.env.BASE_URL}sw.js`) + .catch(() => { + /* ignore — browser may block in certain envs */ + }); } createRoot(document.getElementById("root")!).render( diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx index 18e3593..685c422 100644 --- a/src/pages/NotFound.tsx +++ b/src/pages/NotFound.tsx @@ -28,7 +28,7 @@ const NotFound = () => { Oops! Page not found

Return to Home diff --git a/src/services/settings.ts b/src/services/settings.ts index 8f9a1a7..5438a72 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -124,8 +124,6 @@ export const fallbackRelaysCustomised$ = isCustomised$( * These are used by the event loaders to find events more efficiently. */ export const DEFAULT_LOOKUP_RELAYS = normalizeRelayList([ - "wss://purplepag.es", - "wss://index.hzrd149.com", "wss://indexer.coracle.social", ]); diff --git a/vite.config.ts b/vite.config.ts index 0534fb9..d94fc70 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -39,37 +39,38 @@ function htmlAppNamePlugin(): Plugin { */ function manifestPlugin(): Plugin { const virtualId = "/manifest.webmanifest"; + const appBase = process.env.APP_BASE_PATH ?? "/"; const manifest = JSON.stringify( { name: "GitWorkshop.dev", short_name: "GitWorkshop", description: "Decentralized GitHub alternative over Nostr", - start_url: "/", + start_url: appBase, display: "standalone", background_color: "#16171e", theme_color: "#16171e", categories: ["development", "productivity", "utilities"], icons: [ { - src: "/icons/icon-192x192.png", + src: `${appBase}icons/icon-192x192.png`, sizes: "192x192", type: "image/png", purpose: "any", }, { - src: "/icons/icon-512x512.png", + src: `${appBase}icons/icon-512x512.png`, sizes: "512x512", type: "image/png", purpose: "any", }, { - src: "/icons/pwa-maskable-192x192.png", + src: `${appBase}icons/pwa-maskable-192x192.png`, sizes: "192x192", type: "image/png", purpose: "maskable", }, { - src: "/icons/pwa-maskable-512x512.png", + src: `${appBase}icons/pwa-maskable-512x512.png`, sizes: "512x512", type: "image/png", purpose: "maskable", @@ -82,6 +83,12 @@ function manifestPlugin(): Plugin { return { name: "manifest", + transformIndexHtml(html) { + return html.replace( + 'href="/manifest.webmanifest"', + `href="${appBase}manifest.webmanifest"`, + ); + }, configureServer(server) { server.middlewares.use((req, res, next) => { if (req.url === virtualId) { @@ -104,6 +111,10 @@ function manifestPlugin(): Plugin { // https://vitejs.dev/config/ export default defineConfig(() => ({ + // Archipelago serves GitWorkshop behind the dashboard origin. Vite's base + // controls emitted asset URLs while BrowserRouter consumes the same value + // below, so repository routes remain valid below that mount point. + base: process.env.APP_BASE_PATH ?? "/", define: { __APP_NAME__: JSON.stringify(name), __APP_RELEASE_VERSION__: JSON.stringify( diff --git a/src/components/auth/AccountSwitcher.tsx b/src/components/auth/AccountSwitcher.tsx index f59a7d2..3168910 100644 --- a/src/components/auth/AccountSwitcher.tsx +++ b/src/components/auth/AccountSwitcher.tsx @@ -46,7 +46,7 @@ function SignerTypeBadge({ account }: { account: IAccount }) { return ( - Extension + Extension / Archipelago ); if (account instanceof NostrConnectAccount) diff --git a/src/components/auth/LoginDialog.tsx b/src/components/auth/LoginDialog.tsx index 11f6716..0bba6a2 100644 --- a/src/components/auth/LoginDialog.tsx +++ b/src/components/auth/LoginDialog.tsx @@ -239,9 +239,15 @@ const LoginDialog: React.FC = ({ try { if (!("nostr" in window)) { throw new Error( - "Nostr extension not found. Please install a NIP-07 extension.", + "No NIP-07 signer found. Open GitWorkshop through Archipelago or install a browser extension.", ); } + const archipelago = ( + window as Window & { + archipelagoNostr?: { selectIdentity?: () => Promise }; + } + ).archipelagoNostr; + if (archipelago?.selectIdentity) await archipelago.selectIdentity(); await login.extension(); onLogin(); onClose(); @@ -437,7 +443,9 @@ const LoginDialog: React.FC = ({ disabled={isLoading} > - {isLoading ? "Logging in..." : "Log in with Extension"} + {isLoading + ? "Logging in..." + : "Log in with Extension / Archipelago"} )} diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index b8de377..7f72f31 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -23,6 +23,7 @@ import { ChevronUp, Pin, Search, + Globe2, } from "lucide-react"; import { CreateRepoDialog } from "@/components/CreateRepoDialog"; import { Button } from "@/components/ui/button"; @@ -36,6 +37,7 @@ import { useUserActivity } from "@/hooks/useUserActivity"; import { useUserRepositories } from "@/hooks/useUserRepositories"; import { useUserFollowedRepos } from "@/hooks/useUserFollowedRepos"; import { useUserPinnedCoords } from "@/hooks/useUserPinnedRepos"; +import { useRepositorySearch } from "@/hooks/useRepositorySearch"; import { useNotifications } from "@/hooks/useNotifications"; import { useUserProfileSubscription } from "@/hooks/useUserProfileSubscription"; import { useUserPath } from "@/hooks/useUserPath"; @@ -409,6 +411,63 @@ function FollowedReposPanel({ pubkey }: { pubkey: string }) { ); } +// --------------------------------------------------------------------------- +// Recent repositories from the wider Nostr network +// --------------------------------------------------------------------------- + +function NetworkRepositoriesPanel() { + const { repos, isLoading } = useRepositorySearch(""); + const recent = repos?.slice(0, 6); + + return ( +
+
+

+ + Nostr network +

+ +
+ + {recent === undefined || (isLoading && recent.length === 0) ? ( +
+ {Array.from({ length: 5 }).map((_, i) => ( + + ))} +
+ ) : recent.length > 0 ? ( +
+ {recent.map((repo) => ( + + ))} +
+ ) : ( +
+

+ No network repositories available +

+

+ Check the git index relay in Settings +

+
+ )} +
+ ); +} + // --------------------------------------------------------------------------- // Embedded notifications panel (compact, inbox only, max 5) // --------------------------------------------------------------------------- @@ -591,6 +650,8 @@ export function Dashboard() { + +