mirror of
https://github.com/silvanocerza/github-gitless-sync.git
synced 2026-07-22 12:10:28 +00:00
296 lines
7.7 KiB
TypeScript
296 lines
7.7 KiB
TypeScript
import {
|
|
EventRef,
|
|
Plugin,
|
|
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 Logger from "./logger";
|
|
import {
|
|
ConflictsResolutionView,
|
|
CONFLICTS_RESOLUTION_VIEW_TYPE,
|
|
} from "./views/conflicts-resolution/view";
|
|
|
|
export default class GitHubSyncPlugin extends Plugin {
|
|
settings: GitHubSyncSettings;
|
|
syncManager: SyncManager;
|
|
logger: Logger;
|
|
|
|
statusBarItem: HTMLElement | null = null;
|
|
syncRibbonIcon: HTMLElement | null = null;
|
|
conflictsRibbonIcon: HTMLElement | null = null;
|
|
|
|
activeLeafChangeListener: EventRef | null = null;
|
|
vaultCreateListener: EventRef | null = null;
|
|
vaultModifyListener: EventRef | null = null;
|
|
|
|
// Called in ConflictResolutionView when the user solves all the conflicts.
|
|
// This is initialized every time we open the view to set new conflicts so
|
|
// we can notify the SyncManager that everything has been resolved and the sync
|
|
// process can continue on.
|
|
conflictsResolver: ((resolutions: ConflictResolution[]) => void) | null =
|
|
null;
|
|
|
|
// We keep track of the sync conflicts in here too in case the
|
|
// conflicts view must be rebuilt, or the user closes the view
|
|
// and it gets destroyed.
|
|
// By keeping them here we can recreate it easily.
|
|
private conflicts: ConflictFile[] = [];
|
|
|
|
async onUserEnable() {
|
|
if (
|
|
this.settings.githubToken === "" ||
|
|
this.settings.githubOwner === "" ||
|
|
this.settings.githubRepo === "" ||
|
|
this.settings.githubBranch === ""
|
|
) {
|
|
new Notice("Go to settings to configure syncing");
|
|
}
|
|
}
|
|
|
|
getConflictsView(): ConflictsResolutionView | null {
|
|
const leaves = this.app.workspace.getLeavesOfType(
|
|
CONFLICTS_RESOLUTION_VIEW_TYPE,
|
|
);
|
|
if (leaves.length === 0) {
|
|
return null;
|
|
}
|
|
return leaves[0].view as ConflictsResolutionView;
|
|
}
|
|
|
|
async activateView() {
|
|
const { workspace } = this.app;
|
|
let leaf: WorkspaceLeaf | null = null;
|
|
const leaves = workspace.getLeavesOfType(CONFLICTS_RESOLUTION_VIEW_TYPE);
|
|
if (leaves.length > 0) {
|
|
leaf = leaves[0];
|
|
} else {
|
|
leaf = workspace.getLeaf(false)!;
|
|
await leaf.setViewState({
|
|
type: CONFLICTS_RESOLUTION_VIEW_TYPE,
|
|
active: true,
|
|
});
|
|
}
|
|
workspace.revealLeaf(leaf);
|
|
}
|
|
|
|
async onload() {
|
|
await this.loadSettings();
|
|
|
|
this.logger = new Logger(this.app.vault, this.settings.enableLogging);
|
|
this.logger.init();
|
|
|
|
this.registerView(
|
|
CONFLICTS_RESOLUTION_VIEW_TYPE,
|
|
(leaf) => new ConflictsResolutionView(leaf, this, this.conflicts),
|
|
);
|
|
|
|
this.addSettingTab(new GitHubSyncSettingsTab(this.app, this));
|
|
|
|
this.syncManager = new SyncManager(
|
|
this.app.vault,
|
|
this.settings,
|
|
this.onConflicts.bind(this),
|
|
this.logger,
|
|
);
|
|
await this.syncManager.loadMetadata();
|
|
|
|
if (this.settings.syncStrategy == "interval") {
|
|
this.restartSyncInterval();
|
|
}
|
|
|
|
this.app.workspace.onLayoutReady(async () => {
|
|
// Create the events handling only after tha layout is ready to avoid
|
|
// getting spammed with create events.
|
|
// See the official Obsidian docs:
|
|
// https://docs.obsidian.md/Reference/TypeScript+API/Vault/on('create')
|
|
this.syncManager.startEventsListener(this);
|
|
|
|
// Load the ribbons after layout is ready so they're shown after the core
|
|
// buttons
|
|
if (this.settings.showStatusBarItem) {
|
|
this.showStatusBarItem();
|
|
}
|
|
|
|
if (this.settings.showConflictsRibbonButton) {
|
|
this.showConflictsRibbonIcon();
|
|
}
|
|
|
|
if (this.settings.showSyncRibbonButton) {
|
|
this.showSyncRibbonIcon();
|
|
}
|
|
});
|
|
|
|
this.addCommand({
|
|
id: "sync-files",
|
|
name: "Sync with GitHub",
|
|
repeatable: false,
|
|
icon: "refresh-cw",
|
|
callback: this.sync.bind(this),
|
|
});
|
|
|
|
this.addCommand({
|
|
id: "merge",
|
|
name: "Open sync conflicts view",
|
|
repeatable: false,
|
|
icon: "refresh-cw",
|
|
callback: this.openConflictsView.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();
|
|
}
|
|
|
|
showStatusBarItem() {
|
|
if (this.statusBarItem) {
|
|
return;
|
|
}
|
|
this.statusBarItem = this.addStatusBarItem();
|
|
|
|
if (!this.activeLeafChangeListener) {
|
|
this.activeLeafChangeListener = this.app.workspace.on(
|
|
"active-leaf-change",
|
|
() => this.updateStatusBarItem(),
|
|
);
|
|
}
|
|
if (!this.vaultCreateListener) {
|
|
this.vaultCreateListener = this.app.vault.on("create", () => {
|
|
this.updateStatusBarItem();
|
|
});
|
|
}
|
|
if (!this.vaultModifyListener) {
|
|
this.vaultModifyListener = this.app.vault.on("modify", () => {
|
|
this.updateStatusBarItem();
|
|
});
|
|
}
|
|
}
|
|
|
|
hideStatusBarItem() {
|
|
this.statusBarItem?.remove();
|
|
this.statusBarItem = null;
|
|
}
|
|
|
|
updateStatusBarItem() {
|
|
if (!this.statusBarItem) {
|
|
return;
|
|
}
|
|
const activeFile = this.app.workspace.getActiveFile();
|
|
if (!activeFile) {
|
|
return;
|
|
}
|
|
|
|
let state = "Unknown";
|
|
const fileData = this.syncManager.getFileMetadata(activeFile.path);
|
|
if (!fileData) {
|
|
state = "Untracked";
|
|
} else if (fileData.dirty) {
|
|
state = "Outdated";
|
|
} else if (!fileData.dirty) {
|
|
state = "Up to date";
|
|
}
|
|
|
|
this.statusBarItem.setText(`GitHub: ${state}`);
|
|
}
|
|
|
|
showSyncRibbonIcon() {
|
|
if (this.syncRibbonIcon) {
|
|
return;
|
|
}
|
|
this.syncRibbonIcon = this.addRibbonIcon(
|
|
"refresh-cw",
|
|
"Sync with GitHub",
|
|
this.sync.bind(this),
|
|
);
|
|
}
|
|
|
|
hideSyncRibbonIcon() {
|
|
this.syncRibbonIcon?.remove();
|
|
this.syncRibbonIcon = null;
|
|
}
|
|
|
|
showConflictsRibbonIcon() {
|
|
if (this.conflictsRibbonIcon) {
|
|
return;
|
|
}
|
|
this.conflictsRibbonIcon = this.addRibbonIcon(
|
|
"merge",
|
|
"Open sync conflicts view",
|
|
this.openConflictsView.bind(this),
|
|
);
|
|
}
|
|
|
|
hideConflictsRibbonIcon() {
|
|
this.conflictsRibbonIcon?.remove();
|
|
this.conflictsRibbonIcon = null;
|
|
}
|
|
|
|
async openConflictsView() {
|
|
await this.activateView();
|
|
this.getConflictsView()?.setConflictFiles(this.conflicts);
|
|
}
|
|
|
|
async onConflicts(conflicts: ConflictFile[]): Promise<ConflictResolution[]> {
|
|
this.conflicts = conflicts;
|
|
return await new Promise(async (resolve) => {
|
|
this.conflictsResolver = resolve;
|
|
await this.activateView();
|
|
this.getConflictsView()?.setConflictFiles(conflicts);
|
|
});
|
|
}
|
|
|
|
async loadSettings() {
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
|
}
|
|
|
|
async saveSettings() {
|
|
await this.saveData(this.settings);
|
|
}
|
|
|
|
// Proxy methods from sync manager to ease handling the interval
|
|
// when settings are changed
|
|
startSyncInterval() {
|
|
const intervalID = this.syncManager.startSyncInterval(
|
|
this.settings.syncInterval,
|
|
);
|
|
this.registerInterval(intervalID);
|
|
}
|
|
|
|
stopSyncInterval() {
|
|
this.syncManager.stopSyncInterval();
|
|
}
|
|
|
|
restartSyncInterval() {
|
|
this.syncManager.stopSyncInterval();
|
|
this.syncManager.startSyncInterval(this.settings.syncInterval);
|
|
}
|
|
|
|
async reset() {
|
|
this.settings = DEFAULT_SETTINGS;
|
|
this.saveSettings();
|
|
await this.syncManager.resetMetadata();
|
|
}
|
|
}
|