mirror of
https://github.com/nathonius/obsidian-github-link.git
synced 2026-07-22 09:20:25 +00:00
Merge 52bece28c4 into a5ad979881
This commit is contained in:
commit
1dd22a91ae
15 changed files with 217 additions and 54 deletions
|
|
@ -5,11 +5,21 @@ import { PluginSettingTabMock } from "./obsidian/PluginSettingTab";
|
|||
import { NoticeMock } from "./obsidian/Notice";
|
||||
import { setIconMock } from "./obsidian/setIcon";
|
||||
|
||||
class SecretComponentMock {
|
||||
setValue(_value: string) {
|
||||
return this;
|
||||
}
|
||||
onChange(_cb: (value: string) => unknown) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
App: AppMock,
|
||||
Plugin: PluginMock,
|
||||
Modal: ModalMock,
|
||||
PluginSettingTab: PluginSettingTabMock,
|
||||
Notice: NoticeMock,
|
||||
SecretComponent: SecretComponentMock,
|
||||
setIcon: setIconMock,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { App, FileManager, Keymap, MetadataCache, Scope, UserEvent, Vault, Workspace } from "obsidian";
|
||||
import type { App, FileManager, Keymap, MetadataCache, RenderContext, Scope, SecretStorage, UserEvent, Vault, Workspace } from "obsidian";
|
||||
|
||||
export class AppMock implements App {
|
||||
get keymap(): Keymap {
|
||||
|
|
@ -22,4 +22,21 @@ export class AppMock implements App {
|
|||
get lastEvent(): UserEvent | null {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
get renderContext(): RenderContext {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
secretStorage: SecretStorage = {
|
||||
setSecret: () => {},
|
||||
getSecret: () => null,
|
||||
listSecrets: () => [],
|
||||
} as unknown as SecretStorage;
|
||||
isDarkMode(): boolean {
|
||||
return false;
|
||||
}
|
||||
loadLocalStorage(key: string): string | null {
|
||||
return null;
|
||||
}
|
||||
saveLocalStorage(_key: string, _value: string | undefined): void {
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,4 +30,13 @@ export class ModalMock implements Modal {
|
|||
onClose(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
setTitle(title: string): this {
|
||||
return this;
|
||||
}
|
||||
setContent(content: string | DocumentFragment): this {
|
||||
return this;
|
||||
}
|
||||
setCloseCallback(cb: () => void): this {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import type { Notice } from "obsidian";
|
|||
export class NoticeMock implements Notice {
|
||||
constructor(message: string | DocumentFragment, duration?: number | undefined) {}
|
||||
noticeEl!: HTMLElement;
|
||||
containerEl!: HTMLElement;
|
||||
messageEl!: HTMLElement;
|
||||
setMessage(message: string | DocumentFragment): this {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,16 @@ export class PluginMock implements Plugin {
|
|||
registerCodeMirror(callback: (cm: Editor) => any): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
removeCommand(id: string): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
registerBasesView(id: string, factory: any): boolean {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
registerCliHandler(command: string, handler: any): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
onUserEnable(): void {}
|
||||
registerObsidianProtocolHandler(action: string, handler: ObsidianProtocolHandler): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { App, PluginSettingTab, Plugin } from "obsidian";
|
||||
|
||||
export class PluginSettingTabMock implements PluginSettingTab {
|
||||
icon: string = "";
|
||||
constructor(
|
||||
public app: App,
|
||||
public plugin: Plugin,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "github-link",
|
||||
"name": "GitHub Link",
|
||||
"version": "1.2.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"minAppVersion": "1.11.4",
|
||||
"description": "Enrich your notes with issue and pull request content from GitHub",
|
||||
"author": "Nathonius",
|
||||
"authorUrl": "https://nathan-smith.org/",
|
||||
|
|
|
|||
49
package-lock.json
generated
49
package-lock.json
generated
|
|
@ -33,7 +33,7 @@
|
|||
"eslint-plugin-unused-imports": "^3.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"obsidian": "latest",
|
||||
"obsidian": "^1.12.3",
|
||||
"octokit": "^3.1.2",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-jest": "^29.1.2",
|
||||
|
|
@ -673,18 +673,24 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.0.tgz",
|
||||
"integrity": "sha512-hm8XshYj5Fo30Bb922QX9hXB/bxOAVH+qaqHBzw5TKa72vOeslyGwd4X8M0c1dJ9JqxlaMceOQ8RsL9tC7gU0A==",
|
||||
"dev": true
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
|
||||
"integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.23.0.tgz",
|
||||
"integrity": "sha512-/51px9N4uW8NpuWkyUX+iam5+PM6io2fm+QmRnzwqBy5v/pwGg9T0kILFtYeum8hjuvENtgsGNKluOfqIICmeQ==",
|
||||
"version": "6.38.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
|
||||
"integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"crelt": "^1.0.6",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
|
|
@ -1648,6 +1654,13 @@
|
|||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
|
@ -3190,6 +3203,13 @@
|
|||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
|
|
@ -6398,17 +6418,18 @@
|
|||
}
|
||||
},
|
||||
"node_modules/obsidian": {
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.4.11.tgz",
|
||||
"integrity": "sha512-BCVYTvaXxElJMl6MMbDdY/CGK+aq18SdtDY/7vH8v6BxCBQ6KF4kKxL0vG9UZ0o5qh139KpUoJHNm+6O5dllKA==",
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz",
|
||||
"integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/codemirror": "5.60.8",
|
||||
"moment": "2.29.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0"
|
||||
"@codemirror/state": "6.5.0",
|
||||
"@codemirror/view": "6.38.6"
|
||||
}
|
||||
},
|
||||
"node_modules/obsidian/node_modules/moment": {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
"eslint-plugin-unused-imports": "^3.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"obsidian": "latest",
|
||||
"obsidian": "^1.12.3",
|
||||
"octokit": "^3.1.2",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-jest": "^29.1.2",
|
||||
|
|
@ -49,4 +49,4 @@
|
|||
"lodash": "^4.17.21",
|
||||
"queue": "^7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
66
src/keychain.ts
Normal file
66
src/keychain.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import type { SecretStorage } from "obsidian";
|
||||
import type { GithubAccount } from "./settings/types";
|
||||
import { logger } from "./plugin";
|
||||
|
||||
let storage: SecretStorage;
|
||||
|
||||
export function initKeychain(secretStorage: SecretStorage): void {
|
||||
storage = secretStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the token value from SecretStorage using the account's secret name.
|
||||
*/
|
||||
export function getToken(account: GithubAccount): string | null {
|
||||
if (!account.tokenSecret) {
|
||||
return null;
|
||||
}
|
||||
return storage.getSecret(account.tokenSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a token under a named secret and update the account's tokenSecret reference.
|
||||
*/
|
||||
export function setToken(account: GithubAccount, token: string, secretName?: string): void {
|
||||
if (!secretName && !account.tokenSecret) {
|
||||
secretName = `github-link-${account.name.toLowerCase().replace(/[^a-z0-9]/g, "-")}`;
|
||||
}
|
||||
if (secretName) {
|
||||
account.tokenSecret = secretName;
|
||||
}
|
||||
storage.setSecret(account.tokenSecret, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the token value from SecretStorage (the entry remains with an empty value).
|
||||
*/
|
||||
export function clearToken(account: GithubAccount): void {
|
||||
if (account.tokenSecret) {
|
||||
storage.setSecret(account.tokenSecret, "");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate plain-text tokens from data.json into named secrets.
|
||||
* Called once on data version upgrade.
|
||||
*/
|
||||
export function migrateTokens(accounts: GithubAccount[]): void {
|
||||
for (const account of accounts) {
|
||||
if (account.token && !account.tokenSecret) {
|
||||
setToken(account, account.token);
|
||||
logger.info(`Migrated token for account "${account.name}" to Obsidian Keychain as "${account.tokenSecret}".`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate in-memory account tokens from SecretStorage on startup.
|
||||
*/
|
||||
export function loadTokens(accounts: GithubAccount[]): void {
|
||||
for (const account of accounts) {
|
||||
const token = getToken(account);
|
||||
if (token) {
|
||||
account.token = token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { expect, jest, test, describe, beforeEach } from "@jest/globals";
|
||||
import type { Plugin, RequestUrlResponse } from "obsidian";
|
||||
import type { RequestUrlResponse } from "obsidian";
|
||||
import { App } from "obsidian";
|
||||
import type { PluginMock } from "../__mocks__/obsidian/Plugin";
|
||||
import * as manifest from "../manifest.json";
|
||||
|
|
@ -13,7 +13,7 @@ import { DATA_VERSION } from "./settings/types";
|
|||
jest.mock("./settings/settings-tab");
|
||||
|
||||
function mockedPlugin(plugin: GithubLinkPlugin): PluginMock {
|
||||
return plugin as Plugin as PluginMock;
|
||||
return plugin as unknown as PluginMock;
|
||||
}
|
||||
|
||||
describe("GithubLinkPlugin", () => {
|
||||
|
|
|
|||
|
|
@ -10,11 +10,13 @@ import { createInlineViewPlugin } from "./inline/view-plugin";
|
|||
import { RequestCache } from "./github/cache";
|
||||
import { QueryProcessor } from "./query/processor";
|
||||
import { DATA_VERSION } from "./settings/types";
|
||||
import { initKeychain, loadTokens, migrateTokens } from "./keychain";
|
||||
|
||||
export const PluginSettings: GithubLinkPluginSettings = { ...DEFAULT_SETTINGS };
|
||||
export const PluginData: GithubLinkPluginData = { cache: null, settings: PluginSettings, dataVersion: DATA_VERSION };
|
||||
export const logger = new Logger();
|
||||
let cache: RequestCache;
|
||||
|
||||
export function getCache(): RequestCache {
|
||||
return cache;
|
||||
}
|
||||
|
|
@ -29,25 +31,30 @@ export class GithubLinkPlugin extends Plugin {
|
|||
logger.logLevel = PluginSettings.logLevel;
|
||||
|
||||
cache = new RequestCache(PluginData.cache);
|
||||
initKeychain(this.app.secretStorage);
|
||||
|
||||
if (data.dataVersion === undefined || PluginData.dataVersion < DATA_VERSION) {
|
||||
// Always clear cache when data version changes
|
||||
const entriesDeleted = cache.clean(new Date());
|
||||
PluginData.cache = null;
|
||||
PluginData.dataVersion = DATA_VERSION;
|
||||
await this.saveData(PluginData);
|
||||
|
||||
migrateTokens(PluginSettings.accounts);
|
||||
|
||||
await this.saveData(this.getDataForSave());
|
||||
new Notice(
|
||||
`GitHub link data schema migrated to version ${DATA_VERSION}. Removed ${entriesDeleted} stored items from GitHub Link cache.`,
|
||||
3000,
|
||||
);
|
||||
}
|
||||
|
||||
loadTokens(PluginSettings.accounts);
|
||||
|
||||
// Clean cache
|
||||
const maxAge = new Date(new Date().getTime() - PluginSettings.maxCacheAgeHours * 60 * 60 * 1000);
|
||||
const entriesDeleted = cache.clean(maxAge);
|
||||
if (entriesDeleted > 0) {
|
||||
PluginData.cache = cache.toJSON();
|
||||
await this.saveData(PluginData);
|
||||
await this.saveData(this.getDataForSave());
|
||||
logger.info(`Cleaned ${entriesDeleted} entries from request cache.`);
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +67,20 @@ export class GithubLinkPlugin extends Plugin {
|
|||
this.setCacheInterval();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns plugin data with tokens stripped from accounts, for safe persistence to data.json.
|
||||
*/
|
||||
public getDataForSave(): GithubLinkPluginData {
|
||||
return {
|
||||
cache: PluginData.cache,
|
||||
settings: {
|
||||
...PluginSettings,
|
||||
accounts: PluginSettings.accounts.map((acc) => ({ ...acc, token: "" })),
|
||||
},
|
||||
dataVersion: DATA_VERSION,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Save cache at regular interval
|
||||
*/
|
||||
|
|
@ -68,7 +89,7 @@ export class GithubLinkPlugin extends Plugin {
|
|||
logger.debug("Checking if cache needs a save.");
|
||||
if (cache.cacheUpdated) {
|
||||
PluginData.cache = cache.toJSON();
|
||||
await this.saveData(PluginData);
|
||||
await this.saveData(this.getDataForSave());
|
||||
cache.cacheUpdated = false;
|
||||
logger.info(`Saved request cache with ${PluginData.cache?.length} items.`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import type { App } from "obsidian";
|
||||
import { Setting } from "obsidian";
|
||||
import { SecretComponent, Setting } from "obsidian";
|
||||
import type { Verification } from "@octokit/auth-oauth-device/dist-types/types";
|
||||
import { AuthModal } from "../auth-modal";
|
||||
import { auth } from "../github/auth";
|
||||
import { setToken } from "../keychain";
|
||||
import type { GithubAccount } from "./types";
|
||||
|
||||
export class AccountSettings {
|
||||
|
|
@ -28,7 +29,7 @@ export class AccountSettings {
|
|||
saveNewAccountCallback: (account: GithubAccount) => Promise<void>,
|
||||
): void {
|
||||
if (!this.newAccount) {
|
||||
this.newAccount = { id: crypto.randomUUID(), name: "", orgs: [], token: "", customOAuth: false };
|
||||
this.newAccount = { id: crypto.randomUUID(), name: "", orgs: [], token: "", tokenSecret: "", customOAuth: false };
|
||||
}
|
||||
// TODO: Combine the new account and existing account rendering to reduce duplication
|
||||
const accountContainer = container.createDiv();
|
||||
|
|
@ -98,7 +99,7 @@ export class AccountSettings {
|
|||
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.",
|
||||
"A GitHub token stored in Obsidian's keychain. Generate one automatically (recommended) or link an existing secret.",
|
||||
)
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Generate Token");
|
||||
|
|
@ -113,16 +114,19 @@ export class AccountSettings {
|
|||
this.authModal?.close();
|
||||
this.authModal = null;
|
||||
this.newAccount!.token = authResult.token;
|
||||
setToken(this.newAccount!, 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.trim();
|
||||
});
|
||||
});
|
||||
.addComponent((el) =>
|
||||
new SecretComponent(this.app, el)
|
||||
.setValue(this.newAccount!.tokenSecret)
|
||||
.onChange((secretName) => {
|
||||
this.newAccount!.tokenSecret = secretName;
|
||||
const token = this.app.secretStorage.getSecret(secretName);
|
||||
this.newAccount!.token = token ?? "";
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(accountContainer).addButton((button) => {
|
||||
button.setButtonText("Save account");
|
||||
|
|
@ -204,7 +208,7 @@ export class AccountSettings {
|
|||
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).",
|
||||
"A GitHub token stored in Obsidian's keychain. Generate one automatically (recommended) or link an existing secret.",
|
||||
)
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Generate Token");
|
||||
|
|
@ -219,18 +223,21 @@ export class AccountSettings {
|
|||
this.authModal?.close();
|
||||
this.authModal = null;
|
||||
account.token = authResult.token;
|
||||
setToken(account, 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.trim();
|
||||
void this.saveCallback();
|
||||
});
|
||||
});
|
||||
.addComponent((el) =>
|
||||
new SecretComponent(this.app, el)
|
||||
.setValue(account.tokenSecret)
|
||||
.onChange(async (secretName) => {
|
||||
account.tokenSecret = secretName;
|
||||
const token = this.app.secretStorage.getSecret(secretName);
|
||||
account.token = token ?? "";
|
||||
await this.saveCallback();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private tokenVerification(verification: Verification) {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import type { App } from "obsidian";
|
|||
import type { GithubLinkPlugin } from "../plugin";
|
||||
import { LogLevel } from "../logger";
|
||||
import { PluginData, PluginSettings, getCache } from "../plugin";
|
||||
import type { GithubAccount, GithubLinkPluginData } from "./types";
|
||||
import { DATA_VERSION, DEFAULT_SETTINGS } from "./types";
|
||||
import { clearToken } from "../keychain";
|
||||
import type { GithubAccount } from "./types";
|
||||
import { DEFAULT_SETTINGS } from "./types";
|
||||
import { AccountSettings } from "./account";
|
||||
|
||||
export class GithubLinkPluginSettingsTab extends PluginSettingTab {
|
||||
|
|
@ -30,7 +31,7 @@ export class GithubLinkPluginSettingsTab extends PluginSettingTab {
|
|||
containerEl.createEl("h2", { text: "GitHub authentication" });
|
||||
|
||||
containerEl.createEl("p", {
|
||||
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.",
|
||||
text: "No authentication is required to reference public repositories. Providing a token allows referencing private repos. Tokens are stored securely in the Obsidian Keychain. You can create multiple accounts for multiple tokens.",
|
||||
});
|
||||
|
||||
const newAccountSection = containerEl.createDiv();
|
||||
|
|
@ -330,16 +331,12 @@ export class GithubLinkPluginSettingsTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
private saveSettings() {
|
||||
const newData: GithubLinkPluginData = {
|
||||
cache: PluginData.cache,
|
||||
settings: PluginSettings,
|
||||
dataVersion: DATA_VERSION,
|
||||
};
|
||||
return this.plugin.saveData(newData);
|
||||
return this.plugin.saveData(this.plugin.getDataForSave());
|
||||
}
|
||||
|
||||
private async removeAccount(account: GithubAccount): Promise<void> {
|
||||
PluginSettings.accounts.remove(account);
|
||||
clearToken(account);
|
||||
await this.saveSettings();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import { LogLevel } from "../logger";
|
||||
|
||||
export const DATA_VERSION = 1;
|
||||
export const DATA_VERSION = 2;
|
||||
|
||||
export interface GithubAccount {
|
||||
id: string;
|
||||
name: string;
|
||||
orgs: string[];
|
||||
/** Runtime-only: populated from secretStorage on load. Not persisted. */
|
||||
token: string;
|
||||
/** Name of the secret in Obsidian's SecretStorage. Persisted in data.json. */
|
||||
tokenSecret: string;
|
||||
customOAuth?: boolean;
|
||||
clientId?: string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue