From 144013cee93f81e307d7160e7ac3b86d9aed34a9 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Sat, 6 Apr 2024 13:31:27 -0400 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=94=A8=20refactor:=20#84=20Move=20acc?= =?UTF-8?q?ount=20settings=20into=20new=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugin.ts | 37 ++- src/settings/account.ts | 172 ++++++++++++++ src/settings/index.ts | 3 + src/{settings.ts => settings/settings-tab.ts} | 220 +++--------------- src/settings/types.ts | 36 +++ 5 files changed, 274 insertions(+), 194 deletions(-) create mode 100644 src/settings/account.ts create mode 100644 src/settings/index.ts rename src/{settings.ts => settings/settings-tab.ts} (56%) create mode 100644 src/settings/types.ts diff --git a/src/plugin.ts b/src/plugin.ts index e34330d..f35a8c3 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,7 +1,7 @@ import { DEFAULT_SETTINGS, GithubLinkPluginSettingsTab } from "./settings"; import { Logger } from "./logger"; -import type { GithubLinkPluginSettings } from "./settings"; +import type { GithubLinkPluginData, GithubLinkPluginSettings } from "./settings"; import { InlineRenderer } from "./inline/inline"; import { Plugin } from "obsidian"; import { QueryProcessor } from "./query/processor"; @@ -9,6 +9,7 @@ import { createInlineViewPlugin } from "./inline/view-plugin"; import { RequestCache } from "./github/cache"; export const PluginSettings: GithubLinkPluginSettings = { ...DEFAULT_SETTINGS }; +export const PluginData: GithubLinkPluginData = { cache: null, settings: PluginSettings }; export const logger = new Logger(); let cache: RequestCache; export function getCache(): RequestCache { @@ -18,16 +19,40 @@ export function getCache(): RequestCache { export class GithubLinkPlugin extends Plugin { public cacheInterval: number | undefined; async onload() { - Object.assign(PluginSettings, await this.loadData()); + let data = await this.loadData(); + + // Migrate settings from data root to settings -- remove in v1.0.0 + if (data.accounts) { + const newSettings: GithubLinkPluginSettings = { + accounts: data.accounts ?? PluginSettings.accounts, + cacheIntervalSeconds: data.cacheIntervalSeconds ?? PluginSettings.cacheIntervalSeconds, + defaultPageSize: data.defaultPageSize ?? PluginSettings.defaultPageSize, + logLevel: data.logLevel ?? PluginSettings.logLevel, + maxCacheAgeHours: data.maxCacheAgeHours ?? PluginSettings.maxCacheAgeHours, + minRequestSeconds: data.minRequestSeconds ?? PluginSettings.minRequestSeconds, + tagShowPRMergeable: data.tagShowPRMergeable ?? PluginSettings.tagShowPRMergeable, + tagTooltips: data.tagTooltips ?? PluginSettings.tagTooltips, + defaultAccount: data.defaultAccount ?? PluginSettings.defaultAccount, + }; + const newData: GithubLinkPluginData = { + cache: data.cache ?? PluginData.cache, + settings: newSettings, + }; + await this.saveData(newData); + data = newData; + } + + Object.assign(PluginSettings, data.settings); + Object.assign(PluginData, data); logger.logLevel = PluginSettings.logLevel; - cache = new RequestCache(PluginSettings.cache); + cache = new RequestCache(PluginData.cache); // Clean cache const maxAge = new Date(new Date().getTime() - PluginSettings.maxCacheAgeHours * 60 * 60 * 1000); const entriesDeleted = cache.clean(maxAge); if (entriesDeleted > 0) { - PluginSettings.cache = cache.toJSON(); + PluginData.cache = cache.toJSON(); await this.saveData(PluginSettings); logger.info(`Cleaned ${entriesDeleted} entries from request cache.`); } @@ -50,10 +75,10 @@ export class GithubLinkPlugin extends Plugin { window.setInterval(async () => { logger.debug("Checking if cache needs a save."); if (cache.cacheUpdated) { - PluginSettings.cache = cache.toJSON(); + PluginData.cache = cache.toJSON(); await this.saveData(PluginSettings); cache.cacheUpdated = false; - logger.info(`Saved request cache with ${PluginSettings.cache?.length} items.`); + logger.info(`Saved request cache with ${PluginData.cache?.length} items.`); } }, PluginSettings.cacheIntervalSeconds * 1000), ); diff --git a/src/settings/account.ts b/src/settings/account.ts new file mode 100644 index 0000000..5fecd82 --- /dev/null +++ b/src/settings/account.ts @@ -0,0 +1,172 @@ +import type { App } from "obsidian"; +import { Setting } from "obsidian"; +import type { GithubAccount } from "./types"; +import { AuthModal } from "src/auth-modal"; +import { auth } from "src/github/auth"; +import type { Verification } from "@octokit/auth-oauth-device/dist-types/types"; + +export class AccountSettings { + authModal: AuthModal | null = null; + newAccount: GithubAccount | null = null; + + constructor( + private readonly app: App, + private readonly container: HTMLElement, + private readonly saveCallback: () => Promise, + private readonly displayCallback: () => void, + private readonly removeCallback: (account: GithubAccount) => void, + ) {} + + public render(accounts: GithubAccount[]): void { + for (const account of accounts) { + this.renderAccountSetting(account); + } + } + + public renderNewAccount( + container: HTMLElement, + saveNewAccountCallback: (account: GithubAccount) => Promise, + ): void { + this.newAccount = { id: crypto.randomUUID(), name: "", orgs: [], token: "" }; + // TODO: Combine the new account and existing account rendering to reduce duplication + const accountContainer = container.createDiv(); + const header = accountContainer.createEl("h3", { text: "New account" }); + + new Setting(accountContainer) + .setName("Account name") + .setDesc("Required.") + .addText((text) => { + text.setValue(this.newAccount!.name); + text.onChange((value) => { + this.newAccount!.name = value; + header.setText(value ?? "New account"); + }); + }) + .addButton((button) => { + button.setIcon("trash"); + button.setTooltip("Delete account"); + button.onClick(() => { + this.newAccount = null; + this.displayCallback(); + }); + }); + + new Setting(accountContainer) + .setName("Orgs and users") + .setDesc( + "A comma separated list of the GitHub organizations and users this account should be used for. Optional.", + ) + .addTextArea((text) => { + text.setValue(this.newAccount!.orgs.join(", ")); + text.onChange((value) => { + this.newAccount!.orgs = value.split(","); + }); + }); + + new Setting(accountContainer) + .setName("Token") + .setDesc( + "A GitHub token, which can be generated automatically (recommended) or by creating a personal access token (not recommended unless org does not allow OAuth tokens). Required.", + ) + .addButton((button) => { + button.setButtonText("Generate Token"); + button.onClick(async () => { + const authResult = await auth(this.tokenVerification.bind(this))({ + type: "oauth", + }); + this.authModal?.close(); + this.authModal = null; + this.newAccount!.token = authResult.token; + this.displayCallback(); + }); + }) + .addText((text) => { + text.setPlaceholder("Personal Access Token / OAuth Token"); + text.setValue(this.newAccount!.token); + text.onChange((value) => { + this.newAccount!.token = value; + }); + }); + + new Setting(accountContainer).addButton((button) => { + button.setButtonText("Save account"); + button.setTooltip("Save account"); + button.setIcon("save"); + button.onClick(async () => { + if (!this.newAccount?.name || !this.newAccount.token) { + return; + } + await saveNewAccountCallback(this.newAccount); + this.newAccount = null; + this.displayCallback(); + }); + }); + } + + public renderAccountSetting(account: GithubAccount, parent: HTMLElement = this.container): void { + const accountContainer = parent.createDiv(); + accountContainer.createEl("h3", { text: account.name }); + + new Setting(accountContainer) + .setName("Account name") + .addText((text) => { + text.setValue(account.name); + text.onChange((value) => { + account.name = value; + this.saveCallback(); + }); + }) + .addButton((button) => { + button.setIcon("trash"); + button.setTooltip("Delete account"); + button.onClick(async () => { + this.removeCallback(account); + await this.saveCallback(); + this.displayCallback(); + }); + }); + + new Setting(accountContainer) + .setName("Orgs and users") + .setDesc("A comma separated list of the GitHub organizations and users this account should be used for.") + .addTextArea((text) => { + text.setValue(account.orgs.join(", ")); + text.onChange((value) => { + account.orgs = value.split(",").map((org) => org.trim()); + this.saveCallback(); + }); + }); + + new Setting(accountContainer) + .setName("Token") + .setDesc( + "A GitHub token, which can be generated automatically (recommended) or by creating a personal access token (not recommended unless org does not allow OAuth tokens).", + ) + .addButton((button) => { + button.setButtonText("Generate Token"); + button.onClick(async () => { + const authResult = await auth(this.tokenVerification.bind(this))({ + type: "oauth", + }); + this.authModal?.close(); + this.authModal = null; + account.token = authResult.token; + await this.saveCallback(); + this.displayCallback(); + }); + }) + .addText((text) => { + text.setPlaceholder("Personal Access Token / OAuth Token"); + text.setValue(account.token); + text.onChange((value) => { + account.token = value; + this.saveCallback(); + }); + }); + } + + private tokenVerification(verification: Verification) { + this.authModal = new AuthModal(this.app, verification); + this.authModal.open(); + } +} diff --git a/src/settings/index.ts b/src/settings/index.ts new file mode 100644 index 0000000..ea50da3 --- /dev/null +++ b/src/settings/index.ts @@ -0,0 +1,3 @@ +export type { GithubAccount, GithubLinkPluginData, GithubLinkPluginSettings } from "./types"; +export { DEFAULT_SETTINGS } from "./types"; +export { GithubLinkPluginSettingsTab } from "./settings-tab"; diff --git a/src/settings.ts b/src/settings/settings-tab.ts similarity index 56% rename from src/settings.ts rename to src/settings/settings-tab.ts index c2e06f7..77b2753 100644 --- a/src/settings.ts +++ b/src/settings/settings-tab.ts @@ -2,49 +2,21 @@ import { Notice, PluginSettingTab, Setting } from "obsidian"; import type { App } from "obsidian"; -import { AuthModal } from "./auth-modal"; -import type { GithubLinkPlugin } from "./plugin"; -import { LogLevel } from "./logger"; -import { PluginSettings, getCache } from "./plugin"; -import type { Verification } from "@octokit/auth-oauth-device/dist-types/types"; -import { auth } from "./github/auth"; - -export interface GithubAccount { - id: string; - name: string; - orgs: string[]; - token: string; -} - -export interface GithubLinkPluginSettings { - accounts: GithubAccount[]; - defaultAccount?: string; - defaultPageSize: number; - logLevel: LogLevel; - tagTooltips: boolean; - tagShowPRMergeable: boolean; - cacheIntervalSeconds: number; - maxCacheAgeHours: number; - minRequestSeconds: number; - cache: string[] | null; // TODO: Move this out of settings; it's not a setting it's just part of plugin data. -} - -export const DEFAULT_SETTINGS: GithubLinkPluginSettings = { - accounts: [], - defaultPageSize: 10, - logLevel: LogLevel.Error, - tagTooltips: false, - tagShowPRMergeable: false, - cacheIntervalSeconds: 60, - maxCacheAgeHours: 120, - minRequestSeconds: 60, - cache: null, -}; +import type { GithubLinkPlugin } from "../plugin"; +import { LogLevel } from "../logger"; +import { PluginData, PluginSettings, getCache } from "../plugin"; +import type { GithubAccount, GithubLinkPluginData } from "./types"; +import { DEFAULT_SETTINGS } from "./types"; +import { AccountSettings } from "./account"; export class GithubLinkPluginSettingsTab extends PluginSettingTab { - authModal: AuthModal | null = null; - newAccount: GithubAccount | null = null; - + private readonly accountSettings = new AccountSettings( + this.app, + this.containerEl, + this.saveSettings.bind(this), + this.display.bind(this), + this.removeAccount.bind(this), + ); constructor( public app: App, private readonly plugin: GithubLinkPlugin, @@ -52,7 +24,7 @@ export class GithubLinkPluginSettingsTab extends PluginSettingTab { super(app, plugin); } - display() { + public display() { const { containerEl } = this; containerEl.empty(); containerEl.createEl("h2", { text: "GitHub authentication" }); @@ -61,13 +33,13 @@ export class GithubLinkPluginSettingsTab extends PluginSettingTab { text: "No authentication is required to reference public repositories. Providing a token allows referencing private repos, but the token is stored in plain text. You can create multiple accounts for multiple tokens.", }); - new Setting(containerEl).setName("Add account").addButton((button) => { + const newAccountSection = containerEl.createDiv(); + new Setting(newAccountSection).setName("Add account").addButton((button) => { button.setButtonText(""); button.setIcon("plus"); button.setTooltip("Add Account"); button.onClick(() => { - this.newAccount = { id: crypto.randomUUID(), name: "", orgs: [], token: "" }; - this.display(); + this.accountSettings.renderNewAccount(newAccountSection, this.saveNewAccount.bind(this)); }); }); @@ -90,144 +62,7 @@ export class GithubLinkPluginSettingsTab extends PluginSettingTab { }); }); - // TODO: Combine the new account and existing account rendering to reduce duplication - if (this.newAccount !== null) { - const accountContainer = containerEl.createDiv(); - const header = accountContainer.createEl("h3", { text: "New account" }); - - new Setting(accountContainer) - .setName("Account name") - .setDesc("Required.") - .addText((text) => { - text.setValue(this.newAccount!.name); - text.onChange((value) => { - this.newAccount!.name = value; - header.setText(value ?? "New account"); - }); - }) - .addButton((button) => { - button.setIcon("trash"); - button.setTooltip("Delete account"); - button.onClick(() => { - this.newAccount = null; - this.display(); - }); - }); - - new Setting(accountContainer) - .setName("Orgs and users") - .setDesc( - "A comma separated list of the GitHub organizations and users this account should be used for. Optional.", - ) - .addTextArea((text) => { - text.setValue(this.newAccount!.orgs.join(", ")); - text.onChange((value) => { - this.newAccount!.orgs = value.split(","); - }); - }); - - new Setting(accountContainer) - .setName("Token") - .setDesc( - "A GitHub token, which can be generated automatically (recommended) or by creating a personal access token (not recommended unless org does not allow OAuth tokens). Required.", - ) - .addButton((button) => { - button.setButtonText("Generate Token"); - button.onClick(async () => { - const authResult = await auth(this.tokenVerification.bind(this))({ - type: "oauth", - }); - this.authModal?.close(); - this.authModal = null; - this.newAccount!.token = authResult.token; - this.display(); - }); - }) - .addText((text) => { - text.setPlaceholder("Personal Access Token / OAuth Token"); - text.setValue(this.newAccount!.token); - text.onChange((value) => { - this.newAccount!.token = value; - }); - }); - - new Setting(accountContainer).addButton((button) => { - button.setButtonText("Save account"); - button.setTooltip("Save account"); - button.setIcon("save"); - button.onClick(async () => { - if (!this.newAccount?.name || !this.newAccount.token) { - return; - } - PluginSettings.accounts.unshift(this.newAccount); - await this.saveSettings(); - this.newAccount = null; - this.display(); - }); - }); - } - - for (const account of PluginSettings.accounts) { - const accountContainer = containerEl.createDiv(); - accountContainer.createEl("h3", { text: account.name }); - - new Setting(accountContainer) - .setName("Account name") - .addText((text) => { - text.setValue(account.name); - text.onChange((value) => { - account.name = value; - this.saveSettings(); - }); - }) - .addButton((button) => { - button.setIcon("trash"); - button.setTooltip("Delete account"); - button.onClick(async () => { - PluginSettings.accounts.remove(account); - await this.saveSettings(); - this.display(); - }); - }); - - new Setting(accountContainer) - .setName("Orgs and users") - .setDesc("A comma separated list of the GitHub organizations and users this account should be used for.") - .addTextArea((text) => { - text.setValue(account.orgs.join(", ")); - text.onChange((value) => { - account.orgs = value.split(",").map((org) => org.trim()); - this.saveSettings(); - }); - }); - - new Setting(accountContainer) - .setName("Token") - .setDesc( - "A GitHub token, which can be generated automatically (recommended) or by creating a personal access token (not recommended unless org does not allow OAuth tokens).", - ) - .addButton((button) => { - button.setButtonText("Generate Token"); - button.onClick(async () => { - const authResult = await auth(this.tokenVerification.bind(this))({ - type: "oauth", - }); - this.authModal?.close(); - this.authModal = null; - account.token = authResult.token; - await this.saveSettings(); - this.display(); - }); - }) - .addText((text) => { - text.setPlaceholder("Personal Access Token / OAuth Token"); - text.setValue(account.token); - text.onChange((value) => { - account.token = value; - this.saveSettings(); - }); - }); - } + this.accountSettings.render(PluginSettings.accounts); containerEl.createEl("h2", { text: "Other settings" }); @@ -374,7 +209,7 @@ export class GithubLinkPluginSettingsTab extends PluginSettingTab { button.setButtonText("Clear cache"); button.onClick(async () => { const itemsDeleted = getCache().clean(new Date()); - PluginSettings.cache = null; + PluginData.cache = null; await this.saveSettings(); new Notice(`Removed ${itemsDeleted} stored items from GitHub Link cache.`, 3000); }); @@ -408,11 +243,20 @@ export class GithubLinkPluginSettingsTab extends PluginSettingTab { } private saveSettings() { - return this.plugin.saveData(PluginSettings); + const newData: GithubLinkPluginData = { + cache: PluginData.cache, + settings: PluginSettings, + }; + return this.plugin.saveData(newData); } - private tokenVerification(verification: Verification) { - this.authModal = new AuthModal(this.app, verification); - this.authModal.open(); + private async removeAccount(account: GithubAccount): Promise { + PluginSettings.accounts.remove(account); + await this.saveSettings(); + } + + private async saveNewAccount(account: GithubAccount): Promise { + PluginSettings.accounts.unshift(account); + await this.saveSettings(); } } diff --git a/src/settings/types.ts b/src/settings/types.ts new file mode 100644 index 0000000..1e6a328 --- /dev/null +++ b/src/settings/types.ts @@ -0,0 +1,36 @@ +import { LogLevel } from "src/logger"; + +export interface GithubAccount { + id: string; + name: string; + orgs: string[]; + token: string; +} + +export interface GithubLinkPluginData { + settings: GithubLinkPluginSettings; + cache: string[] | null; +} + +export interface GithubLinkPluginSettings { + accounts: GithubAccount[]; + defaultAccount?: string; + defaultPageSize: number; + logLevel: LogLevel; + tagTooltips: boolean; + tagShowPRMergeable: boolean; + cacheIntervalSeconds: number; + maxCacheAgeHours: number; + minRequestSeconds: number; +} + +export const DEFAULT_SETTINGS: GithubLinkPluginSettings = { + accounts: [], + defaultPageSize: 10, + logLevel: LogLevel.Error, + tagTooltips: false, + tagShowPRMergeable: false, + cacheIntervalSeconds: 60, + maxCacheAgeHours: 120, + minRequestSeconds: 60, +}; From 40aff6125a7088b5fb6a3805e7ce5f4a43a12ce2 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Sat, 6 Apr 2024 13:35:28 -0400 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=90=9B=20fix:=20#84=20Trim=20extra=20?= =?UTF-8?q?spaces=20from=20orgs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/settings/account.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/account.ts b/src/settings/account.ts index 5fecd82..6786caa 100644 --- a/src/settings/account.ts +++ b/src/settings/account.ts @@ -59,7 +59,7 @@ export class AccountSettings { .addTextArea((text) => { text.setValue(this.newAccount!.orgs.join(", ")); text.onChange((value) => { - this.newAccount!.orgs = value.split(","); + this.newAccount!.orgs = value.split(",").map((acc) => acc.trim()); }); }); From 807d604a1dc90be995ca4125bfc9d24a5026394d Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Sat, 6 Apr 2024 13:55:34 -0400 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=90=9B=20fix:=20#84=20Correct=20cache?= =?UTF-8?q?=20save?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin.ts b/src/plugin.ts index f35a8c3..da8f9be 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -76,7 +76,7 @@ export class GithubLinkPlugin extends Plugin { logger.debug("Checking if cache needs a save."); if (cache.cacheUpdated) { PluginData.cache = cache.toJSON(); - await this.saveData(PluginSettings); + await this.saveData(PluginData); cache.cacheUpdated = false; logger.info(`Saved request cache with ${PluginData.cache?.length} items.`); } From 62a6f0d94e8fda9636a8b4697a933370e30e8669 Mon Sep 17 00:00:00 2001 From: Nathan Smith Date: Thu, 11 Apr 2024 22:37:59 -0400 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=A8=20feat:=20#84=20Support=20custom?= =?UTF-8?q?=20oauth=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: #84 --- src/github/auth.ts | 6 ++-- src/settings/account.ts | 70 ++++++++++++++++++++++++++++++++++-- src/settings/settings-tab.ts | 4 +++ src/settings/types.ts | 2 ++ 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/github/auth.ts b/src/github/auth.ts index fd817e5..af9b990 100644 --- a/src/github/auth.ts +++ b/src/github/auth.ts @@ -4,6 +4,8 @@ import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device"; import { request } from "@octokit/request"; import { requestUrl } from "obsidian"; +const defaultClientId = "baf0370cb98e1387d244"; + function getHeaders(headers: HeadersInit | undefined): Record | undefined { if (!headers) { return undefined; @@ -71,10 +73,10 @@ async function doFetch(url: RequestInfo | URL, options?: RequestInit | undefined return partialResult as Response; } -export const auth = (verificationHandler: OnVerificationCallback) => +export const auth = (verificationHandler: OnVerificationCallback, clientId: string = defaultClientId) => createOAuthDeviceAuth({ clientType: "oauth-app", - clientId: "baf0370cb98e1387d244", + clientId, scopes: ["repo"], onVerification: verificationHandler, request: request.defaults({ diff --git a/src/settings/account.ts b/src/settings/account.ts index 6786caa..146db16 100644 --- a/src/settings/account.ts +++ b/src/settings/account.ts @@ -27,7 +27,9 @@ export class AccountSettings { container: HTMLElement, saveNewAccountCallback: (account: GithubAccount) => Promise, ): void { - this.newAccount = { id: crypto.randomUUID(), name: "", orgs: [], token: "" }; + if (!this.newAccount) { + this.newAccount = { id: crypto.randomUUID(), name: "", orgs: [], token: "", customOAuth: false }; + } // TODO: Combine the new account and existing account rendering to reduce duplication const accountContainer = container.createDiv(); const header = accountContainer.createEl("h3", { text: "New account" }); @@ -63,6 +65,36 @@ export class AccountSettings { }); }); + const oauthDesc = createFragment((fragment) => { + fragment.appendText( + "You can optionally provide your own OAuth app for more control and a larger request rate limit. See the ", + ); + fragment.createEl("a", { + text: "plugin documentation", + href: "https://github.com/nathonius/obsidian-github-link/wiki/Authentication#custom-oauth-app", + }); + fragment.appendText(" for instructions."); + }); + const customOAuthSetting = new Setting(accountContainer) + .setName("Use custom OAuth app") + .setDesc(oauthDesc) + .addToggle((toggle) => { + toggle.setValue(this.newAccount!.customOAuth ?? false); + toggle.onChange((value) => { + this.newAccount!.customOAuth = value; + this.displayCallback(); + }); + }); + if (this.newAccount.customOAuth) { + customOAuthSetting.addText((text) => { + text.setValue(this.newAccount!.clientId ?? ""); + text.setPlaceholder("Client ID"); + text.onChange((value) => { + this.newAccount!.clientId = value; + }); + }); + } + new Setting(accountContainer) .setName("Token") .setDesc( @@ -93,7 +125,7 @@ export class AccountSettings { button.setTooltip("Save account"); button.setIcon("save"); button.onClick(async () => { - if (!this.newAccount?.name || !this.newAccount.token) { + if (!this.newAccount?.name) { return; } await saveNewAccountCallback(this.newAccount); @@ -137,6 +169,34 @@ export class AccountSettings { }); }); + const oauthDesc = createFragment((fragment) => { + fragment.appendText( + "You can optionally provide your own OAuth app for more control and a larger request rate limit. See the ", + ); + fragment.createEl("a", { text: "plugin documentation", href: "#" }); + fragment.appendText(" for instructions."); + }); + const customOAuthSetting = new Setting(accountContainer) + .setName("Use custom OAuth app") + .setDesc(oauthDesc) + .addToggle((toggle) => { + toggle.setValue(account.customOAuth ?? false); + toggle.onChange((value) => { + account.customOAuth = value; + this.displayCallback(); + }); + }); + if (account.customOAuth) { + customOAuthSetting.addText((text) => { + text.setValue(account.clientId ?? ""); + text.setPlaceholder("Client ID"); + text.onChange((value) => { + account.clientId = value; + this.saveCallback(); + }); + }); + } + new Setting(accountContainer) .setName("Token") .setDesc( @@ -145,7 +205,11 @@ export class AccountSettings { .addButton((button) => { button.setButtonText("Generate Token"); button.onClick(async () => { - const authResult = await auth(this.tokenVerification.bind(this))({ + const customClientId = account.customOAuth ? account.clientId : undefined; + const authResult = await auth( + this.tokenVerification.bind(this), + customClientId, + )({ type: "oauth", }); this.authModal?.close(); diff --git a/src/settings/settings-tab.ts b/src/settings/settings-tab.ts index 77b2753..4fe682f 100644 --- a/src/settings/settings-tab.ts +++ b/src/settings/settings-tab.ts @@ -43,6 +43,10 @@ export class GithubLinkPluginSettingsTab extends PluginSettingTab { }); }); + if (this.accountSettings.newAccount) { + this.accountSettings.renderNewAccount(newAccountSection, this.saveNewAccount.bind(this)); + } + new Setting(containerEl) .setName("Default account") .setDesc("The account that will be used if no other users or organizations match.") diff --git a/src/settings/types.ts b/src/settings/types.ts index 1e6a328..0f7f4b5 100644 --- a/src/settings/types.ts +++ b/src/settings/types.ts @@ -5,6 +5,8 @@ export interface GithubAccount { name: string; orgs: string[]; token: string; + customOAuth?: boolean; + clientId?: string; } export interface GithubLinkPluginData {