From a0d0fa7451fc0ede7dc26368ddb7c30f85edbad0 Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Wed, 17 Jan 2024 21:50:05 +0100 Subject: [PATCH 01/10] feat(assets): implement basic asset support --- src/main.ts | 50 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/main.ts b/src/main.ts index bfb383f..fe09455 100644 --- a/src/main.ts +++ b/src/main.ts @@ -103,10 +103,8 @@ export default class RelayMdPLugin extends Plugin { folder.split('/').reduce( (directories, directory) => { directories += `${directory}/`; - try { + if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { this.app.vault.createFolder(directories); - } catch (e) { - // do nothing } return directories; }, @@ -152,7 +150,7 @@ export default class RelayMdPLugin extends Plugin { if (team != "_") full_path_to_file += team + "/"; full_path_to_file += topic; - this.upsert_document(full_path_to_file, filename, body); + this.upsert_document(normalizePath(full_path_to_file), filename, body); } } catch (e) { console.log(JSON.stringify(e)); @@ -221,7 +219,7 @@ export default class RelayMdPLugin extends Plugin { } // Do we already have an id maybe? - const id = metadata.frontmatter["relay-document"] + let id = metadata.frontmatter["relay-document"] // Get the content of the file const body = await this.app.vault.cachedRead(activeFile); @@ -233,7 +231,7 @@ export default class RelayMdPLugin extends Plugin { method = "PUT" url = this.settings.base_uri + '/v1/doc/' + id; } - console.log("Sending API request to api.relay.md (" + method + ")"); + console.log("Sending API request to " + this.settings.base_uri + " (" + method + ")"); const options: RequestUrlParam = { url: url, method: method, @@ -256,15 +254,49 @@ export default class RelayMdPLugin extends Plugin { // allows the backend to decide if a new document should be // created, or the old one should be updated, depending on // permissions. - const doc_id = response.json.result["relay-document"]; + id = response.json.result["relay-document"]; // update document to contain new document id - app.fileManager.processFrontMatter(activeFile, (frontmatter) => { - frontmatter["relay-document"] = doc_id; + this.app.fileManager.processFrontMatter(activeFile, (frontmatter) => { + frontmatter["relay-document"] = id; }); } catch (e) { console.log(e); } + + // Now try upload the embeds + if (!metadata.embeds) { + return; + } + metadata.embeds.map(async (item: any) => { + let file = this.app.vault.getAbstractFileByPath(item.link); + console.log("Uploading attachment: " + item.link); + if (file instanceof TFile) { + this.upload_asset(id, item.link, file); + } + }); + } + + async upload_asset(id: string, link: string, file: TFile) { + this.app.vault.read(file).then(async (content) => { + const options: RequestUrlParam = { + url: this.settings.base_uri + '/v1/assets/' + id, + method: "POST", + headers: { + 'X-API-KEY': this.settings.api_key, + 'Content-Type': 'application/octet-stream', + 'x-relay-filename': link + }, + body: content, + } + const response: RequestUrlResponse = await requestUrl(options); + if (response.json.error) { + console.error("API server returned an error"); + new Notice("Relay.md returned an error: " + response.json.error.message); + return; + } + }); } + } class RelayMDSettingTab extends PluginSettingTab { From 58b8618f739e8961cad78f9600f62ed1f8529305 Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Thu, 18 Jan 2024 20:36:42 +0100 Subject: [PATCH 02/10] fix: transfer assets as binary files --- src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index fe09455..04d8f98 100644 --- a/src/main.ts +++ b/src/main.ts @@ -277,7 +277,7 @@ export default class RelayMdPLugin extends Plugin { } async upload_asset(id: string, link: string, file: TFile) { - this.app.vault.read(file).then(async (content) => { + this.app.vault.readBinary(file).then(async (content) => { const options: RequestUrlParam = { url: this.settings.base_uri + '/v1/assets/' + id, method: "POST", From ed5e11912fbd6d2c2e5569d2640c5383388fbd2a Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Fri, 19 Jan 2024 21:00:04 +0100 Subject: [PATCH 03/10] chore: download attachments --- src/main.ts | 156 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 126 insertions(+), 30 deletions(-) diff --git a/src/main.ts b/src/main.ts index 04d8f98..3d180b9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,6 +13,13 @@ import { normalizePath } from 'obsidian'; +interface Embed { + checksum_sha256: string; + filename: string; + filesize: number; + id: string; +} + interface RelayMDSettings { base_uri: string; api_key: string; @@ -97,19 +104,23 @@ export default class RelayMdPLugin extends Plugin { await this.saveData(this.settings); } + make_directory_recursively(folder: string) { + folder.split('/').reduce( + (directories, directory) => { + directories += `${directory}/`; + if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { + this.app.vault.createFolder(directories); + } + return directories; + }, + '', + ); + } + async upsert_document(folder: string, filename: string, body: string) { - // Does the folder exist? If not, create it "recusrively" + // Does the folder exist? If not, create it "recursively" if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { - folder.split('/').reduce( - (directories, directory) => { - directories += `${directory}/`; - if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { - this.app.vault.createFolder(directories); - } - return directories; - }, - '', - ); + this.make_directory_recursively(folder); } const full_path_to_file = normalizePath(folder + "/" + filename); const fileRef = this.app.vault.getAbstractFileByPath(full_path_to_file); @@ -124,6 +135,36 @@ export default class RelayMdPLugin extends Plugin { } async load_document(id: string) { + // We first obtain the metdata of the document using application/json + const options: RequestUrlParam = { + url: this.settings.base_uri + '/v1/doc/' + id, + method: 'GET', + headers: { + 'X-API-KEY': this.settings.api_key, + 'Content-Type': 'application/json' + }, + } + const response: RequestUrlResponse = await requestUrl(options); + if (response.json.error) { + console.error("API server returned an error"); + new Notice("Relay.md returned an error: " + response.json.error.message); + return; + } + + const result = response.json.result; + const embeds = result.embeds; + if (embeds) { + embeds.map((embed: Embed) => { + this.load_embeds(embed); + }); + } + + // Load the document body, we are going to use text/markdown here + //this.load_document_body(id); + + // + } + async load_document_body(id: string) { const options: RequestUrlParam = { url: this.settings.base_uri + '/v1/doc/' + id, method: 'GET', @@ -148,16 +189,70 @@ export default class RelayMdPLugin extends Plugin { let full_path_to_file: string = this.settings.vault_base_folder + "/"; // We ignore the "_" team which is a "global" team if (team != "_") - full_path_to_file += team + "/"; + full_path_to_file += team; full_path_to_file += topic; this.upsert_document(normalizePath(full_path_to_file), filename, body); } + } catch (e) { console.log(JSON.stringify(e)); throw e; } } + async load_embeds(embed: Embed) { + // TODO: think about this for a bit, it allows to update other peoples file by just using the same filename + // On the other hand, if we were to put the team name into the path, we end up (potentially) having tos + // duplicate the file into multiple team's attachment folder. Hmm + const folder = this.settings.vault_base_folder + "/" + "_attachments" + if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { + this.make_directory_recursively(folder); + } + const path = normalizePath(folder + "/" + embed.filename); + const file = this.app.vault.getAbstractFileByPath(path); + if (!(file instanceof TFile)) { + const content = await this.get_embed_binady(embed); + this.app.vault.createBinary(path, content); + console.log("Binary file " + path + " has been created!"); + } else { + console.log("file exists, checking hash"); + const local_content = await this.app.vault.readBinary(file); + const checksum = await this.calculateSHA256Checksum(local_content); + console.log(checksum, embed.checksum_sha256); + if (checksum != embed.checksum_sha256) { + const content = await this.get_embed_binady(embed); + this.app.vault.modifyBinary(file, content); + console.log("Binary file " + path + " has been updated!"); + } + } + } + + async get_embed_binady(embed: Embed) { + const options: RequestUrlParam = { + url: this.settings.base_uri + '/v1/assets/' + embed.id, + method: 'GET', + headers: { + 'X-API-KEY': this.settings.api_key, + 'Content-Type': 'application/octet-stream' + }, + } + const response: RequestUrlResponse = await requestUrl(options); + return response.arrayBuffer; + } + + async calculateSHA256Checksum(buffer: ArrayBuffer): Promise { + const data = new Uint8Array(buffer); + + try { + const hashBuffer = await window.crypto.subtle.digest('SHA-256', data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const checksum = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); + return checksum; + } catch (error) { + throw new Error(`Error calculating SHA-256 checksum: ${error}`); + } + } + async get_recent_documents() { const options: RequestUrlParam = { url: this.settings.base_uri + '/v1/docs', @@ -248,6 +343,7 @@ export default class RelayMdPLugin extends Plugin { return; } + console.log("Successfully sent to " + this.settings.base_uri); try { // WARNING: This overwrites an existing relay-document id potentially, // depending on how the server responds. It's a feature, and not a bug and @@ -277,24 +373,24 @@ export default class RelayMdPLugin extends Plugin { } async upload_asset(id: string, link: string, file: TFile) { - this.app.vault.readBinary(file).then(async (content) => { - const options: RequestUrlParam = { - url: this.settings.base_uri + '/v1/assets/' + id, - method: "POST", - headers: { - 'X-API-KEY': this.settings.api_key, - 'Content-Type': 'application/octet-stream', - 'x-relay-filename': link - }, - body: content, - } - const response: RequestUrlResponse = await requestUrl(options); - if (response.json.error) { - console.error("API server returned an error"); - new Notice("Relay.md returned an error: " + response.json.error.message); - return; - } - }); + const content = await this.app.vault.readBinary(file); + const options: RequestUrlParam = { + url: this.settings.base_uri + '/v1/assets/' + id, + method: "POST", + headers: { + 'X-API-KEY': this.settings.api_key, + 'Content-Type': 'application/octet-stream', + 'x-relay-filename': link + }, + body: content, + } + const response: RequestUrlResponse = await requestUrl(options); + if (response.json.error) { + console.error("API server returned an error"); + new Notice("Relay.md returned an error: " + response.json.error.message); + return; + } + console.log("Successfully uploaded " + file.path); } } From 5cef56b4add37d2f0f0abb21d5b6fa75232a4d4b Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Fri, 19 Jan 2024 21:01:46 +0100 Subject: [PATCH 04/10] chore: a remark on updating docs and its assets --- src/main.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.ts b/src/main.ts index 3d180b9..4c9dd31 100644 --- a/src/main.ts +++ b/src/main.ts @@ -325,6 +325,10 @@ export default class RelayMdPLugin extends Plugin { if (id) { method = "PUT" url = this.settings.base_uri + '/v1/doc/' + id; + + // In this case, we also need to check if we maybe have to update the assets we originally had in the document as well + // TODO: this also means that we may have to delete embeds that have been removed from the note since it has been last + // sent to the API } console.log("Sending API request to " + this.settings.base_uri + " (" + method + ")"); const options: RequestUrlParam = { From 759769dd43faa3ec889ad7bb3bd3063f191870e5 Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Mon, 22 Jan 2024 13:41:45 +0100 Subject: [PATCH 05/10] feat: allow to update a file located somewhere else --- src/main.ts | 86 +++++++++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/src/main.ts b/src/main.ts index 4c9dd31..142ab54 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,6 +13,7 @@ import { normalizePath } from 'obsidian'; + interface Embed { checksum_sha256: string; filename: string; @@ -117,20 +118,14 @@ export default class RelayMdPLugin extends Plugin { ); } - async upsert_document(folder: string, filename: string, body: string) { - // Does the folder exist? If not, create it "recursively" - if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { - this.make_directory_recursively(folder); - } - const full_path_to_file = normalizePath(folder + "/" + filename); - const fileRef = this.app.vault.getAbstractFileByPath(full_path_to_file); + async upsert_document(path: string, body: string) { + const fileRef = this.app.vault.getAbstractFileByPath(path); if (fileRef === undefined || fileRef === null) { - await this.app.vault.create(full_path_to_file, body); - new Notice('File ' + full_path_to_file + ' has been created!'); + await this.app.vault.create(path, body); + new Notice('File ' + path + ' has been created!'); } else if (fileRef instanceof TFile) { - // TODO: consider storing multiple versions of a file here! await this.app.vault.modify(fileRef, body); - new Notice('File ' + full_path_to_file + ' has been modified!'); + new Notice('File ' + path + ' has been modified!'); } } @@ -160,11 +155,14 @@ export default class RelayMdPLugin extends Plugin { } // Load the document body, we are going to use text/markdown here - //this.load_document_body(id); - - // + this.load_document_body(result); } - async load_document_body(id: string) { + + async load_document_body(result: any) { + const id: string = result["relay-document"]; + const filename: string = result["relay-filename"]; + const relay_to: Array = result["relay-to"] + const options: RequestUrlParam = { url: this.settings.base_uri + '/v1/doc/' + id, method: 'GET', @@ -176,27 +174,31 @@ export default class RelayMdPLugin extends Plugin { const response: RequestUrlResponse = await requestUrl(options); // we do not look for error in json response as we do not get a json // response but markdown in the body - try { - const filename: string = response.headers["x-relay-filename"]; - const relay_to = JSON.parse(response.headers["x-relay-to"]); - const body: string = response.text; + const body: string = response.text; + // Let's first see if we maybe have the document already somewhere in the fault + const located_documents = await this.locate_document(id); + if (located_documents) { + located_documents.forEach((located_document: TFile) => { + this.upsert_document(located_document.path, body); + }) + } else { // Loop through team/topics for (const to of relay_to) { const tos: Array = to.split("@", 2); const team: string = tos[1]; const topic: string = tos[0]; - let full_path_to_file: string = this.settings.vault_base_folder + "/"; + let folder: string = this.settings.vault_base_folder + "/"; // We ignore the "_" team which is a "global" team if (team != "_") - full_path_to_file += team; - full_path_to_file += topic; - this.upsert_document(normalizePath(full_path_to_file), filename, body); + folder += team; + folder += topic; + // Does the folder exist? If not, create it "recursively" + if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { + this.make_directory_recursively(folder); + } + this.upsert_document(normalizePath(folder + "/" + filename), body); } - - } catch (e) { - console.log(JSON.stringify(e)); - throw e; } } @@ -215,10 +217,8 @@ export default class RelayMdPLugin extends Plugin { this.app.vault.createBinary(path, content); console.log("Binary file " + path + " has been created!"); } else { - console.log("file exists, checking hash"); const local_content = await this.app.vault.readBinary(file); const checksum = await this.calculateSHA256Checksum(local_content); - console.log(checksum, embed.checksum_sha256); if (checksum != embed.checksum_sha256) { const content = await this.get_embed_binady(embed); this.app.vault.modifyBinary(file, content); @@ -301,18 +301,11 @@ export default class RelayMdPLugin extends Plugin { } // We only share if relay-to is provided, even if its empty - if (!("relay-to" in metadata.frontmatter)) { + if (!("relay-to" in metadata.frontmatter) + || !(metadata.frontmatter["relay-to"])) { return } - // File is in the shared folder, no re-sharing - if (activeFile.path.startsWith(this.settings.vault_base_folder + "/")) { - console.warn( - "Files from the relay.md base folder cannot be sent." - ); - return; - } - // Do we already have an id maybe? let id = metadata.frontmatter["relay-document"] @@ -397,6 +390,21 @@ export default class RelayMdPLugin extends Plugin { console.log("Successfully uploaded " + file.path); } + async locate_document(document_id: string) { + const files = this.app.vault.getMarkdownFiles(); + let located_files: Array = []; + for (let i = 0; i < files.length; i++) { + const activeFile = files[i]; + const metadata = this.app.metadataCache.getCache(activeFile.path); + if (!metadata || !metadata.frontmatter) { + return; + } + if (metadata.frontmatter["relay-document"] == document_id) + located_files.push(activeFile); + } + return located_files; + } + } class RelayMDSettingTab extends PluginSettingTab { @@ -423,7 +431,7 @@ class RelayMDSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); })); - if (this.plugin.settings.api_key === DEFAULT_SETTINGS.api_key) { + if (!(this.plugin.settings.api_key) || this.plugin.settings.api_key === DEFAULT_SETTINGS.api_key) { new Setting(containerEl) .setName('API Access') .setDesc('Authenticate against the relay.md API') From afc0296f621378e45c20a3933dad439f00b639a8 Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Mon, 22 Jan 2024 21:37:34 +0100 Subject: [PATCH 06/10] chore: allow update of timer in settings --- src/main.ts | 59 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/src/main.ts b/src/main.ts index 142ab54..8d114d8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -25,16 +25,19 @@ interface RelayMDSettings { base_uri: string; api_key: string; vault_base_folder: string + fetch_recent_documents_interval: number } const DEFAULT_SETTINGS: RelayMDSettings = { base_uri: 'https://api.relay.md', api_key: '', - vault_base_folder: "relay.md" + vault_base_folder: "relay.md", + fetch_recent_documents_interval: 5 * 60, } export default class RelayMdPLugin extends Plugin { settings: RelayMDSettings; + fetch_recent_document_timer: number; async onload() { await this.loadSettings(); @@ -85,10 +88,7 @@ export default class RelayMdPLugin extends Plugin { } })); - // Additionally, we register a timer to fetch documents for us - this.registerInterval(window.setInterval(() => { - this.get_recent_documents(); - }, 5 * 60 * 1000)); // 5 minutes + this.register_timer(); this.addSettingTab(new RelayMDSettingTab(this.app, this)); } @@ -161,7 +161,8 @@ export default class RelayMdPLugin extends Plugin { async load_document_body(result: any) { const id: string = result["relay-document"]; const filename: string = result["relay-filename"]; - const relay_to: Array = result["relay-to"] + const relay_to: Array = result["relay-to"]; + const remote_checksum = result["checksum_sha256"]; const options: RequestUrlParam = { url: this.settings.base_uri + '/v1/doc/' + id, @@ -179,8 +180,16 @@ export default class RelayMdPLugin extends Plugin { // Let's first see if we maybe have the document already somewhere in the fault const located_documents = await this.locate_document(id); if (located_documents) { - located_documents.forEach((located_document: TFile) => { - this.upsert_document(located_document.path, body); + located_documents.forEach(async (located_document: TFile) => { + // Compare checksum + const local_content = await this.app.vault.readBinary(located_document); + const checksum = await this.calculateSHA256Checksum(local_content); + console.log(checksum); + if (checksum != remote_checksum) { + this.upsert_document(located_document.path, body); + } else { + console.log("No change detected on " + located_document.path); + } }) } else { // Loop through team/topics @@ -191,7 +200,7 @@ export default class RelayMdPLugin extends Plugin { let folder: string = this.settings.vault_base_folder + "/"; // We ignore the "_" team which is a "global" team if (team != "_") - folder += team; + folder += team + "/"; folder += topic; // Does the folder exist? If not, create it "recursively" if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { @@ -341,6 +350,7 @@ export default class RelayMdPLugin extends Plugin { } console.log("Successfully sent to " + this.settings.base_uri); + try { // WARNING: This overwrites an existing relay-document id potentially, // depending on how the server responds. It's a feature, and not a bug and @@ -405,6 +415,20 @@ export default class RelayMdPLugin extends Plugin { return located_files; } + register_timer() { + // Additionally, we register a timer to fetch documents for us + if (this.fetch_recent_document_timer) { + window.clearInterval( + this.fetch_recent_document_timer + ); + } + this.fetch_recent_document_timer = window.setInterval(() => { + this.get_recent_documents(); + }, this.settings.fetch_recent_documents_interval * 1000); + + this.registerInterval(this.fetch_recent_document_timer); + } + } class RelayMDSettingTab extends PluginSettingTab { @@ -465,5 +489,22 @@ class RelayMDSettingTab extends PluginSettingTab { this.plugin.settings.vault_base_folder = value; await this.plugin.saveSettings(); })); + + new Setting(containerEl) + .setName('Fetch recent documents interval') + .setDesc('How often to look for document updates in seconds') + .addText(text => text + .setPlaceholder('300') + .setValue(this.plugin.settings.fetch_recent_documents_interval.toString()) + .onChange(async (value) => { + const as_float = parseFloat(value); + if (isNaN(as_float) || !isFinite(+as_float)) { + new Notice("Interval must be an number!") + } else { + this.plugin.settings.fetch_recent_documents_interval = as_float; + await this.plugin.saveSettings(); + this.plugin.register_timer(); + } + })); } } From 4cba6886b229e83346e0dda741fb2041bf69f8ae Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Sat, 27 Jan 2024 20:34:25 +0100 Subject: [PATCH 07/10] feat: download assets into team attachment folder --- src/main.ts | 65 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/src/main.ts b/src/main.ts index 8d114d8..dea505f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,7 +13,6 @@ import { normalizePath } from 'obsidian'; - interface Embed { checksum_sha256: string; filename: string; @@ -21,6 +20,19 @@ interface Embed { id: string; } +/* + * Doesn't work because we use "-" in keys in the json api +interface Document { + checksum_sha256: string; + embeds: Array; + filesize: int; + relay_document: string; + relay_filename: string; + relay_title: string; + relay_to: string; +} +*/ + interface RelayMDSettings { base_uri: string; api_key: string; @@ -49,7 +61,8 @@ export default class RelayMdPLugin extends Plugin { return; } this.settings.api_key = params.token; - this.settings.base_uri = DEFAULT_SETTINGS.base_uri; // also potentially reset the base uri + //Let's not update this here because we got redirected from the page after clicking anyhow + //this.settings.base_uri = DEFAULT_SETTINGS.base_uri; // also potentially reset the base uri this.saveSettings(); new Notice("Access credentials for relay.md have been succesfully installed!"); }); @@ -150,7 +163,7 @@ export default class RelayMdPLugin extends Plugin { const embeds = result.embeds; if (embeds) { embeds.map((embed: Embed) => { - this.load_embeds(embed); + this.load_embeds(embed, result); }); } @@ -184,7 +197,6 @@ export default class RelayMdPLugin extends Plugin { // Compare checksum const local_content = await this.app.vault.readBinary(located_document); const checksum = await this.calculateSHA256Checksum(local_content); - console.log(checksum); if (checksum != remote_checksum) { this.upsert_document(located_document.path, body); } else { @@ -211,29 +223,34 @@ export default class RelayMdPLugin extends Plugin { } } - async load_embeds(embed: Embed) { + async load_embeds(embed: Embed, document) { // TODO: think about this for a bit, it allows to update other peoples file by just using the same filename // On the other hand, if we were to put the team name into the path, we end up (potentially) having tos // duplicate the file into multiple team's attachment folder. Hmm - const folder = this.settings.vault_base_folder + "/" + "_attachments" - if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { - this.make_directory_recursively(folder); - } - const path = normalizePath(folder + "/" + embed.filename); - const file = this.app.vault.getAbstractFileByPath(path); - if (!(file instanceof TFile)) { - const content = await this.get_embed_binady(embed); - this.app.vault.createBinary(path, content); - console.log("Binary file " + path + " has been created!"); - } else { - const local_content = await this.app.vault.readBinary(file); - const checksum = await this.calculateSHA256Checksum(local_content); - if (checksum != embed.checksum_sha256) { + document["relay-to"].forEach(async (team_topic: string) => { + const parts = team_topic.split("@", 2); + const team = parts[1]; + if (!team) return; + const folder = normalizePath(this.settings.vault_base_folder + "/" + team + "/_attachments"); + if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { + this.make_directory_recursively(folder); + } + const path = normalizePath(folder + "/" + embed.filename); + const file = this.app.vault.getAbstractFileByPath(path); + if (!(file instanceof TFile)) { const content = await this.get_embed_binady(embed); - this.app.vault.modifyBinary(file, content); - console.log("Binary file " + path + " has been updated!"); + this.app.vault.createBinary(path, content); + console.log("Binary file " + path + " has been created!"); + } else { + const local_content = await this.app.vault.readBinary(file); + const checksum = await this.calculateSHA256Checksum(local_content); + if (checksum != embed.checksum_sha256) { + const content = await this.get_embed_binady(embed); + this.app.vault.modifyBinary(file, content); + console.log("Binary file " + path + " has been updated!"); + } } - } + }) } async get_embed_binady(embed: Embed) { @@ -372,8 +389,8 @@ export default class RelayMdPLugin extends Plugin { } metadata.embeds.map(async (item: any) => { let file = this.app.vault.getAbstractFileByPath(item.link); - console.log("Uploading attachment: " + item.link); if (file instanceof TFile) { + console.log("Uploading attachment: " + item.link); this.upload_asset(id, item.link, file); } }); @@ -397,7 +414,7 @@ export default class RelayMdPLugin extends Plugin { new Notice("Relay.md returned an error: " + response.json.error.message); return; } - console.log("Successfully uploaded " + file.path); + console.log("Successfully uploaded " + file.path + " as " + response.json.result.id); } async locate_document(document_id: string) { From d67f115d386a0e4f6cb944fa8af0a33c7e1b2013 Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Thu, 1 Feb 2024 21:12:51 +0100 Subject: [PATCH 08/10] feat: support for folders in filename --- src/main.ts | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/main.ts b/src/main.ts index dea505f..2ef1b4c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -118,14 +118,15 @@ export default class RelayMdPLugin extends Plugin { await this.saveData(this.settings); } - make_directory_recursively(folder: string) { - folder.split('/').reduce( - (directories, directory) => { + make_directory_recursively(path: string) { + // path contains the filename as well which could contain a folder. That's why we slice away the last part + path.split('/').slice(0, -1).reduce( + (directories = "", directory) => { directories += `${directory}/`; - if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { + if (!(this.app.vault.getAbstractFileByPath(directories) instanceof TFolder)) { this.app.vault.createFolder(directories); } - return directories; + return directories }, '', ); @@ -160,6 +161,8 @@ export default class RelayMdPLugin extends Plugin { } const result = response.json.result; + + // Load embeds const embeds = result.embeds; if (embeds) { embeds.map((embed: Embed) => { @@ -192,7 +195,7 @@ export default class RelayMdPLugin extends Plugin { // Let's first see if we maybe have the document already somewhere in the fault const located_documents = await this.locate_document(id); - if (located_documents) { + if (located_documents?.length) { located_documents.forEach(async (located_document: TFile) => { // Compare checksum const local_content = await this.app.vault.readBinary(located_document); @@ -214,16 +217,15 @@ export default class RelayMdPLugin extends Plugin { if (team != "_") folder += team + "/"; folder += topic; + const path = normalizePath(folder + "/" + filename); // Does the folder exist? If not, create it "recursively" - if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { - this.make_directory_recursively(folder); - } - this.upsert_document(normalizePath(folder + "/" + filename), body); + this.make_directory_recursively(path); + this.upsert_document(path, body); } } } - async load_embeds(embed: Embed, document) { + async load_embeds(embed: Embed, document: any) { // document is a dictionary provided by the API with incompatible key names like ("relay-to") // TODO: think about this for a bit, it allows to update other peoples file by just using the same filename // On the other hand, if we were to put the team name into the path, we end up (potentially) having tos // duplicate the file into multiple team's attachment folder. Hmm @@ -232,16 +234,10 @@ export default class RelayMdPLugin extends Plugin { const team = parts[1]; if (!team) return; const folder = normalizePath(this.settings.vault_base_folder + "/" + team + "/_attachments"); - if (!(this.app.vault.getAbstractFileByPath(folder) instanceof TFolder)) { - this.make_directory_recursively(folder); - } const path = normalizePath(folder + "/" + embed.filename); + this.make_directory_recursively(path); const file = this.app.vault.getAbstractFileByPath(path); - if (!(file instanceof TFile)) { - const content = await this.get_embed_binady(embed); - this.app.vault.createBinary(path, content); - console.log("Binary file " + path + " has been created!"); - } else { + if (file instanceof TFile) { const local_content = await this.app.vault.readBinary(file); const checksum = await this.calculateSHA256Checksum(local_content); if (checksum != embed.checksum_sha256) { @@ -249,6 +245,10 @@ export default class RelayMdPLugin extends Plugin { this.app.vault.modifyBinary(file, content); console.log("Binary file " + path + " has been updated!"); } + } else { + const content = await this.get_embed_binady(embed); + this.app.vault.createBinary(path, content); + console.log("Binary file " + path + " has been created!"); } }) } @@ -340,7 +340,7 @@ export default class RelayMdPLugin extends Plugin { // Either we POST a new document or we PUT an existing document let method = "POST"; - let url = this.settings.base_uri + '/v1/doc?filename=' + encodeURIComponent(activeFile.name); + let url = this.settings.base_uri + '/v1/doc?filename=' + encodeURIComponent(activeFile.path); if (id) { method = "PUT" url = this.settings.base_uri + '/v1/doc/' + id; @@ -389,8 +389,12 @@ export default class RelayMdPLugin extends Plugin { } metadata.embeds.map(async (item: any) => { let file = this.app.vault.getAbstractFileByPath(item.link); - if (file instanceof TFile) { - console.log("Uploading attachment: " + item.link); + if (!(file instanceof TFile)) { + file = this.app.metadataCache.getFirstLinkpathDest(item.link, ""); + } + if (!file || !(file instanceof TFile)) { + console.log(`Embed ${item.link} was not found!`) + } else { this.upload_asset(id, item.link, file); } }); From 824cd374e8c68c03761dd22ef2dfadcacd003e50 Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Fri, 2 Feb 2024 21:11:00 +0100 Subject: [PATCH 09/10] chore: more details in settings --- src/main.ts | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/main.ts b/src/main.ts index 2ef1b4c..f28e4af 100644 --- a/src/main.ts +++ b/src/main.ts @@ -34,15 +34,25 @@ interface Document { */ interface RelayMDSettings { + // Webseite + auth_url: string; + // API Site base_uri: string; + // API Access key api_key: string; + // username that corresponds to the api_key + api_username: string; + // vault folder to store new files in vault_base_folder: string + // Interval to check for new documents fetch_recent_documents_interval: number } const DEFAULT_SETTINGS: RelayMDSettings = { + auth_url: 'https://relay.md', base_uri: 'https://api.relay.md', api_key: '', + api_username: '', vault_base_folder: "relay.md", fetch_recent_documents_interval: 5 * 60, } @@ -61,8 +71,8 @@ export default class RelayMdPLugin extends Plugin { return; } this.settings.api_key = params.token; - //Let's not update this here because we got redirected from the page after clicking anyhow - //this.settings.base_uri = DEFAULT_SETTINGS.base_uri; // also potentially reset the base uri + this.settings.api_username = params.username; + this.settings.base_uri = params.api_url; this.saveSettings(); new Notice("Access credentials for relay.md have been succesfully installed!"); }); @@ -156,7 +166,7 @@ export default class RelayMdPLugin extends Plugin { const response: RequestUrlResponse = await requestUrl(options); if (response.json.error) { console.error("API server returned an error"); - new Notice("Relay.md returned an error: " + response.json.error.message); + new Notice("API returned an error: " + response.json.error.message); return; } @@ -291,7 +301,7 @@ export default class RelayMdPLugin extends Plugin { const response: RequestUrlResponse = await requestUrl(options); if (response.json.error) { console.error("API server returned an error"); - new Notice("Relay.md returned an error: " + response.json.error.message); + new Notice("API returned an error: " + response.json.error.message); return; } try { @@ -362,7 +372,7 @@ export default class RelayMdPLugin extends Plugin { const response: RequestUrlResponse = await requestUrl(options); if (response.json.error) { console.error("API server returned an error"); - new Notice("Relay.md returned an error: " + response.json.error.message); + new Notice("API returned an error: " + response.json.error.message); return; } @@ -415,7 +425,7 @@ export default class RelayMdPLugin extends Plugin { const response: RequestUrlResponse = await requestUrl(options); if (response.json.error) { console.error("API server returned an error"); - new Notice("Relay.md returned an error: " + response.json.error.message); + new Notice("API returned an error: " + response.json.error.message); return; } console.log("Successfully uploaded " + file.path + " as " + response.json.result.id); @@ -466,32 +476,31 @@ class RelayMDSettingTab extends PluginSettingTab { containerEl.empty(); new Setting(containerEl) - .setName('Base API URI') - .setDesc('Base URL for API access') + .setName('Authenticate against') + .setDesc('Main Website to manage accounts') .addText(text => text - .setPlaceholder('Enter your API url') - .setValue(this.plugin.settings.base_uri) + .setPlaceholder('Enter your URL') + .setValue(this.plugin.settings.auth_url) .onChange(async (value) => { - this.plugin.settings.base_uri = value; + this.plugin.settings.auth_url = value; await this.plugin.saveSettings(); })); if (!(this.plugin.settings.api_key) || this.plugin.settings.api_key === DEFAULT_SETTINGS.api_key) { new Setting(containerEl) .setName('API Access') - .setDesc('Authenticate against the relay.md API') + .setDesc('Link with your account') .addButton((button) => button.setButtonText("Obtain access to relay.md").onClick(async () => { - window.open("https://relay.md/configure/obsidian"); - //window.open("http://localhost:5000/configure/obsidian"); + window.open(this.plugin.settings.auth_url + "/configure/obsidian"); }) ); } else { new Setting(containerEl) .setName('API Access') - .setDesc('Authenticate against the relay.md API') + .setDesc(`Logged in as @${this.plugin.settings.api_username}`) .addButton((button) => - button.setButtonText("reset").onClick(async () => { + button.setButtonText(`Logout!`).onClick(async () => { this.plugin.settings.api_key = DEFAULT_SETTINGS.api_key; await this.plugin.saveSettings(); // refresh settings page From 6d100add8e867ed0440aeebed3f7e986e52a81cf Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Fri, 9 Feb 2024 21:18:51 +0100 Subject: [PATCH 10/10] chore: improve readme and docs --- DEVELOP.md | 21 ++++++++++++ README.md | 95 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 76 insertions(+), 40 deletions(-) create mode 100644 DEVELOP.md diff --git a/DEVELOP.md b/DEVELOP.md new file mode 100644 index 0000000..1dbbfc4 --- /dev/null +++ b/DEVELOP.md @@ -0,0 +1,21 @@ +# 🐝 Developer Section + +What comes below is meant for developers and maintainers. + +## 🚢 Releasing new releases + +Releases are entirely managed in github actions and only require a pull-request +against `master` to be merged. Semantic versioning in connection with +conventional commits will take care of versioning + +## 🏠 How to build + +- Clone this repo. +- Make sure your NodeJS is at least v16 (`node --version`). +- `npm i` or `yarn` to install dependencies. +- `npm run dev` to start compilation in watch mode. +- (optionally) `npm run lint` to check coding style + +## 🎯 Manually installing the plugin + +Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/relay-md/`. diff --git a/README.md b/README.md index a924b3d..a569d9c 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,72 @@ +

+ +

+

+ GitHub Release + GitHub Actions Workflow Status + GitHub Downloads (all assets, all releases) + GitHub contributors + GitHub last commit + GitHub language count + GitHub License + Mozilla HTTP Observatory Grade + GitHub watchers + GitHub Repo stars +

+

+ "Buy Me A Coffee" +

+ # Relay.md Obsidian plugin This repo contains an [Obsidian](https://obsidian.md) plugin for [relay.md](https://relay.md). -The purpose of relay.md is to make sharing markdown files fun again. In -particular, we want to establish "Markdown workflows for teams". +🎉 The purpose of relay.md is to make **sharing markdown files fun again**. -Relay.md makes it easy to send documents to groups or people or individuals and -allows to subscribe to entire teams and their documents. This will allow -individual team members to share their knowledge with the entire team from -within Obsidian. No more copy&pasting and editing into some strange wiki-syntax. +Relay.md enables **quick** and **easy** sharing of documents with a team and +allows to **subscribe** to individual subjects. Individual team members to share +their knowledge with the entire team from +within Obsidian. + +* ❌ No more **copy&pasting** to share a document with your team. +* ❌ No more changing syntax because your mail client does not support Markdown +* ❌ No more moving your hands away from keyboard to your mouse +* ❌ No more looking up email addresses +* ✅ share your document with the right people in seconds -Further, those that deal with different projects, teams or clients, can keep -their information aggregated within Obsidian and send out their documents to -corresponding people from within Obsidian. Got your specs for -*new-start-up-feature-A* ready, send them out to the tech team of the startup. Finished writing a consultancy contract for *business B*, have them notified -from within Obsidian by sendind the docs via relay.md. +Further, those that deal with different projects, teams or clients, can keep +their information aggregated within **as single vault** and send out their documents to +corresponding people from within Obsidian. + +🤩 Send specification to the right people in your team using **topics**. +from within Obsidian by sending the docs via relay.md. + +🎯 Most importantly, you get to keep your knowledge together! + +# 🏠 Installation + +1. [open relay.md plugin in Obsidian](obsidian://show-plugin?id=relay-md) +2. click **install** +3. click **enable** +4. click **configure** -Most importantly, you get to keep your stuff together! +# 🪢 Link your account with relay.md +1. Open plugin configuration +2. Click **obtain access to relay.md** +3. 🎉 Ready to rumble -# Howto: +# 📶 Use the Plugin Using relay.md couldn't be easier with Obsidian. All you need to do is specify -the recipient(s) in the frontmatter using: +the recipient(s) in the **frontmatter** using: ``` relay-to: - - label@team + - subject@team-name - @funnyfriend49 ``` -Upon updating the document, the file will be sent to relay.md using your -personal access token. As soon as your friends open up their Obsidian, the -relay.md plugin will automatically retrieve all documents that have been created -or updated. - -# Developer Section - -What comes below is meant for developers and maintainers. - -## Releasing new releases -Releases are entirely managed in github actions and only require a pull-request -against `master` to be merged. Semantic versioning in connection with -conventional commits will take care of versioning - -## How to build - -- Clone this repo. -- Make sure your NodeJS is at least v16 (`node --version`). -- `npm i` or `yarn` to install dependencies. -- `npm run dev` to start compilation in watch mode. -- (optionally) `npm run lint` to check coding style - -## Manually installing the plugin - -Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/relay-md/`. +1. Upon updating the document, the file will be sent to relay.md +2. Your access credentials are stored in your vault during configuration +3. As soon as your team mates open up their Obsidian, the relay.md plugin will automatically retrieve all documents that have been created or updated for them.