diff --git a/README.md b/README.md index 15de874..8cd0d3d 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,11 @@ This plugin imports your saved [Omnivore](https://omnivore.app/) articles and hi ## Usage -1. The plugin will automatically sync with Omnivore every time you open the plugin and every time you change the settings -2. You can also manually sync with Omnivore by clicking the Omnivore icon on the ribbon -3. You can also change the API key, the search filter, and how often the plugin syncs with Omnivore by updating the settings -4. The plugin creates a new page for each saved article including metadata, labels. Content you have highlighted in Omnivore, and any notes you added, will be nested in the article page -5. Clicking on the article will open the Omnivore article in a new tab -6. We also create an internal link to each label in the article so you can group articles by label +1. The plugin will sync with Omnivore when you click on Omnivore ribbon icon or use the palette command +2. You can also change the API key, the search filter, and how often the plugin syncs with Omnivore by updating the settings +3. The plugin creates a new page for each saved article including metadata, labels. Content you have highlighted in Omnivore, and any notes you added, will be nested in the article page +4. Clicking on the article will open the Omnivore article in a new tab +5. We also create an internal link to each label in the article so you can group articles by label ## Contacts diff --git a/src/main.ts b/src/main.ts index 94565c5..c1c296b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,6 +8,8 @@ import { Plugin, PluginSettingTab, Setting, + TFile, + TFolder, } from "obsidian"; import { Article, @@ -21,7 +23,6 @@ import { } from "./util"; import { FolderSuggest } from "./settings/file-suggest"; -// Remember to rename these classes and interfaces! enum Filter { ALL = "import all my articles", HIGHLIGHTS = "import just highlights", @@ -102,7 +103,7 @@ export default class OmnivorePlugin extends Plugin { await this.loadSettings(); this.addCommand({ - id: "omnivore-sync", + id: "sync", name: "Sync", callback: () => { this.fetchOmnivore(); @@ -110,7 +111,7 @@ export default class OmnivorePlugin extends Plugin { }); this.addCommand({ - id: "omnivore-resync", + id: "resync", name: "Resync all articles", callback: () => { this.settings.syncAt = ""; @@ -201,9 +202,10 @@ export default class OmnivorePlugin extends Plugin { this.settings.dateFormat ); const folderName = `${folder}/${dateSaved}`; - if ( - !(await this.app.vault.adapter.exists(normalizePath(folderName))) - ) { + const omnivoreFolder = app.vault.getAbstractFileByPath( + normalizePath(folderName) + ); + if (!(omnivoreFolder instanceof TFolder)) { await this.app.vault.createFolder(folderName); } @@ -245,16 +247,6 @@ export default class OmnivorePlugin extends Plugin { }; }); - // // use template from file if specified - // let templateToUse = template; - // if (templateFileLocation) { - // const templateFile = - // this.app.vault.getAbstractFileByPath(templateFileLocation); - // if (templateFile) { - // templateToUse = await this.app.vault.read(templateFile as TFile); - // } - // } - // Build content string based on template const content = Mustache.render(template, { title: article.title, @@ -272,7 +264,16 @@ export default class OmnivorePlugin extends Plugin { content: article.content, }); - await this.app.vault.adapter.write(normalizePath(pageName), content); + const normalizedPath = normalizePath(pageName); + const omnivoreFile = app.vault.getAbstractFileByPath(normalizedPath); + if (omnivoreFile instanceof TFile) { + const existingContent = await this.app.vault.read(omnivoreFile); + if (existingContent !== content) { + await this.app.vault.modify(omnivoreFile, content); + } + } else { + await this.app.vault.create(normalizedPath, content); + } } } @@ -317,17 +318,12 @@ class OmnivoreSettingTab extends PluginSettingTab { this.plugin = plugin; } - private static createFragmentWithHTML = (html: string) => - createFragment( - (documentFragment) => (documentFragment.createDiv().innerHTML = html) - ); - display(): void { const { containerEl } = this; containerEl.empty(); - containerEl.createEl("h2", { text: "Settings for omnivore plugin" }); + containerEl.createEl("h2", { text: "Settings for Omnivore plugin" }); // create a group of general settings containerEl.createEl("h3", { @@ -342,16 +338,21 @@ class OmnivoreSettingTab extends PluginSettingTab { new Setting(generalSettings) .setName("API Key") .setDesc( - OmnivoreSettingTab.createFragmentWithHTML( - "You can create an API key at https://omnivore.app/settings/api" - ) + createFragment((fragment) => { + fragment.append( + "You can create an API key at ", + fragment.createEl("a", { + text: "https://omnivore.app/settings/api", + href: "https://omnivore.app/settings/api", + }) + ); + }) ) .addText((text) => text .setPlaceholder("Enter your Omnivore Api Key") .setValue(this.plugin.settings.apiKey) .onChange(async (value) => { - console.log("apiKey: " + value); this.plugin.settings.apiKey = value; await this.plugin.saveSettings(); }) @@ -365,7 +366,6 @@ class OmnivoreSettingTab extends PluginSettingTab { dropdown .setValue(this.plugin.settings.filter) .onChange(async (value) => { - console.log("filter: " + value); this.plugin.settings.filter = value; await this.plugin.saveSettings(); }); @@ -374,9 +374,16 @@ class OmnivoreSettingTab extends PluginSettingTab { new Setting(generalSettings) .setName("Custom query") .setDesc( - OmnivoreSettingTab.createFragmentWithHTML( - "See https://omnivore.app/help/search for more info on search query syntax" - ) + createFragment((fragment) => { + fragment.append( + "See ", + fragment.createEl("a", { + text: "https://omnivore.app/help/search", + href: "https://omnivore.app/help/search", + }), + " for more info on search query syntax" + ); + }) ) .addText((text) => text @@ -385,7 +392,6 @@ class OmnivoreSettingTab extends PluginSettingTab { ) .setValue(this.plugin.settings.customQuery) .onChange(async (value) => { - console.log("query: " + value); this.plugin.settings.customQuery = value; await this.plugin.saveSettings(); }) @@ -400,7 +406,6 @@ class OmnivoreSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.syncAt) .setDefaultFormat("yyyy-MM-dd'T'HH:mm:ss") .onChange(async (value) => { - console.log("syncAt: " + value); this.plugin.settings.syncAt = value; await this.plugin.saveSettings(); }) @@ -414,7 +419,6 @@ class OmnivoreSettingTab extends PluginSettingTab { dropdown .setValue(this.plugin.settings.highlightOrder) .onChange(async (value) => { - console.log("highlightOrder: " + value); this.plugin.settings.highlightOrder = value; await this.plugin.saveSettings(); }); @@ -423,35 +427,26 @@ class OmnivoreSettingTab extends PluginSettingTab { new Setting(generalSettings) .setName("Template") .setDesc( - OmnivoreSettingTab.createFragmentWithHTML( - `Enter template to render articles with. Reference` - ) + createFragment((fragment) => { + fragment.append( + "Enter template to render articles with ", + fragment.createEl("a", { + text: "Reference", + href: "https://github.com/janl/mustache.js/#templates", + }) + ); + }) ) .addTextArea((text) => text .setPlaceholder("Enter the template") .setValue(this.plugin.settings.template) .onChange(async (value) => { - console.log("template: " + value); this.plugin.settings.template = value; await this.plugin.saveSettings(); }) ); - // new Setting(generalSettings) - // .setName("Template file location") - // .setDesc("Choose the file to use as the template") - // .addSearch((search) => { - // new FileSuggest(this.app, search.inputEl); - // search - // .setPlaceholder("Enter the file path") - // .setValue(this.plugin.settings.templateFileLocation) - // .onChange(async (value) => { - // this.plugin.settings.templateFileLocation = value; - // await this.plugin.saveSettings(); - // }); - // }); - new Setting(generalSettings) .setName("Folder") .setDesc("Enter the folder where the data will be stored") @@ -478,9 +473,15 @@ class OmnivoreSettingTab extends PluginSettingTab { new Setting(generalSettings) .setName("Date Format") .setDesc( - OmnivoreSettingTab.createFragmentWithHTML( - 'Enter the format date for use in rendered template.\nFormat reference.' - ) + createFragment((fragment) => { + fragment.append( + "Enter the format date for use in rendered template.\nFormat ", + fragment.createEl("a", { + text: "reference", + href: "https://moment.github.io/luxon/#/formatting?id=table-of-tokens", + }) + ); + }) ) .addText((text) => text diff --git a/src/settings/file-suggest.ts b/src/settings/file-suggest.ts index e071b9f..b5b5463 100644 --- a/src/settings/file-suggest.ts +++ b/src/settings/file-suggest.ts @@ -1,3 +1,5 @@ +// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes + import { TAbstractFile, TFile, TFolder } from "obsidian"; import { TextInputSuggest } from "./suggest"; diff --git a/src/settings/suggest.ts b/src/settings/suggest.ts index a487ac4..fc0f29d 100644 --- a/src/settings/suggest.ts +++ b/src/settings/suggest.ts @@ -1,3 +1,5 @@ +// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes + import { createPopper, type Instance as PopperInstance } from "@popperjs/core"; import { App, type ISuggestOwner, Scope } from "obsidian"; import { wrapAround } from "../util"; diff --git a/src/util.ts b/src/util.ts index 7f2e446..cec47aa 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,6 +1,7 @@ import { diff_match_patch } from "diff-match-patch"; import { DateTime } from "luxon"; import escape from "markdown-escape"; +import { requestUrl } from "obsidian"; export const DATE_FORMAT_W_OUT_SECONDS = "yyyy-MM-dd'T'HH:mm"; export const DATE_FORMAT = `${DATE_FORMAT_W_OUT_SECONDS}:ss`; @@ -94,12 +95,13 @@ export const loadArticle = async ( slug: string, apiKey: string ): Promise
=> { - const res = await fetch(ENDPOINT, { + const res = await requestUrl({ + url: ENDPOINT, headers: requestHeaders(apiKey), body: `{"query":"\\n query GetArticle(\\n $username: String!\\n $slug: String!\\n ) {\\n article(username: $username, slug: $slug) {\\n ... on ArticleSuccess {\\n article {\\n ...ArticleFields\\n highlights {\\n ...HighlightFields\\n }\\n labels {\\n ...LabelFields\\n }\\n }\\n }\\n ... on ArticleError {\\n errorCodes\\n }\\n }\\n }\\n \\n fragment ArticleFields on Article {\\n savedAt\\n }\\n\\n \\n fragment HighlightFields on Highlight {\\n id\\n quote\\n annotation\\n }\\n\\n \\n fragment LabelFields on Label {\\n name\\n }\\n\\n","variables":{"username":"me","slug":"${slug}"}}`, method: "POST", }); - const response = (await res.json()) as GetArticleResponse; + const response = res.json as GetArticleResponse; return response.data.article.article; }; @@ -114,7 +116,8 @@ export const loadArticles = async ( includeContent = false, format = "html" ): Promise<[Article[], boolean]> => { - const res = await fetch(endpoint, { + const res = await requestUrl({ + url: endpoint, headers: requestHeaders(apiKey), body: `{"query":"\\n query Search($after: String, $first: Int, $query: String, $includeContent: Boolean, $format: String) {\\n search(first: $first, after: $after, query: $query, includeContent: $includeContent, format: $format) {\\n ... on SearchSuccess {\\n edges {\\n node {\\n title\\n slug\\n siteName\\n originalArticleUrl\\n url\\n author\\n updatedAt\\n description\\n savedAt\\n pageType\\n content\\n highlights {\\n id\\n quote\\n annotation\\n patch\\n updatedAt\\n }\\n labels {\\n name\\n }\\n }\\n }\\n pageInfo {\\n hasNextPage\\n }\\n }\\n ... on SearchError {\\n errorCodes\\n }\\n }\\n }\\n ","variables":{"after":"${after}","first":${first}, "query":"${ updatedAt ? "updated:" + updatedAt : "" @@ -122,7 +125,7 @@ export const loadArticles = async ( method: "POST", }); - const jsonRes = (await res.json()) as SearchResponse; + const jsonRes = res.json as SearchResponse; const articles = jsonRes.data.search.edges.map((e) => e.node); return [articles, jsonRes.data.search.pageInfo.hasNextPage]; @@ -135,7 +138,8 @@ export const loadDeletedArticleSlugs = async ( first = 10, updatedAt = "" ): Promise<[string[], boolean]> => { - const res = await fetch(endpoint, { + const res = await requestUrl({ + url: endpoint, headers: requestHeaders(apiKey), body: `{"query":"\\n query UpdatesSince($after: String, $first: Int, $since: Date!) {\\n updatesSince(first: $first, after: $after, since: $since) {\\n ... on UpdatesSinceSuccess {\\n edges {\\n updateReason\\n node {\\n slug\\n }\\n }\\n pageInfo {\\n hasNextPage\\n }\\n }\\n ... on UpdatesSinceError {\\n errorCodes\\n }\\n }\\n }\\n ","variables":{"after":"${after}","first":${first}, "since":"${ updatedAt || "2021-01-01" @@ -143,7 +147,7 @@ export const loadDeletedArticleSlugs = async ( method: "POST", }); - const jsonRes = (await res.json()) as UpdatesSinceResponse; + const jsonRes = res.json as UpdatesSinceResponse; const deletedArticleSlugs = jsonRes.data.updatesSince.edges .filter((edge) => edge.updateReason === UpdateReason.DELETED) .map((edge) => edge.node.slug);