diff --git a/packages/obsidian/src/main.ts b/packages/obsidian/src/main.ts index 48401d7..6f08ad5 100644 --- a/packages/obsidian/src/main.ts +++ b/packages/obsidian/src/main.ts @@ -16,6 +16,8 @@ import { TMDBSeasonAPI } from 'packages/obsidian/src/api/apis/TMDBSeasonAPI'; import { TMDBSeriesAPI } from 'packages/obsidian/src/api/apis/TMDBSeriesAPI'; import { VNDBAPI } from 'packages/obsidian/src/api/apis/VNDBAPI'; import { WikipediaAPI } from 'packages/obsidian/src/api/apis/WikipediaAPI'; +import type { LegacyApiKeyEntry } from 'packages/obsidian/src/modals/LegacyApiKeysModal'; +import { LegacyApiKeysModal } from 'packages/obsidian/src/modals/LegacyApiKeysModal'; import { PropertyMapper } from 'packages/obsidian/src/settings/PropertyMapper'; import { PropertyMappingModel } from 'packages/obsidian/src/settings/PropertyMapping'; import type { MediaDbPluginSettings } from 'packages/obsidian/src/settings/Settings'; @@ -31,6 +33,8 @@ import { MEDIA_TYPES } from 'packages/obsidian/src/utils/MediaTypeManager'; import { ModalHelper } from 'packages/obsidian/src/utils/ModalHelper'; import { unCamelCase } from 'packages/obsidian/src/utils/Utils'; +const LEGACY_API_KEYS = ['OMDbKey', 'TMDBKey', 'MobyGamesKey', 'GiantBombKey', 'ComicVineKey', 'BoardgameGeekKey'] as const; + export default class MediaDbPlugin extends Plugin { declare settings: MediaDbPluginSettings; apiManager!: APIManager; @@ -168,35 +172,42 @@ export default class MediaDbPlugin extends Plugin { }); } - async loadSettings(): Promise { - const diskSettings: MediaDbPluginSettings = (await this.loadData()) as MediaDbPluginSettings; - const defaultSettings: MediaDbPluginSettings = getDefaultSettings(this); - const loadedSettings: MediaDbPluginSettings = Object.assign({}, defaultSettings, diskSettings); + private getLegacyApiKeyEntries(diskSettings: Record): LegacyApiKeyEntry[] { + return LEGACY_API_KEYS.filter(key => typeof diskSettings[key] === 'string' && (diskSettings[key]).length > 0).map(key => ({ + key, + value: diskSettings[key] as string, + })); + } - // delete old api keys - // @ts-ignore - delete loadedSettings.BoardgameGeekKey; - // @ts-ignore - delete loadedSettings.ComicVineKey; - // @ts-ignore - delete loadedSettings.GiantBombKey; - // @ts-ignore - delete loadedSettings.MobyGamesKey; - // @ts-ignore - delete loadedSettings.OMDbKey; - // @ts-ignore - delete loadedSettings.TMDBKey; + private removeLegacyApiKeys(settings: Record): void { + for (const key of LEGACY_API_KEYS) { + delete settings[key]; + } + } + + async loadSettings(): Promise { + const diskSettings = (await this.loadData()) as Record; + const defaultSettings: MediaDbPluginSettings = getDefaultSettings(this); + const loadedSettings = Object.assign({}, defaultSettings, diskSettings) as MediaDbPluginSettings; const migratedModels = PropertyMappingModel.migrateModels( loadedSettings.propertyMappingModels || [], defaultSettings.propertyMappingModels.map(m => PropertyMappingModel.fromJSON(m)), ); - loadedSettings.propertyMappingModels = migratedModels.map(m => m.toJSON()); - this.settings = loadedSettings; - await this.saveSettings(); + const legacyEntries = this.getLegacyApiKeyEntries(diskSettings); + if (legacyEntries.length > 0) { + this.app.workspace.onLayoutReady(() => { + window.setTimeout(() => { + new LegacyApiKeysModal(this.app, legacyEntries, async () => { + this.removeLegacyApiKeys(this.settings as unknown as Record); + await this.saveSettings(); + }).open(); + }, 0); + }); + } } async saveSettings(): Promise { diff --git a/packages/obsidian/src/modals/LegacyApiKeysModal.ts b/packages/obsidian/src/modals/LegacyApiKeysModal.ts new file mode 100644 index 0000000..7a13f6d --- /dev/null +++ b/packages/obsidian/src/modals/LegacyApiKeysModal.ts @@ -0,0 +1,91 @@ +import type { App } from 'obsidian'; +import { Modal, Setting, Notice } from 'obsidian'; + +export interface LegacyApiKeyEntry { + key: string; + value: string; +} + +export class LegacyApiKeysModal extends Modal { + private entries: LegacyApiKeyEntry[]; + private onConfirm: () => void; + + constructor(app: App, entries: LegacyApiKeyEntry[], onConfirm: () => void) { + super(app); + this.entries = entries; + this.onConfirm = onConfirm; + } + + onOpen(): void { + const { contentEl } = this; + contentEl.addClass('media-db-plugin-legacy-keys-modal'); + + contentEl.createEl('h2', { text: 'Media DB plugin: Legacy API keys found' }); + + const wrapper = contentEl.createDiv({ cls: 'media-db-plugin-legacy-keys-wrapper' }); + + const intro = wrapper.createDiv({ cls: 'media-db-plugin-legacy-keys-intro' }); + intro.createEl('p', { + text: 'Media DB plugin has found old plaintext API keys in your settings. Copy them now if needed, they should be removed for safety. The new keychain-backed system will be used instead. The plugin will not be usable until the old plaintext keys have been deleted.', + }); + + const textarea = wrapper.createEl('textarea', { + cls: 'media-db-plugin-legacy-keys-textarea', + attr: { + readonly: 'true', + spellcheck: 'false', + }, + }); + + textarea.value = this.entries.map(e => `${e.key}: ${e.value}`).join('\n'); + textarea.style.display = 'none'; + + contentEl.createDiv({ cls: 'media-db-plugin-spacer' }); + + const bottomSettingRow = new Setting(contentEl); + + bottomSettingRow.addButton(btn => { + btn.setButtonText('Copy'); + btn.onClick(async () => { + await navigator.clipboard.writeText(textarea.value); + new Notice('Legacy API keys copied to clipboard.'); + }); + btn.buttonEl.addClass('media-db-plugin-button'); + }); + + bottomSettingRow.addButton(btn => { + btn.setButtonText('Show keys'); + + btn.onClick(() => { + const isHidden = textarea.style.display === 'none'; + + if (isHidden) { + textarea.style.display = ''; + btn.setButtonText('Hide keys'); + } else { + textarea.style.display = 'none'; + btn.setButtonText('Show keys'); + } + }); + + btn.buttonEl.addClass('media-db-plugin-button'); + }); + + bottomSettingRow.addButton(btn => { + btn.setButtonText('Delete plaintext keys'); + btn.setCta(); + btn.onClick(() => { + this.close(); + this.onConfirm(); + }); + btn.buttonEl.addClass('media-db-plugin-button'); + btn.buttonEl.addClass('mod-warning'); + btn.buttonEl.addClass('mod-danger'); + + }); + } + + onClose(): void { + this.contentEl.empty(); + } +} diff --git a/packages/obsidian/src/styles.css b/packages/obsidian/src/styles.css index ed3a79e..90e6c6b 100644 --- a/packages/obsidian/src/styles.css +++ b/packages/obsidian/src/styles.css @@ -295,3 +295,37 @@ small.media-db-plugin-list-text { width: var(--checkbox-size); height: var(--checkbox-size); } + +.media-db-plugin-legacy-keys-modal { + display: contents; +} + +.media-db-plugin-legacy-keys-intro { + color: var(--text-normal); +} + +.media-db-plugin-legacy-keys-wrapper { + display: flex; + flex-direction: column; + gap: 0.75rem; + width: 100%; + max-width: 100%; +} + +.media-db-plugin-legacy-keys-textarea { + width: 100%; + max-width: 100%; + min-height: 100px; + min-width: 500px; + box-sizing: border-box; + resize: vertical; + padding: 0.75rem; + border-radius: var(--radius-s); + border: 1px solid var(--background-modifier-border); + background: var(--background-primary); + color: var(--text-normal); + font-family: var(--font-monospace); + font-size: var(--font-ui-smaller); + line-height: 1.5; + white-space: pre; +}