This commit is contained in:
		
							parent
							
								
									6d09ce3e39
								
							
						
					
					
						commit
						a2fbc15948
					
				
							
								
								
									
										299
									
								
								main.ts
								
								
								
								
							
							
						
						
									
										299
									
								
								main.ts
								
								
								
								
							| 
						 | 
				
			
			@ -1,134 +1,191 @@
 | 
			
		|||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
 | 
			
		||||
import { Plugin, PluginSettingTab, Setting, Notice, App } from "obsidian";
 | 
			
		||||
 | 
			
		||||
// Remember to rename these classes and interfaces!
 | 
			
		||||
 | 
			
		||||
interface MyPluginSettings {
 | 
			
		||||
	mySetting: string;
 | 
			
		||||
interface GitHubSyncSettings {
 | 
			
		||||
    repoOwner: string;
 | 
			
		||||
    repoName: string;
 | 
			
		||||
    branch: string;
 | 
			
		||||
    authToken: string;
 | 
			
		||||
    folderPath: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DEFAULT_SETTINGS: MyPluginSettings = {
 | 
			
		||||
	mySetting: 'default'
 | 
			
		||||
const DEFAULT_SETTINGS: GitHubSyncSettings = {
 | 
			
		||||
    repoOwner: "",
 | 
			
		||||
    repoName: "",
 | 
			
		||||
    branch: "main",
 | 
			
		||||
    authToken: "",
 | 
			
		||||
    folderPath: "/",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default class GitHubSyncPlugin extends Plugin {
 | 
			
		||||
    settings: GitHubSyncSettings;
 | 
			
		||||
 | 
			
		||||
    async onload() {
 | 
			
		||||
        await this.loadSettings();
 | 
			
		||||
        this.addSettingTab(new GitHubSyncSettingTab(this.app, this));
 | 
			
		||||
 | 
			
		||||
        this.addCommand({
 | 
			
		||||
            id: "github-pull",
 | 
			
		||||
            name: "Pull from GitHub",
 | 
			
		||||
            callback: () => this.pullFromGitHub(),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.addCommand({
 | 
			
		||||
            id: "github-add-commit-push",
 | 
			
		||||
            name: "Commit & Push",
 | 
			
		||||
            callback: () => this.commitAndPush(),
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async pullFromGitHub() {
 | 
			
		||||
        try {
 | 
			
		||||
            const response = await fetch(
 | 
			
		||||
                `https://api.github.com/repos/${this.settings.repoOwner}/${this.settings.repoName}/contents/${this.settings.folderPath}?ref=${this.settings.branch}`,
 | 
			
		||||
                {
 | 
			
		||||
                    headers: { Authorization: `token ${this.settings.authToken}` },
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (!response.ok) throw new Error(`Failed to fetch files: ${response.statusText}`);
 | 
			
		||||
 | 
			
		||||
            const files = await response.json();
 | 
			
		||||
            for (const file of files) {
 | 
			
		||||
                if (file.type === "file") {
 | 
			
		||||
                    const contentResponse = await fetch(file.download_url);
 | 
			
		||||
                    const content = await contentResponse.text();
 | 
			
		||||
                    const filePath = `${this.settings.folderPath}/${file.name}`;
 | 
			
		||||
                    await this.app.vault.adapter.write(filePath, content);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            new Notice("Pull completed successfully!");
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.error("GitHub Pull Error:", error);
 | 
			
		||||
            new Notice("GitHub Pull failed. Check console.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async commitAndPush() {
 | 
			
		||||
        try {
 | 
			
		||||
            const files = await this.app.vault.adapter.list(this.settings.folderPath);
 | 
			
		||||
            for (const filePath of files.files) {
 | 
			
		||||
                if (!filePath.startsWith(this.settings.folderPath)) continue;
 | 
			
		||||
 | 
			
		||||
                const content = await this.app.vault.adapter.read(filePath);
 | 
			
		||||
                const base64Content = btoa(unescape(encodeURIComponent(content)));
 | 
			
		||||
 | 
			
		||||
                // Fetch the existing file metadata (needed for updating a file on GitHub)
 | 
			
		||||
                const metadataResponse = await fetch(
 | 
			
		||||
                    `https://api.github.com/repos/${this.settings.repoOwner}/${this.settings.repoName}/contents/${filePath}`,
 | 
			
		||||
                    {
 | 
			
		||||
                        headers: {
 | 
			
		||||
                            Authorization: `token ${this.settings.authToken}`,
 | 
			
		||||
                        },
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                let sha = "";
 | 
			
		||||
                if (metadataResponse.ok) {
 | 
			
		||||
                    const metadata = await metadataResponse.json();
 | 
			
		||||
                    sha = metadata.sha; // GitHub requires the SHA of existing files
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Commit and push
 | 
			
		||||
                const response = await fetch(
 | 
			
		||||
                    `https://api.github.com/repos/${this.settings.repoOwner}/${this.settings.repoName}/contents/${filePath}`,
 | 
			
		||||
                    {
 | 
			
		||||
                        method: "PUT",
 | 
			
		||||
                        headers: {
 | 
			
		||||
                            Authorization: `token ${this.settings.authToken}`,
 | 
			
		||||
                            "Content-Type": "application/json",
 | 
			
		||||
                        },
 | 
			
		||||
                        body: JSON.stringify({
 | 
			
		||||
                            message: "#",
 | 
			
		||||
                            content: base64Content,
 | 
			
		||||
                            branch: this.settings.branch,
 | 
			
		||||
                            sha: sha || undefined, // If file is new, omit SHA
 | 
			
		||||
                        }),
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                if (!response.ok) throw new Error(`Failed to commit: ${response.statusText}`);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            new Notice("Commit & Push successful!");
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.error("GitHub Commit & Push Error:", error);
 | 
			
		||||
            new Notice("Commit & Push failed. Check console.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async loadSettings() {
 | 
			
		||||
        this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async saveSettings() {
 | 
			
		||||
        await this.saveData(this.settings);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class MyPlugin extends Plugin {
 | 
			
		||||
	settings: MyPluginSettings;
 | 
			
		||||
class GitHubSyncSettingTab extends PluginSettingTab {
 | 
			
		||||
    plugin: GitHubSyncPlugin;
 | 
			
		||||
 | 
			
		||||
	async onload() {
 | 
			
		||||
		await this.loadSettings();
 | 
			
		||||
    constructor(app: App, plugin: GitHubSyncPlugin) {
 | 
			
		||||
        super(app, plugin);
 | 
			
		||||
        this.plugin = plugin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
		// This creates an icon in the left ribbon.
 | 
			
		||||
		const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
 | 
			
		||||
			// Called when the user clicks the icon.
 | 
			
		||||
			new Notice('This is a notice!');
 | 
			
		||||
		});
 | 
			
		||||
		// Perform additional things with the ribbon
 | 
			
		||||
		ribbonIconEl.addClass('my-plugin-ribbon-class');
 | 
			
		||||
    display(): void {
 | 
			
		||||
        const { containerEl } = this;
 | 
			
		||||
        containerEl.empty();
 | 
			
		||||
 | 
			
		||||
		// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
 | 
			
		||||
		const statusBarItemEl = this.addStatusBarItem();
 | 
			
		||||
		statusBarItemEl.setText('Status Bar Text');
 | 
			
		||||
        new Setting(containerEl)
 | 
			
		||||
            .setName("GitHub Repository Owner")
 | 
			
		||||
            .setDesc("Enter your GitHub username or organization.")
 | 
			
		||||
            .addText(text => text
 | 
			
		||||
                .setValue(this.plugin.settings.repoOwner)
 | 
			
		||||
                .onChange(async (value) => {
 | 
			
		||||
                    this.plugin.settings.repoOwner = value;
 | 
			
		||||
                    await this.plugin.saveSettings();
 | 
			
		||||
                }));
 | 
			
		||||
 | 
			
		||||
		// This adds a simple command that can be triggered anywhere
 | 
			
		||||
		this.addCommand({
 | 
			
		||||
			id: 'open-sample-modal-simple',
 | 
			
		||||
			name: 'Open sample modal (simple)',
 | 
			
		||||
			callback: () => {
 | 
			
		||||
				new SampleModal(this.app).open();
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		// This adds an editor command that can perform some operation on the current editor instance
 | 
			
		||||
		this.addCommand({
 | 
			
		||||
			id: 'sample-editor-command',
 | 
			
		||||
			name: 'Sample editor command',
 | 
			
		||||
			editorCallback: (editor: Editor, view: MarkdownView) => {
 | 
			
		||||
				console.log(editor.getSelection());
 | 
			
		||||
				editor.replaceSelection('Sample Editor Command');
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		// This adds a complex command that can check whether the current state of the app allows execution of the command
 | 
			
		||||
		this.addCommand({
 | 
			
		||||
			id: 'open-sample-modal-complex',
 | 
			
		||||
			name: 'Open sample modal (complex)',
 | 
			
		||||
			checkCallback: (checking: boolean) => {
 | 
			
		||||
				// Conditions to check
 | 
			
		||||
				const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
 | 
			
		||||
				if (markdownView) {
 | 
			
		||||
					// If checking is true, we're simply "checking" if the command can be run.
 | 
			
		||||
					// If checking is false, then we want to actually perform the operation.
 | 
			
		||||
					if (!checking) {
 | 
			
		||||
						new SampleModal(this.app).open();
 | 
			
		||||
					}
 | 
			
		||||
        new Setting(containerEl)
 | 
			
		||||
            .setName("GitHub Repository Name")
 | 
			
		||||
            .setDesc("Enter your GitHub repository name.")
 | 
			
		||||
            .addText(text => text
 | 
			
		||||
                .setValue(this.plugin.settings.repoName)
 | 
			
		||||
                .onChange(async (value) => {
 | 
			
		||||
                    this.plugin.settings.repoName = value;
 | 
			
		||||
                    await this.plugin.saveSettings();
 | 
			
		||||
                }));
 | 
			
		||||
 | 
			
		||||
					// This command will only show up in Command Palette when the check function returns true
 | 
			
		||||
					return true;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
        new Setting(containerEl)
 | 
			
		||||
            .setName("Branch Name")
 | 
			
		||||
            .setDesc("Branch to sync with (default: main).")
 | 
			
		||||
            .addText(text => text
 | 
			
		||||
                .setValue(this.plugin.settings.branch)
 | 
			
		||||
                .onChange(async (value) => {
 | 
			
		||||
                    this.plugin.settings.branch = value;
 | 
			
		||||
                    await this.plugin.saveSettings();
 | 
			
		||||
                }));
 | 
			
		||||
 | 
			
		||||
		// This adds a settings tab so the user can configure various aspects of the plugin
 | 
			
		||||
		this.addSettingTab(new SampleSettingTab(this.app, this));
 | 
			
		||||
        new Setting(containerEl)
 | 
			
		||||
            .setName("GitHub Auth Token")
 | 
			
		||||
            .setDesc("Enter your GitHub authentication token (hidden after input).")
 | 
			
		||||
            .addText(text => text
 | 
			
		||||
                .setValue(this.plugin.settings.authToken ? "********" : "")
 | 
			
		||||
                .onChange(async (value) => {
 | 
			
		||||
                    this.plugin.settings.authToken = value;
 | 
			
		||||
                    await this.plugin.saveSettings();
 | 
			
		||||
                }));
 | 
			
		||||
 | 
			
		||||
		// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
 | 
			
		||||
		// Using this function will automatically remove the event listener when this plugin is disabled.
 | 
			
		||||
		this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
 | 
			
		||||
			console.log('click', evt);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
 | 
			
		||||
		this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onunload() {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async loadSettings() {
 | 
			
		||||
		this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async saveSettings() {
 | 
			
		||||
		await this.saveData(this.settings);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SampleModal extends Modal {
 | 
			
		||||
	constructor(app: App) {
 | 
			
		||||
		super(app);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onOpen() {
 | 
			
		||||
		const {contentEl} = this;
 | 
			
		||||
		contentEl.setText('Woah!');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onClose() {
 | 
			
		||||
		const {contentEl} = this;
 | 
			
		||||
		contentEl.empty();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SampleSettingTab extends PluginSettingTab {
 | 
			
		||||
	plugin: MyPlugin;
 | 
			
		||||
 | 
			
		||||
	constructor(app: App, plugin: MyPlugin) {
 | 
			
		||||
		super(app, plugin);
 | 
			
		||||
		this.plugin = plugin;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	display(): void {
 | 
			
		||||
		const {containerEl} = this;
 | 
			
		||||
 | 
			
		||||
		containerEl.empty();
 | 
			
		||||
 | 
			
		||||
		new Setting(containerEl)
 | 
			
		||||
			.setName('Setting #1')
 | 
			
		||||
			.setDesc('It\'s a secret')
 | 
			
		||||
			.addText(text => text
 | 
			
		||||
				.setPlaceholder('Enter your secret')
 | 
			
		||||
				.setValue(this.plugin.settings.mySetting)
 | 
			
		||||
				.onChange(async (value) => {
 | 
			
		||||
					this.plugin.settings.mySetting = value;
 | 
			
		||||
					await this.plugin.saveSettings();
 | 
			
		||||
				}));
 | 
			
		||||
	}
 | 
			
		||||
        new Setting(containerEl)
 | 
			
		||||
            .setName("Folder Path")
 | 
			
		||||
            .setDesc("Enter the folder path in the repository to sync with.")
 | 
			
		||||
            .addText(text => text
 | 
			
		||||
                .setValue(this.plugin.settings.folderPath)
 | 
			
		||||
                .onChange(async (value) => {
 | 
			
		||||
                    this.plugin.settings.folderPath = value;
 | 
			
		||||
                    await this.plugin.saveSettings();
 | 
			
		||||
                }));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue