import { useState, useEffect } from "react"; import SyncManager from "src/sync-manager"; import { usePlugin } from "src/views/hooks"; import { DEFAULT_SETTINGS, GitHubSyncSettings } from "src/settings/settings"; const STEPS = ["welcome", "repo", "token", "sync", "first_sync"] as const; type Step = (typeof STEPS)[number]; type StepData = { repo: { owner: string; repo: string; branch: string }; token: { token: string }; sync: { mode: "manual" | "interval"; syncOnStart: boolean; syncConfigDir: boolean; onConflict: "ignore" | "ask" | "overwrite"; }; }; const DEFAULT_STEP_DATA: StepData = { repo: { owner: "", repo: "", branch: "" }, token: { token: "" }, sync: { mode: "manual", syncOnStart: false, syncConfigDir: true, onConflict: "overwrite", }, }; const WelcomeStepComponent = () => { return (

First setup

This plugin is still in beta so not all features are available yet. If you find any issues report that.

You'll be guided through some steps to get you started.

); }; const RepoStepComponent = ({ values, onChange, }: { values: { owner: string; repo: string; branch: string }; onChange: (values: { owner: string; repo: string; branch: string }) => void; }) => { return (

First we need the repository owner, name and branch.

You must use a repository that you have read and write access to. If your vault already has content I suggest creating a new private repository to sync.

onChange({ ...values, owner: e.target.value })} /> onChange({ ...values, repo: e.target.value })} /> onChange({ ...values, branch: e.target.value })} />
); }; const TokenStepComponent = ({ values, onChange, }: { values: { token: string }; onChange: (values: { token: string }) => void; }) => { return (

Now we need a GitHub Personal Access Token.

The token is used to get, create and update the files between your vault and your GitHub repository. Click{" "} here to create a new token. For more information about the tokens see{" "} the official GitHub documentation.

The token must have the Contents Read and Write permissions for the same repository you set in the previous step.

onChange({ token: e.target.value })} />
); }; const SyncSettingsStepComponent = ({ values, onChange, }: { values: { mode: "manual" | "interval"; syncOnStart: boolean; syncConfigDir: boolean; onConflict: "ignore" | "ask" | "overwrite"; }; onChange: (values: { mode: "manual" | "interval"; syncOnStart: boolean; syncConfigDir: boolean; onConflict: "ignore" | "ask" | "overwrite"; }) => void; }) => { return (

You can sync your vault manually or every minute. You can change the interval at a later time.

Optionally you can sync every time you open Obsidian.

onChange({ ...values, syncOnStart: !values.syncOnStart }) } >

If you want different configs for different vaults you can disable config syncing.

onChange({ ...values, syncConfigDir: !values.syncConfigDir }) } >

In case of conflicts you can choose how to handle them.

This is not yet implemented so remote files will always overwrite local files in case of conflicts.

); }; const FirstSyncStepComponent = ({ stepData, onClose, }: { stepData: StepData; onClose: () => Promise; }) => { const [syncing, setSyncing] = useState(true); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); const plugin = usePlugin(); if (!plugin) { // Unlikely to happen, makes TS happy though throw new Error("Plugin is not initialized"); } useEffect(() => { (async () => { const settings: GitHubSyncSettings = { ...DEFAULT_SETTINGS, githubToken: stepData.token.token, githubOwner: stepData.repo.owner, githubRepo: stepData.repo.repo, githubBranch: stepData.repo.branch, syncStrategy: stepData.sync.mode, syncOnStartup: stepData.sync.syncOnStart, syncConfigDir: stepData.sync.syncConfigDir, conflictHandling: stepData.sync.onConflict, }; // We don't want to save the settings yet, so we create a new sync manager // used only for the first sync. // After that's sucessful we'll save the settings. const syncManager = new SyncManager( plugin.app.vault, settings, // There can't be any conflicts at this point so we don't need to handle them async (_) => { return []; }, ); await syncManager.loadMetadata(); try { await syncManager.firstSync(); } catch (e) { setError(e.message); setSuccess(false); setSyncing(false); return; } plugin.settings = settings; await plugin.saveSettings(); setSyncing(false); setSuccess(true); })(); }, [stepData]); const loadingBarStyle = () => { if (error) { return { width: 0 }; } else if (success) { return { width: "100%" }; } else { return {}; } }; return (

Now we can try and sync your vault with your repository. This might take a few minutes depending on the number of files.

{syncing ? "Syncing..." : null} {error ? "Syncing failed" : null} {success ? "Syncing complete" : null}
{error ?
{error}
: null} {success ?
Everything is set up and ready to go.
: null}
{success ? ( ) : null}
); }; const OnBoardingComponent = ({ onClose }: { onClose: () => Promise }) => { // It starts as true because the first step is just a welcome message // so the button is already enabled. const [isValid, setIsValid] = useState(true); const [step, setStep] = useState("welcome"); const [stepData, setStepData] = useState(DEFAULT_STEP_DATA); const updateStepData = (step: Step, data: StepData[keyof StepData]) => { setStepData((stepData) => { return { ...stepData, [step]: data, }; }); }; const isStepValid = (step: Step) => { switch (step) { // Only these steps have required data, the others don't // so the step is automatically valid case "repo": const { owner, repo, branch } = stepData.repo ?? {}; return Boolean(owner && repo && branch); case "token": return Boolean(stepData.token?.token); case "sync": default: return true; } }; useEffect(() => { setIsValid(isStepValid(step)); }, [step, stepData]); const previousStep = () => { setStep((currentStep) => { const currentIndex = STEPS.indexOf(currentStep); return STEPS[currentIndex - 1] ?? STEPS[0]; }); }; const nextStep = () => { setStep((currentStep) => { const currentIndex = STEPS.indexOf(currentStep); return STEPS[currentIndex + 1] ?? STEPS[STEPS.length - 1]; }); }; const currentStepComponent = () => { switch (step) { case "welcome": return ; case "repo": return ( updateStepData("repo", data)} /> ); case "token": return ( updateStepData("token", data)} /> ); case "sync": return ( updateStepData("sync", data)} /> ); case "first_sync": return ; } }; return (
{currentStepComponent()}
); }; export default OnBoardingComponent;