feat: update Obsidian connector UI and improve user instructions

This commit is contained in:
Anish Sarkar 2026-04-22 06:07:38 +05:30
parent 9ecccc5403
commit ae264290d0
3 changed files with 117 additions and 134 deletions

View file

@ -1,6 +1,6 @@
"use client";
import { Check, Copy, Download, Info, KeyRound, Settings2 } from "lucide-react";
import { Check, Copy, Info } from "lucide-react";
import { type FC, useCallback, useRef, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
@ -55,145 +55,126 @@ export const ObsidianConnectForm: FC<ConnectFormProps> = ({ onBack }) => {
that just closes the dialog (see component-level docstring). */}
<form id="obsidian-connect-form" onSubmit={handleSubmit} />
<Alert className="border-purple-500/30 bg-purple-500/10 p-2 sm:p-3 dark:bg-purple-500/10">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0 text-purple-500" />
<AlertTitle className="text-xs sm:text-sm">Plugin-based sync</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs">
SurfSense now syncs Obsidian via an official plugin that runs inside
Obsidian itself. Works on desktop and mobile, in cloud and self-hosted
deployments no server-side vault mounts required.
deployments.
</AlertDescription>
</Alert>
{/* Step 1 — Install plugin */}
<section className="rounded-xl border border-border bg-slate-400/5 p-3 sm:p-6 dark:bg-white/5">
<header className="mb-3 flex items-center gap-2">
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
1
</div>
<h3 className="text-sm font-medium sm:text-base">Install the plugin</h3>
</header>
<p className="mb-3 text-[11px] text-muted-foreground sm:text-xs">
Grab the latest SurfSense plugin release. Once it's in the community
store, you'll also be able to install it from{" "}
<span className="font-medium">Settings Community plugins</span>{" "}
inside Obsidian.
</p>
<a
href={PLUGIN_RELEASES_URL}
target="_blank"
rel="noopener noreferrer"
className="inline-flex"
>
<Button type="button" variant="outline" size="sm" className="gap-2">
<Download className="size-3.5" />
Open plugin releases
</Button>
</a>
</section>
{/* Step 2 — Copy API key */}
<section className="rounded-xl border border-border bg-slate-400/5 p-3 sm:p-6 dark:bg-white/5">
<header className="mb-3 flex items-center gap-2">
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
2
</div>
<h3 className="text-sm font-medium sm:text-base">
Copy your API key
</h3>
<KeyRound className="ml-auto size-4 text-muted-foreground" />
</header>
<p className="mb-3 text-[11px] text-muted-foreground sm:text-xs">
Paste this into the plugin's <span className="font-medium">API token</span>{" "}
setting. The token expires after 24 hours; long-lived personal access
tokens are coming in a future release.
</p>
{isLoading ? (
<div className="h-10 w-full animate-pulse rounded-md border border-border/60 bg-muted/30" />
) : apiKey ? (
<div className="flex items-center gap-2 rounded-md border border-border/60 bg-muted/30 px-2.5 py-1.5">
<div className="min-w-0 flex-1 overflow-x-auto scrollbar-hide">
<p className="cursor-text select-all whitespace-nowrap font-mono text-[10px] text-muted-foreground">
{apiKey}
</p>
</div>
<Button
type="button"
variant="ghost"
size="icon"
onClick={copyToClipboard}
className="size-7 shrink-0 text-muted-foreground hover:text-foreground"
aria-label={copied ? "Copied" : "Copy API key"}
>
{copied ? (
<Check className="size-3.5 text-green-500" />
) : (
<Copy className="size-3.5" />
)}
</Button>
</div>
) : (
<p className="text-center text-xs text-muted-foreground/60">
No API key available try refreshing the page.
</p>
)}
</section>
{/* Step 3 — Server URL */}
<section className="rounded-xl border border-border bg-slate-400/5 p-3 sm:p-6 dark:bg-white/5">
<header className="mb-3 flex items-center gap-2">
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
3
</div>
<h3 className="text-sm font-medium sm:text-base">
Point the plugin at this server
</h3>
</header>
<p className="mb-3 text-[11px] text-muted-foreground sm:text-xs">
Paste this URL into the plugin's <span className="font-medium">Server URL</span>{" "}
setting. We auto-detect it from your current dashboard origin.
</p>
<div className="flex items-center gap-2 rounded-md border border-border/60 bg-muted/30 px-2.5 py-1.5">
<div className="min-w-0 flex-1 overflow-x-auto scrollbar-hide">
<p className="cursor-text select-all whitespace-nowrap font-mono text-[10px] text-muted-foreground">
{BACKEND_URL}
<div className="space-y-5 sm:space-y-6">
{/* Step 1 — Install plugin */}
<article>
<header className="mb-3 flex items-center gap-2">
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
1
</div>
<h3 className="text-sm font-medium sm:text-base">Install the plugin</h3>
</header>
<p className="mb-3 text-[11px] text-muted-foreground sm:text-xs">
Grab the latest SurfSense plugin release. Once it's in the community
store, you'll also be able to install it from{" "}
<span className="font-medium">Settings Community plugins</span>{" "}
inside Obsidian.
</p>
</div>
<Button
type="button"
variant="ghost"
size="icon"
onClick={copyServerUrl}
className="size-7 shrink-0 text-muted-foreground hover:text-foreground"
aria-label={copiedUrl ? "Copied" : "Copy server URL"}
>
{copiedUrl ? (
<Check className="size-3.5 text-green-500" />
) : (
<Copy className="size-3.5" />
)}
</Button>
</div>
</section>
<a
href={PLUGIN_RELEASES_URL}
target="_blank"
rel="noopener noreferrer"
className="inline-flex"
>
<Button type="button" variant="secondary" size="sm" className="gap-2 text-xs sm:text-sm">
Open plugin releases
</Button>
</a>
</article>
{/* Step 4 — Pick search space */}
<section className="rounded-xl border border-border bg-slate-400/5 p-3 sm:p-6 dark:bg-white/5">
<header className="mb-3 flex items-center gap-2">
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
4
</div>
<h3 className="text-sm font-medium sm:text-base">
Pick this search space
</h3>
<Settings2 className="ml-auto size-4 text-muted-foreground" />
</header>
<p className="text-[11px] text-muted-foreground sm:text-xs">
In the plugin's <span className="font-medium">Search space</span>{" "}
setting, choose the search space you want this vault to sync into.
The connector will appear here automatically once the plugin makes
its first sync.
</p>
<div className="h-px bg-border/60" />
{/* Step 2 — Copy API key */}
<article>
<header className="mb-3 flex items-center gap-2">
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
2
</div>
<h3 className="text-sm font-medium sm:text-base">Copy your API key</h3>
</header>
<p className="mb-3 text-[11px] text-muted-foreground sm:text-xs">
Paste this into the plugin's <span className="font-medium">API token</span>{" "}
setting. The token expires after 24 hours. Long-lived personal access
tokens are coming in a future release.
</p>
{isLoading ? (
<div className="h-10 w-full animate-pulse rounded-md border border-border/60 bg-muted/30" />
) : apiKey ? (
<div className="flex items-center gap-2 rounded-md border border-border/60 bg-muted/30 px-2.5 py-1.5">
<div className="min-w-0 flex-1 overflow-x-auto scrollbar-hide">
<p className="cursor-text select-all whitespace-nowrap font-mono text-[10px] text-muted-foreground">
{apiKey}
</p>
</div>
<Button
type="button"
variant="ghost"
size="icon"
onClick={copyToClipboard}
className="size-7 shrink-0 text-muted-foreground hover:text-foreground"
aria-label={copied ? "Copied" : "Copy API key"}
>
{copied ? (
<Check className="size-3.5 text-green-500" />
) : (
<Copy className="size-3.5" />
)}
</Button>
</div>
) : (
<p className="text-center text-xs text-muted-foreground/60">
No API key available try refreshing the page.
</p>
)}
</article>
<div className="h-px bg-border/60" />
{/* Step 3 — Server URL */}
<article>
<header className="mb-3 flex items-center gap-2">
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
3
</div>
<h3 className="text-sm font-medium sm:text-base">Point the plugin at this server</h3>
</header>
<p className="text-[11px] text-muted-foreground sm:text-xs">
For SurfSense Cloud, use the default <span className="font-medium">surfsense.com</span>.
If you are self-hosting, set the plugin's{" "}
<span className="font-medium">Server URL</span> to your frontend domain.
</p>
</article>
<div className="h-px bg-border/60" />
{/* Step 4 — Pick search space */}
<article>
<header className="mb-3 flex items-center gap-2">
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
4
</div>
<h3 className="text-sm font-medium sm:text-base">Pick this search space</h3>
</header>
<p className="text-[11px] text-muted-foreground sm:text-xs">
In the plugin's <span className="font-medium">Search space</span>{" "}
setting, choose the search space you want this vault to sync into.
The connector will appear here automatically once the plugin makes
its first sync.
</p>
</article>
</div>
</section>
{getConnectorBenefits(EnumConnectorName.OBSIDIAN_CONNECTOR) && (

View file

@ -130,20 +130,20 @@ const PluginStats: FC<{ config: Record<string, unknown> }> = ({ config }) => {
<Info className="size-4 shrink-0 text-emerald-500" />
<AlertTitle className="text-xs sm:text-sm">Plugin connected</AlertTitle>
<AlertDescription className="text-[11px] sm:text-xs">
Edits in Obsidian sync over HTTPS. To stop syncing, disable or uninstall the plugin in
Your notes stay synced automatically. To stop syncing, disable or uninstall the plugin in
Obsidian, or delete this connector.
</AlertDescription>
</Alert>
<div className="rounded-xl border border-border bg-slate-400/5 p-3 sm:p-6 dark:bg-white/5">
<div className="rounded-xl bg-slate-400/5 p-3 sm:p-6 dark:bg-white/5">
<h3 className="mb-3 text-sm font-medium sm:text-base">Vault status</h3>
<dl className="grid grid-cols-1 gap-3 sm:grid-cols-2">
{tileRows.map((stat) => (
<div
key={stat.label}
className="rounded-lg border border-slate-400/20 bg-background/50 p-3"
className="rounded-lg bg-background/50 p-3"
>
<dt className="text-[10px] uppercase tracking-wide text-muted-foreground sm:text-xs">
<dt className="text-xs tracking-wide text-muted-foreground sm:text-sm">
{stat.label}
</dt>
<dd className="mt-1 truncate text-xs font-medium sm:text-sm">{stat.value}</dd>

View file

@ -151,7 +151,9 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
<span className={isSubmitting ? "opacity-0" : ""}>
{connectorType === "MCP_CONNECTOR"
? "Connect"
: `Connect ${getConnectorTypeDisplay(connectorType)}`}
: connectorType === "OBSIDIAN_CONNECTOR"
? "Done"
: `Connect ${getConnectorTypeDisplay(connectorType)}`}
</span>
{isSubmitting && <Spinner size="sm" className="absolute" />}
</Button>