Skip to content

Commit

Permalink
Merge pull request #16 from oleeskild/feature/DeleteNotes
Browse files Browse the repository at this point in the history
Feature/delete notes
  • Loading branch information
oleeskild authored Mar 14, 2022
2 parents 438f118 + 12a0e84 commit de9c530
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 66 deletions.
3 changes: 2 additions & 1 deletion DigitalGardenSiteManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export default class DigitalGardenSiteManager implements IDigitalGardenSiteManag

async getNoteHashes(): Promise<{[key:string]: string}> {
const octokit = new Octokit({ auth: this.settings.githubToken });
const response = await octokit.request('GET /repos/{owner}/{repo}/git/trees/{tree_sha}?recursive=true', {
//Force the cache to be updated
const response = await octokit.request(`GET /repos/{owner}/{repo}/git/trees/{tree_sha}?recursive=${Math.ceil(Math.random()*1000)}`, {
owner: this.settings.githubUserName,
repo: this.settings.githubRepo,
tree_sha: 'main'
Expand Down
145 changes: 86 additions & 59 deletions PublishModal.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
import DigitalGardenSettings from "DigitalGardenSettings";
import { IDigitalGardenSiteManager } from "DigitalGardenSiteManager";
import { App, Modal, TFile } from "obsidian";
import { App, ButtonComponent, Modal} from "obsidian";
import { IPublisher } from "Publisher";
import { generateBlobHash } from "utils";
import { IPublishStatusManager } from "PublishStatusManager";

export class PublishModal {
modal: Modal;
siteManager: IDigitalGardenSiteManager;
settings: DigitalGardenSettings;
publishStatusManager: IPublishStatusManager;
publisher: IPublisher;

publishedContainer: HTMLElement;
changedContainer: HTMLElement;
deletedContainer: HTMLElement;
unpublishedContainer: HTMLElement;

constructor(app: App, siteManager: IDigitalGardenSiteManager, publisher: IPublisher,
settings: DigitalGardenSettings) {
progressContainer: HTMLElement;

constructor(app: App, publishStatusManager: IPublishStatusManager, publisher: IPublisher, settings: DigitalGardenSettings) {
this.modal = new Modal(app)
this.siteManager = siteManager;
this.settings = settings;
this.publishStatusManager = publishStatusManager;
this.publisher = publisher;

this.initialize();
}

createCollapsable(title: string): HTMLElement {
const toggleHeader = this.modal.contentEl.createEl("h3", { text: `➕️ ${title}`, attr: { class: "collapsable collapsed" } });
createCollapsable(title: string, buttonText: string, buttonCallback:()=>Promise<void>): HTMLElement {
const headerContainer = this.modal.contentEl.createEl("div", {attr: {style: "display: flex; justify-content: space-between; margin-bottom: 10px; align-items:center"}});
const toggleHeader = headerContainer.createEl("h3", { text: `➕️ ${title}`, attr: { class: "collapsable collapsed" } });
if(buttonText && buttonCallback){

const button = new ButtonComponent(headerContainer)
.setButtonText(buttonText)
.onClick(async () => {
button.setDisabled(true);
await buttonCallback();
button.setDisabled(false);
});
}

const toggledList = this.modal.contentEl.createEl("ul");
toggledList.hide();

toggleHeader.onClickEvent(() => {
headerContainer.onClickEvent(() => {
if (toggledList.isShown()) {
toggleHeader.textContent = `➕️ ${title}`;
toggledList.hide();
Expand All @@ -53,10 +65,66 @@ export class PublishModal {
this.modal.contentEl.addClass("digital-garden-publish-status-view");
this.modal.contentEl.createEl("h2", { text: "Publication Status" });

this.publishedContainer = this.createCollapsable("Published");
this.changedContainer = this.createCollapsable("Changed");
this.deletedContainer = this.createCollapsable("Deleted from vault");
this.unpublishedContainer = this.createCollapsable("Unpublished");
this.progressContainer = this.modal.contentEl.createEl("div", { attr: {style: "height: 30px;" } });

this.publishedContainer = this.createCollapsable("Published", null, null);
this.changedContainer = this.createCollapsable("Changed", "Update changed files", async () => {
const publishStatus = await this.publishStatusManager.getPublishStatus();
const changed = publishStatus.changedNotes;
let counter = 0;
for(const note of changed){
this.progressContainer.innerText = `⌛Publishing changed notes: ${++counter}/${changed.length}`;
await this.publisher.publish(note);
}

const publishedText = `✅ Published all changed notes: ${counter}/${changed.length}`;
this.progressContainer.innerText = publishedText;
setTimeout(() => {
if(this.progressContainer.innerText === publishedText){
this.progressContainer.innerText = "";
}
}, 5000)

await this.refreshView();
});

this.deletedContainer = this.createCollapsable("Deleted from vault", "Delete notes from garden", async () => {
const deletedNotes = await this.publishStatusManager.getDeletedNotePaths();
let counter = 0;
for(const note of deletedNotes){
this.progressContainer.innerText = `⌛Deleting Notes: ${++counter}/${deletedNotes.length}`;
await this.publisher.delete(note);
}

const deleteDoneText = `✅ Deleted all notes: ${counter}/${deletedNotes.length}`;
this.progressContainer.innerText = deleteDoneText;
setTimeout(() => {
if(this.progressContainer.innerText === deleteDoneText){
this.progressContainer.innerText = "";
}
}, 5000);

await this.refreshView();

});
this.unpublishedContainer = this.createCollapsable("Unpublished", "Publish unpublished notes", async () => {
const publishStatus = await this.publishStatusManager.getPublishStatus();
const unpublished = publishStatus.unpublishedNotes;
let counter = 0;
for(const note of unpublished){
this.progressContainer.innerText = `⌛Publishing unpublished notes: ${++counter}/${unpublished.length}`;
await this.publisher.publish(note);
}
const publishDoneText = `✅ Published all unpublished notes: ${counter}/${unpublished.length}`;
this.progressContainer.innerText = publishDoneText;
setTimeout(() => {
if(this.progressContainer.innerText === publishDoneText){
this.progressContainer.innerText = "";
}
}, 5000)
await this.refreshView();
});


this.modal.onOpen = () => this.populateWithNotes();
this.modal.onClose = () => this.clearView();
Expand All @@ -77,60 +145,19 @@ export class PublishModal {
}
}
async populateWithNotes() {
const publishStatus = await this.buildPublishStatus();
const publishStatus = await this.publishStatusManager.getPublishStatus();
publishStatus.publishedNotes.map(file => this.publishedContainer.createEl("li", { text: file.path }));
publishStatus.unpublishedNotes.map(file => this.unpublishedContainer.createEl("li", { text: file.path }));
publishStatus.changedNotes.map(file => this.changedContainer.createEl("li", { text: file.path }));
publishStatus.deletedNotePaths.map(path => this.deletedContainer.createEl("li", { text: path }));
}

async buildPublishStatus(): Promise<PublishStatus> {
const unpublishedNotes: Array<TFile> = [];
const publishedNotes: Array<TFile> = [];
const changedNotes: Array<TFile> = [];

const deletedNotePaths: Array<string> = [];

const remoteNoteHashes = await this.siteManager.getNoteHashes();
const marked = await this.publisher.getFilesMarkedForPublishing();

for (const file of marked) {
const content = await this.publisher.generateMarkdown(file);

const localHash = generateBlobHash(content);
const remoteHash = remoteNoteHashes[file.path];
if (!remoteHash) {
unpublishedNotes.push(file);
}
else if (remoteHash === localHash) {
publishedNotes.push(file);
}
else {
changedNotes.push(file);
}
}

Object.keys(remoteNoteHashes).forEach(key => {
if (!marked.find(f => f.path === key)) {
deletedNotePaths.push(key);
}
});

unpublishedNotes.sort((a, b) => a.path > b.path ? 1 : -1);
publishedNotes.sort((a, b) => a.path > b.path ? 1 : -1);
changedNotes.sort((a, b) => a.path > b.path ? 1 : -1);
deletedNotePaths.sort((a, b) => a > b ? 1 : -1);
return { unpublishedNotes, publishedNotes, changedNotes, deletedNotePaths };
private async refreshView(){
this.clearView();
await this.populateWithNotes();
}

open() {
this.modal.open();
}
}

interface PublishStatus{
unpublishedNotes: Array<TFile>;
publishedNotes: Array<TFile>;
changedNotes: Array<TFile>;
deletedNotePaths: Array<string>;
}
76 changes: 76 additions & 0 deletions PublishStatusManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { IDigitalGardenSiteManager } from "DigitalGardenSiteManager";
import { TFile } from "obsidian";
import { IPublisher } from "Publisher";
import { generateBlobHash } from "utils";

export default class PublishStatusManager implements IPublishStatusManager{
siteManager: IDigitalGardenSiteManager;
publisher: IPublisher;
constructor(siteManager: IDigitalGardenSiteManager, publisher:IPublisher ){
this.siteManager = siteManager;
this.publisher = publisher;
}

async getDeletedNotePaths(): Promise<Array<string>> {

const remoteNoteHashes = await this.siteManager.getNoteHashes();
const marked = await this.publisher.getFilesMarkedForPublishing();
return this.generateDeletedNotePaths(remoteNoteHashes, marked);
}

private generateDeletedNotePaths(remoteNoteHashes: {[key:string]: string}, marked: TFile[]): Array<string> {
const deletedNotePaths: Array<string> = [];
Object.keys(remoteNoteHashes).forEach(key => {
if (!marked.find(f => f.path === key)) {
deletedNotePaths.push(key);
}
});

return deletedNotePaths;
}
async getPublishStatus(): Promise<PublishStatus> {
const unpublishedNotes: Array<TFile> = [];
const publishedNotes: Array<TFile> = [];
const changedNotes: Array<TFile> = [];


const remoteNoteHashes = await this.siteManager.getNoteHashes();
const marked = await this.publisher.getFilesMarkedForPublishing();

for (const file of marked) {
const content = await this.publisher.generateMarkdown(file);

const localHash = generateBlobHash(content);
const remoteHash = remoteNoteHashes[file.path];
if (!remoteHash) {
unpublishedNotes.push(file);
}
else if (remoteHash === localHash) {
publishedNotes.push(file);
}
else {
changedNotes.push(file);
}
}

const deletedNotePaths = this.generateDeletedNotePaths(remoteNoteHashes, marked);

unpublishedNotes.sort((a, b) => a.path > b.path ? 1 : -1);
publishedNotes.sort((a, b) => a.path > b.path ? 1 : -1);
changedNotes.sort((a, b) => a.path > b.path ? 1 : -1);
deletedNotePaths.sort((a, b) => a > b ? 1 : -1);
return { unpublishedNotes, publishedNotes, changedNotes, deletedNotePaths };
}
}

export interface PublishStatus{
unpublishedNotes: Array<TFile>;
publishedNotes: Array<TFile>;
changedNotes: Array<TFile>;
deletedNotePaths: Array<string>;
}

export interface IPublishStatusManager{
getPublishStatus(): Promise<PublishStatus>;
getDeletedNotePaths(): Promise<Array<string>>;
}
52 changes: 52 additions & 0 deletions Publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { excaliDrawBundle, excalidraw } from "./constants";

export interface IPublisher {
publish(file: TFile): Promise<boolean>;
delete(vaultFilePath: string): Promise<boolean>;
getFilesMarkedForPublishing(): Promise<TFile[]>;
generateMarkdown(file: TFile): Promise<string>;
}
Expand Down Expand Up @@ -40,6 +41,57 @@ export default class Publisher {
return filesToPublish;
}

async delete(vaultFilePath: string): Promise<boolean> {
if (!this.settings.githubRepo) {
new Notice("Config error: You need to define a GitHub repo in the plugin settings");
throw {};
}
if (!this.settings.githubUserName) {
new Notice("Config error: You need to define a GitHub Username in the plugin settings");
throw {};
}
if (!this.settings.githubToken) {
new Notice("Config error: You need to define a GitHub Token in the plugin settings");
throw {};
}

const octokit = new Octokit({ auth: this.settings.githubToken });
const path = `src/site/notes/${vaultFilePath}`;

const payload = {
owner: this.settings.githubUserName,
repo: this.settings.githubRepo,
path,
message: `Delete note ${vaultFilePath}`,
sha: ''
};

try {
const response = await octokit.request('GET /repos/{owner}/{repo}/contents/{path}', {
owner: this.settings.githubUserName,
repo: this.settings.githubRepo,
path
});
if (response.status === 200 && response.data.type === "file") {
payload.sha = response.data.sha;
}
} catch (e) {
console.log(e)
return false;
}



try {
const response = await octokit.request('DELETE /repos/{owner}/{repo}/contents/{path}', payload);
} catch (e) {
console.log(e)
return false
}
return true;
}


async publish(file: TFile): Promise<boolean> {
if (!vallidatePublishFrontmatter(this.metadataCache.getCache(file.path).frontmatter)) {
return false;
Expand Down
Loading

0 comments on commit de9c530

Please sign in to comment.