mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-28 21:49:40 +02:00
feat: update Obsidian connector UI and improve user instructions
This commit is contained in:
parent
9ecccc5403
commit
ae264290d0
3 changed files with 117 additions and 134 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"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 { type FC, useCallback, useRef, useState } from "react";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { Button } from "@/components/ui/button";
|
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). */}
|
that just closes the dialog (see component-level docstring). */}
|
||||||
<form id="obsidian-connect-form" onSubmit={handleSubmit} />
|
<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" />
|
<Info className="size-4 shrink-0 text-purple-500" />
|
||||||
<AlertTitle className="text-xs sm:text-sm">Plugin-based sync</AlertTitle>
|
<AlertTitle className="text-xs sm:text-sm">Plugin-based sync</AlertTitle>
|
||||||
<AlertDescription className="text-[10px] sm:text-xs">
|
<AlertDescription className="text-[10px] sm:text-xs">
|
||||||
SurfSense now syncs Obsidian via an official plugin that runs inside
|
SurfSense now syncs Obsidian via an official plugin that runs inside
|
||||||
Obsidian itself. Works on desktop and mobile, in cloud and self-hosted
|
Obsidian itself. Works on desktop and mobile, in cloud and self-hosted
|
||||||
deployments — no server-side vault mounts required.
|
deployments.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</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">
|
<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="space-y-5 sm:space-y-6">
|
||||||
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
|
{/* Step 1 — Install plugin */}
|
||||||
1
|
<article>
|
||||||
</div>
|
<header className="mb-3 flex items-center gap-2">
|
||||||
<h3 className="text-sm font-medium sm:text-base">Install the plugin</h3>
|
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
|
||||||
</header>
|
1
|
||||||
<p className="mb-3 text-[11px] text-muted-foreground sm:text-xs">
|
</div>
|
||||||
Grab the latest SurfSense plugin release. Once it's in the community
|
<h3 className="text-sm font-medium sm:text-base">Install the plugin</h3>
|
||||||
store, you'll also be able to install it from{" "}
|
</header>
|
||||||
<span className="font-medium">Settings → Community plugins</span>{" "}
|
<p className="mb-3 text-[11px] text-muted-foreground sm:text-xs">
|
||||||
inside Obsidian.
|
Grab the latest SurfSense plugin release. Once it's in the community
|
||||||
</p>
|
store, you'll also be able to install it from{" "}
|
||||||
<a
|
<span className="font-medium">Settings → Community plugins</span>{" "}
|
||||||
href={PLUGIN_RELEASES_URL}
|
inside Obsidian.
|
||||||
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}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<a
|
||||||
<Button
|
href={PLUGIN_RELEASES_URL}
|
||||||
type="button"
|
target="_blank"
|
||||||
variant="ghost"
|
rel="noopener noreferrer"
|
||||||
size="icon"
|
className="inline-flex"
|
||||||
onClick={copyServerUrl}
|
>
|
||||||
className="size-7 shrink-0 text-muted-foreground hover:text-foreground"
|
<Button type="button" variant="secondary" size="sm" className="gap-2 text-xs sm:text-sm">
|
||||||
aria-label={copiedUrl ? "Copied" : "Copy server URL"}
|
Open plugin releases
|
||||||
>
|
</Button>
|
||||||
{copiedUrl ? (
|
</a>
|
||||||
<Check className="size-3.5 text-green-500" />
|
</article>
|
||||||
) : (
|
|
||||||
<Copy className="size-3.5" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Step 4 — Pick search space */}
|
<div className="h-px bg-border/60" />
|
||||||
<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">
|
{/* Step 2 — Copy API key */}
|
||||||
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
|
<article>
|
||||||
4
|
<header className="mb-3 flex items-center gap-2">
|
||||||
</div>
|
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
|
||||||
<h3 className="text-sm font-medium sm:text-base">
|
2
|
||||||
Pick this search space
|
</div>
|
||||||
</h3>
|
<h3 className="text-sm font-medium sm:text-base">Copy your API key</h3>
|
||||||
<Settings2 className="ml-auto size-4 text-muted-foreground" />
|
</header>
|
||||||
</header>
|
<p className="mb-3 text-[11px] text-muted-foreground sm:text-xs">
|
||||||
<p className="text-[11px] text-muted-foreground sm:text-xs">
|
Paste this into the plugin's <span className="font-medium">API token</span>{" "}
|
||||||
In the plugin's <span className="font-medium">Search space</span>{" "}
|
setting. The token expires after 24 hours. Long-lived personal access
|
||||||
setting, choose the search space you want this vault to sync into.
|
tokens are coming in a future release.
|
||||||
The connector will appear here automatically once the plugin makes
|
</p>
|
||||||
its first sync.
|
|
||||||
</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>
|
</section>
|
||||||
|
|
||||||
{getConnectorBenefits(EnumConnectorName.OBSIDIAN_CONNECTOR) && (
|
{getConnectorBenefits(EnumConnectorName.OBSIDIAN_CONNECTOR) && (
|
||||||
|
|
|
||||||
|
|
@ -130,20 +130,20 @@ const PluginStats: FC<{ config: Record<string, unknown> }> = ({ config }) => {
|
||||||
<Info className="size-4 shrink-0 text-emerald-500" />
|
<Info className="size-4 shrink-0 text-emerald-500" />
|
||||||
<AlertTitle className="text-xs sm:text-sm">Plugin connected</AlertTitle>
|
<AlertTitle className="text-xs sm:text-sm">Plugin connected</AlertTitle>
|
||||||
<AlertDescription className="text-[11px] sm:text-xs">
|
<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.
|
Obsidian, or delete this connector.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</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>
|
<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">
|
<dl className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||||
{tileRows.map((stat) => (
|
{tileRows.map((stat) => (
|
||||||
<div
|
<div
|
||||||
key={stat.label}
|
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}
|
{stat.label}
|
||||||
</dt>
|
</dt>
|
||||||
<dd className="mt-1 truncate text-xs font-medium sm:text-sm">{stat.value}</dd>
|
<dd className="mt-1 truncate text-xs font-medium sm:text-sm">{stat.value}</dd>
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,9 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
|
||||||
<span className={isSubmitting ? "opacity-0" : ""}>
|
<span className={isSubmitting ? "opacity-0" : ""}>
|
||||||
{connectorType === "MCP_CONNECTOR"
|
{connectorType === "MCP_CONNECTOR"
|
||||||
? "Connect"
|
? "Connect"
|
||||||
: `Connect ${getConnectorTypeDisplay(connectorType)}`}
|
: connectorType === "OBSIDIAN_CONNECTOR"
|
||||||
|
? "Done"
|
||||||
|
: `Connect ${getConnectorTypeDisplay(connectorType)}`}
|
||||||
</span>
|
</span>
|
||||||
{isSubmitting && <Spinner size="sm" className="absolute" />}
|
{isSubmitting && <Spinner size="sm" className="absolute" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue