diff --git a/src/main.ts b/src/main.ts index 6a6aca4..a6a750e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,11 +4,11 @@ import { Platform, WorkspaceLeaf, normalizePath, + Notice, } from "obsidian"; import { GitHubSyncSettings, DEFAULT_SETTINGS } from "./settings/settings"; import GitHubSyncSettingsTab from "./settings/tab"; import SyncManager, { ConflictFile, ConflictResolution } from "./sync-manager"; -import { OnboardingDialog } from "./views/onboarding/view"; import Logger from "./logger"; import { ConflictsResolutionView, @@ -41,14 +41,13 @@ export default class GitHubSyncPlugin extends Plugin { private conflicts: ConflictFile[] = []; async onUserEnable() { - if (Platform.isMobile) { - // TODO: Implement onboarding for mobile - this.settings.firstStart = false; - this.saveSettings(); - return; - } - if (this.settings.firstStart) { - new OnboardingDialog(this).open(); + if ( + this.settings.githubToken === "" || + this.settings.githubOwner === "" || + this.settings.githubRepo === "" || + this.settings.githubBranch === "" + ) { + new Notice("Go to settings to configure syncing"); } } @@ -129,13 +128,30 @@ export default class GitHubSyncPlugin extends Plugin { name: "Sync with GitHub", repeatable: false, icon: "refresh-cw", - callback: async () => { - await this.syncManager.sync(); - this.updateStatusBarItem(); - }, + callback: this.sync.bind(this), }); } + async sync() { + if ( + this.settings.githubToken === "" || + this.settings.githubOwner === "" || + this.settings.githubRepo === "" || + this.settings.githubBranch === "" + ) { + new Notice("Sync plugin not configured"); + return; + } + if (this.settings.firstSync) { + await this.syncManager.firstSync(); + this.settings.firstSync = false; + this.saveSettings(); + } else { + await this.syncManager.sync(); + } + this.updateStatusBarItem(); + } + async onunload() { this.stopSyncInterval(); } @@ -198,10 +214,7 @@ export default class GitHubSyncPlugin extends Plugin { this.uploadModifiedFilesRibbonIcon = this.addRibbonIcon( "refresh-cw", "Sync with GitHub", - async () => { - await this.syncManager.sync(); - this.updateStatusBarItem(); - }, + this.sync.bind(this), ); } diff --git a/src/settings/settings.ts b/src/settings/settings.ts index bb7ae29..97dba70 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -1,5 +1,5 @@ export interface GitHubSyncSettings { - firstStart: boolean; + firstSync: boolean; githubToken: string; githubOwner: string; githubRepo: string; @@ -15,7 +15,7 @@ export interface GitHubSyncSettings { } export const DEFAULT_SETTINGS: GitHubSyncSettings = { - firstStart: true, + firstSync: true, githubToken: "", githubOwner: "", githubRepo: "", diff --git a/src/sync-manager.ts b/src/sync-manager.ts index 8697fc9..0efaef0 100644 --- a/src/sync-manager.ts +++ b/src/sync-manager.ts @@ -87,17 +87,24 @@ export default class SyncManager { return; } + const notice = new Notice("Syncing..."); this.syncing = true; try { - this.firstSyncImpl(); - } finally { - this.syncing = false; + await this.firstSyncImpl(); + // Shown only if sync doesn't fail + new Notice("Sync successful", 5000); + } catch (err) { + // Show the error to the user, it's not automatically dismissed to make sure + // the user sees it. + new Notice(`Error syncing. ${err}`); } + this.syncing = false; + notice.hide(); } private async firstSyncImpl() { await this.logger.info("Starting first sync"); - let repositoryIsBare = false; + let repositoryIsEmpty = false; let res: RepoContent; let files: { [key: string]: GetTreeResponseItem; @@ -108,16 +115,20 @@ export default class SyncManager { files = res.files; treeSha = res.sha; } catch (err) { - if (err.status !== 409) { + // 409 is returned in case the remote repo has been just created + // and contains no files. + // 404 instead is returned in case there are no files. + // Either way we can handle both by commiting a new empty manifest. + if (err.status !== 409 && err.status !== 404) { this.syncing = false; throw err; } // The repository is bare, meaning it has no tree, no commits and no branches - repositoryIsBare = true; + repositoryIsEmpty = true; } - if (repositoryIsBare) { - await this.logger.info("Remote repository is bare"); + if (repositoryIsEmpty) { + await this.logger.info("Remote repository is empty"); // Since the repository is completely empty we need to create a first commit. // We can't create that by going throught the normal sync process since the // API doesn't let us create a new tree when the repo is empty. @@ -126,7 +137,7 @@ export default class SyncManager { await this.client.createFile( `${this.vault.configDir}/${MANIFEST_FILE_NAME}`, "", - "Initial commit", + "First sync", ); // Now get the repo content again cause we know for sure it will return a // valid sha that we can use to create the first sync commit. @@ -135,14 +146,13 @@ export default class SyncManager { treeSha = res.sha; } - const remoteRepoIsEmpty = Object.keys(files).length === 0; const vaultIsEmpty = await this.vaultIsEmpty(); - if (!remoteRepoIsEmpty && !vaultIsEmpty) { + if (!repositoryIsEmpty && !vaultIsEmpty) { // Both have files, we can't sync, show error await this.logger.error("Both remote and local have files, can't sync"); throw new Error("Both remote and local have files, can't sync"); - } else if (remoteRepoIsEmpty || repositoryIsBare) { + } else if (repositoryIsEmpty) { // Remote has no files and no manifest, let's just upload whatever we have locally. // This is fine even if the vault is empty. // The most important thing at this point is that the remote manifest is created. diff --git a/src/views/onboarding/component.tsx b/src/views/onboarding/component.tsx deleted file mode 100644 index 65dd91e..0000000 --- a/src/views/onboarding/component.tsx +++ /dev/null @@ -1,556 +0,0 @@ -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 ( -
- 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. -
-- 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. -
-- 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 })} - /> -- 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.
-- If you want different configs for different vaults you can disable - config syncing. -
-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. -
- -- Now we can try and sync your vault with your repository. This might take - a few minutes depending on the number of files. -
-