diff --git a/.gitignore b/.gitignore index 2c3c4af..2c1c4e4 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ exampleVault/.obsidian/plugins/obsidian-media-db-plugin/* !exampleVault/.obsidian/plugins/obsidian-media-db-plugin/.hotreload exampleVault/Media DB/* -meta.txt +meta.txt +main.js \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 88a3ee5..f7a1bfc 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 1d4f84f..2fbc05c 100644 --- a/package.json +++ b/package.json @@ -26,26 +26,26 @@ "@happy-dom/global-registrator": "^18.0.1", "@lemons_dev/parsinom": "^0.0.12", "@popperjs/core": "^2.11.8", - "@types/bun": "^1.2.19", + "@types/bun": "^1.3.7", "builtin-modules": "^5.0.0", - "esbuild": "^0.25.8", + "esbuild": "^0.25.12", "esbuild-plugin-copy-watch": "^2.3.1", - "esbuild-svelte": "^0.9.3", - "eslint": "^9.32.0", + "esbuild-svelte": "^0.9.4", + "eslint": "^9.39.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-only-warn": "^1.1.0", "iso-639-2": "^3.0.2", "obsidian": "latest", - "openapi-fetch": "^0.14.0", - "openapi-typescript": "^7.8.0", - "prettier": "^3.6.2", - "prettier-plugin-svelte": "^3.4.0", + "openapi-fetch": "^0.14.1", + "openapi-typescript": "^7.10.1", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.4.1", "string-argv": "^0.3.2", - "svelte": "^5.38.0", - "svelte-check": "^4.3.1", + "svelte": "^5.48.5", + "svelte-check": "^4.3.5", "svelte-preprocess": "^6.0.3", "tslib": "^2.8.1", - "typescript": "^5.9.2", - "typescript-eslint": "^8.39.0" + "typescript": "^5.9.3", + "typescript-eslint": "^8.54.0" } } diff --git a/src/api/apis/TMDBMovieAPI.ts b/src/api/apis/TMDBMovieAPI.ts index 32e90da..936ab57 100644 --- a/src/api/apis/TMDBMovieAPI.ts +++ b/src/api/apis/TMDBMovieAPI.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */ + import createClient from 'openapi-fetch'; import type MediaDbPlugin from '../../main'; import type { MediaTypeModel } from '../../models/MediaTypeModel'; diff --git a/src/api/apis/TMDBSeasonAPI.ts b/src/api/apis/TMDBSeasonAPI.ts index bdeffd8..426f6db 100644 --- a/src/api/apis/TMDBSeasonAPI.ts +++ b/src/api/apis/TMDBSeasonAPI.ts @@ -1,9 +1,11 @@ +/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */ + import createClient from 'openapi-fetch'; import type MediaDbPlugin from '../../main'; import type { MediaTypeModel } from '../../models/MediaTypeModel'; +import { SeasonModel } from '../../models/SeasonModel'; import { MediaType } from '../../utils/MediaType'; import { APIModel } from '../APIModel'; -import { SeasonModel } from '../../models/SeasonModel'; import type { paths } from '../schemas/TMDB'; export class TMDBSeasonAPI extends APIModel { @@ -82,7 +84,9 @@ export class TMDBSeasonAPI extends APIModel { totalSeasons = detailsData.seasons.length; } } - } catch {} + } catch { + // Ignore errors and assume 0 seasons + } ret.push( new SeasonModel({ diff --git a/src/api/apis/TMDBSeriesAPI.ts b/src/api/apis/TMDBSeriesAPI.ts index 299663f..1c8a21f 100644 --- a/src/api/apis/TMDBSeriesAPI.ts +++ b/src/api/apis/TMDBSeriesAPI.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */ + import createClient from 'openapi-fetch'; import type MediaDbPlugin from '../../main'; import type { MediaTypeModel } from '../../models/MediaTypeModel'; diff --git a/src/api/apis/VNDBAPI.ts b/src/api/apis/VNDBAPI.ts index 3867657..0139d4e 100644 --- a/src/api/apis/VNDBAPI.ts +++ b/src/api/apis/VNDBAPI.ts @@ -257,4 +257,8 @@ export class VNDBAPI extends APIModel { }, }); } + + getDisabledMediaTypes(): MediaType[] { + return this.plugin.settings.VNDBAPI_disabledMediaTypes; + } } diff --git a/src/main.ts b/src/main.ts index 93784e8..78d2173 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,15 +13,14 @@ import { MusicBrainzAPI } from './api/apis/MusicBrainzAPI'; import { OMDbAPI } from './api/apis/OMDbAPI'; import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI'; import { SteamAPI } from './api/apis/SteamAPI'; -import { TMDBSeriesAPI } from './api/apis/TMDBSeriesAPI'; -import { TMDBSeasonAPI } from './api/apis/TMDBSeasonAPI'; import { TMDBMovieAPI } from './api/apis/TMDBMovieAPI'; -import { WikipediaAPI } from './api/apis/WikipediaAPI'; -import { ComicVineAPI } from './api/apis/ComicVineAPI'; +import { TMDBSeasonAPI } from './api/apis/TMDBSeasonAPI'; +import { TMDBSeriesAPI } from './api/apis/TMDBSeriesAPI'; import { VNDBAPI } from './api/apis/VNDBAPI'; -import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal'; +import { WikipediaAPI } from './api/apis/WikipediaAPI'; import { ConfirmOverwriteModal } from './modals/ConfirmOverwriteModal'; -import { MediaDbSeasonSelectModal } from './modals/MediaDbSeasonSelectModal'; +import type {SeasonSelectModalElement} from './modals/MediaDbSeasonSelectModal'; +import { MediaDbSeasonSelectModal } from './modals/MediaDbSeasonSelectModal'; import type { MediaTypeModel } from './models/MediaTypeModel'; import { PropertyMapper } from './settings/PropertyMapper'; import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping'; @@ -271,7 +270,7 @@ export default class MediaDbPlugin extends Plugin { private async handleSeasonSelectModal(types: string[], selectResults: MediaTypeModel[]): Promise { if (types.length === 1 && types[0] === 'season' && selectResults.length === 1 && selectResults[0].dataSource === 'TMDBSeasonAPI') { // Use static import for the modal - const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as import('./api/apis/TMDBSeasonAPI').TMDBSeasonAPI; + const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as TMDBSeasonAPI; if (!tmdbSeasonAPI) { new Notice('TMDBSeasonAPI not found.'); return true; @@ -296,8 +295,8 @@ export default class MediaDbPlugin extends Plugin { true, seriesName, ); - const selectedSeasons: any[] = await new Promise(resolve => { - modal.setSubmitCallback(resolve); + const selectedSeasons: SeasonSelectModalElement[] = await new Promise(resolve => { + modal.setSubmitCb(resolve); modal.open(); }); if (!selectedSeasons || selectedSeasons.length === 0) { @@ -309,7 +308,7 @@ export default class MediaDbPlugin extends Plugin { const orig = allSeasons.find(s => s.seasonNumber === season.season_number); if (orig) { // Fetch full metadata using getById - const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as import('./api/apis/TMDBSeasonAPI').TMDBSeasonAPI; + const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as TMDBSeasonAPI; if (tmdbSeasonAPI) { const fullMeta = await tmdbSeasonAPI.getById(orig.id); await this.createMediaDbNotes([fullMeta]); diff --git a/src/modals/MediaDbAdvancedSearchModal.ts b/src/modals/MediaDbAdvancedSearchModal.ts index 7a9a6f7..e0e8020 100644 --- a/src/modals/MediaDbAdvancedSearchModal.ts +++ b/src/modals/MediaDbAdvancedSearchModal.ts @@ -28,11 +28,11 @@ export class MediaDbAdvancedSearchModal extends Modal { this.isBusy = false; } - setSubmitCallback(submitCallback: (res: AdvancedSearchModalData) => void): void { + setSubmitCb(submitCallback: (res: AdvancedSearchModalData) => void): void { this.submitCallback = submitCallback; } - setCloseCallback(closeCallback: (err?: Error) => void): void { + setCloseCb(closeCallback: (err?: Error) => void): void { this.closeCallback = closeCallback; } diff --git a/src/modals/MediaDbIdSearchModal.ts b/src/modals/MediaDbIdSearchModal.ts index c3bc3d4..7027bb5 100644 --- a/src/modals/MediaDbIdSearchModal.ts +++ b/src/modals/MediaDbIdSearchModal.ts @@ -28,11 +28,11 @@ export class MediaDbIdSearchModal extends Modal { this.isBusy = false; } - setSubmitCallback(submitCallback: (res: IdSearchModalData, err?: Error) => void): void { + setSubmitCb(submitCallback: (res: IdSearchModalData, err?: Error) => void): void { this.submitCallback = submitCallback; } - setCloseCallback(closeCallback: (err?: Error) => void): void { + setCloseCb(closeCallback: (err?: Error) => void): void { this.closeCallback = closeCallback; } diff --git a/src/modals/MediaDbPreviewModal.ts b/src/modals/MediaDbPreviewModal.ts index 533730e..931556c 100644 --- a/src/modals/MediaDbPreviewModal.ts +++ b/src/modals/MediaDbPreviewModal.ts @@ -26,11 +26,11 @@ export class MediaDbPreviewModal extends Modal { this.markdownComponent = new Component(); } - setSubmitCallback(submitCallback: (previewModalData: PreviewModalData) => void): void { + setSubmitCb(submitCallback: (previewModalData: PreviewModalData) => void): void { this.submitCallback = submitCallback; } - setCloseCallback(closeCallback: (err?: Error) => void): void { + setCloseCb(closeCallback: (err?: Error) => void): void { this.closeCallback = closeCallback; } diff --git a/src/modals/MediaDbSearchModal.ts b/src/modals/MediaDbSearchModal.ts index c4bb7d6..986cb8a 100644 --- a/src/modals/MediaDbSearchModal.ts +++ b/src/modals/MediaDbSearchModal.ts @@ -31,11 +31,11 @@ export class MediaDbSearchModal extends Modal { this.isBusy = false; } - setSubmitCallback(submitCallback: (res: SearchModalData) => void): void { + setSubmitCb(submitCallback: (res: SearchModalData) => void): void { this.submitCallback = submitCallback; } - setCloseCallback(closeCallback: (err?: Error) => void): void { + setCloseCb(closeCallback: (err?: Error) => void): void { this.closeCallback = closeCallback; } diff --git a/src/modals/MediaDbSearchResultModal.ts b/src/modals/MediaDbSearchResultModal.ts index 1f34444..786df40 100644 --- a/src/modals/MediaDbSearchResultModal.ts +++ b/src/modals/MediaDbSearchResultModal.ts @@ -27,11 +27,11 @@ export class MediaDbSearchResultModal extends SelectModal { this.sendCallback = false; } - setSubmitCallback(submitCallback: (res: SelectModalData) => void): void { + setSubmitCb(submitCallback: (res: SelectModalData) => void): void { this.submitCallback = submitCallback; } - setCloseCallback(closeCallback: (err?: Error) => void): void { + setCloseCb(closeCallback: (err?: Error) => void): void { this.closeCallback = closeCallback; } diff --git a/src/modals/MediaDbSeasonSelectModal.ts b/src/modals/MediaDbSeasonSelectModal.ts index ca05d0e..0066c31 100644 --- a/src/modals/MediaDbSeasonSelectModal.ts +++ b/src/modals/MediaDbSeasonSelectModal.ts @@ -40,11 +40,11 @@ export class MediaDbSeasonSelectModal extends SelectModal void): void { + setSubmitCb(cb: (selectedSeasons: SeasonSelectModalElement[]) => void): void { this.submitCallback = cb; } - setCloseCallback(cb: (err?: Error) => void): void { + setCloseCb(cb: (err?: Error) => void): void { this.closeCallback = cb; } } diff --git a/src/models/SeasonModel.ts b/src/models/SeasonModel.ts index 07f9626..fc4e0a3 100644 --- a/src/models/SeasonModel.ts +++ b/src/models/SeasonModel.ts @@ -59,7 +59,7 @@ export class SeasonModel extends MediaTypeModel { migrateObject(this, obj, this); - if (!obj.hasOwnProperty('userData')) { + if (!Object.hasOwn(obj, 'userData')) { migrateObject(this.userData, obj, this.userData); } diff --git a/src/settings/PropertyMapping.ts b/src/settings/PropertyMapping.ts index 730b579..de4adaa 100644 --- a/src/settings/PropertyMapping.ts +++ b/src/settings/PropertyMapping.ts @@ -124,14 +124,16 @@ export class PropertyMapping { if (!this.property || !containsOnlyLettersAndUnderscores(this.property)) { return { res: false, - err: new PropertyMappingValidationError(`Error in property mapping "${this.toString()}": property may not be empty and only contain letters and underscores.`), + err: new PropertyMappingValidationError(`Error in property mapping "${this.toString()}": property may not be empty and may only contain letters and underscores.`), }; } if (!this.newProperty || !containsOnlyLettersAndUnderscores(this.newProperty)) { return { res: false, - err: new PropertyMappingValidationError(`Error in property mapping "${this.toString()}": new property may not be empty and only contain letters and underscores.`), + err: new PropertyMappingValidationError( + `Error in property mapping "${this.toString()}": new property may not be empty and may only contain letters and underscores.`, + ), }; } diff --git a/src/settings/PropertyMappingModelComponent.svelte b/src/settings/PropertyMappingModelComponent.svelte index 0fcb95f..064eec5 100644 --- a/src/settings/PropertyMappingModelComponent.svelte +++ b/src/settings/PropertyMappingModelComponent.svelte @@ -2,7 +2,7 @@ import { PropertyMappingModel, PropertyMappingOption, propertyMappingOptions } from './PropertyMapping'; import { capitalizeFirstLetter } from '../utils/Utils'; import Icon from './Icon.svelte'; - + interface Props { model: PropertyMappingModel; save: (model: PropertyMappingModel) => void; @@ -10,70 +10,39 @@ let { model, save }: Props = $props(); + let unsavedChanges = $state(false); + // svelte-ignore state_referenced_locally + let validationResult: { res: boolean; err?: Error } | undefined = $derived(model.validate()); + + function onModelUpdate() { + unsavedChanges = true; + model = model.copy(); + }
-
{capitalizeFirstLetter(model.type)}
+
+
{capitalizeFirstLetter(model.type)}
- - - - - - - - - - - {#each model.properties as property} - - +
+ {#if unsavedChanges} +
Unsaved changes
+ {/if} - {#if property.locked} -
- {:else} - - - - - - {/if} - - {/each} - -
PropertyMappingNew nameWikilink
- {property.property} - -
property cannot be remapped
-
- - - {#if property.mapping === PropertyMappingOption.Map} -
- - -
- {:else} - - {/if} -
+ +
+
{#if !validationResult?.res}
@@ -81,33 +50,169 @@
{/if} - +
+ + + + + + + + + + + {#each model.properties as property} + + + + {#if property.locked} + + {:else} + + + + + + {/if} + + {/each} + +
PropertyMappingNew nameWikilink
+ {property.property} + +
property cannot be remapped
+
+ + + {#if property.mapping === PropertyMappingOption.Map} +
+ + onModelUpdate()} + /> +
+ {:else} + + {/if} +
+
diff --git a/src/settings/PropertyMappingModelsComponent.svelte b/src/settings/PropertyMappingModelsComponent.svelte index 3093af0..edfd197 100644 --- a/src/settings/PropertyMappingModelsComponent.svelte +++ b/src/settings/PropertyMappingModelsComponent.svelte @@ -17,12 +17,6 @@ diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 09d6f3b..e82ef8b 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -1,6 +1,6 @@ import type { App } from 'obsidian'; -import { Notice, PluginSettingTab, Setting } from 'obsidian'; -import type { MediaType } from 'src/utils/MediaType'; +import { Notice, PluginSettingTab, SettingGroup } from 'obsidian'; +import { MediaType } from 'src/utils/MediaType'; import { mount } from 'svelte'; import type MediaDbPlugin from '../main'; import type { MediaTypeModel } from '../models/MediaTypeModel'; @@ -11,6 +11,7 @@ import PropertyMappingModelsComponent from './PropertyMappingModelsComponent.sve import { FileSuggest } from './suggesters/FileSuggest'; import { FolderSuggest } from './suggesters/FolderSuggest'; +// MARK: Settings export interface MediaDbPluginSettings { OMDbKey: string; TMDBKey: string; @@ -24,20 +25,25 @@ export interface MediaDbPluginSettings { openNoteInNewTab: boolean; useDefaultFrontMatter: boolean; enableTemplaterIntegration: boolean; - OMDbAPI_disabledMediaTypes: MediaType[]; - TMDBSeriesAPI_disabledMediaTypes: MediaType[]; - TMDBSeasonAPI_disabledMediaTypes: MediaType[]; - TMDBMovieAPI_disabledMediaTypes: MediaType[]; + imageDownload: boolean; + imageFolder: string; + + BoardgameGeekAPI_disabledMediaTypes: MediaType[]; + ComicVineAPI_disabledMediaTypes: MediaType[]; + GiantBombAPI_disabledMediaTypes: MediaType[]; MALAPI_disabledMediaTypes: MediaType[]; MALAPIManga_disabledMediaTypes: MediaType[]; - ComicVineAPI_disabledMediaTypes: MediaType[]; - SteamAPI_disabledMediaTypes: MediaType[]; MobyGamesAPI_disabledMediaTypes: MediaType[]; - GiantBombAPI_disabledMediaTypes: MediaType[]; - WikipediaAPI_disabledMediaTypes: MediaType[]; - BoardgameGeekAPI_disabledMediaTypes: MediaType[]; MusicBrainzAPI_disabledMediaTypes: MediaType[]; + OMDbAPI_disabledMediaTypes: MediaType[]; OpenLibraryAPI_disabledMediaTypes: MediaType[]; + SteamAPI_disabledMediaTypes: MediaType[]; + TMDBMovieAPI_disabledMediaTypes: MediaType[]; + TMDBSeasonAPI_disabledMediaTypes: MediaType[]; + TMDBSeriesAPI_disabledMediaTypes: MediaType[]; + VNDBAPI_disabledMediaTypes: MediaType[]; + WikipediaAPI_disabledMediaTypes: MediaType[]; + movieTemplate: string; seriesTemplate: string; seasonTemplate: string; @@ -58,16 +64,6 @@ export interface MediaDbPluginSettings { boardgameFileNameTemplate: string; bookFileNameTemplate: string; - moviePropertyConversionRules: string; - seriesPropertyConversionRules: string; - seasonPropertyConversionRules: string; - mangaPropertyConversionRules: string; - gamePropertyConversionRules: string; - wikiPropertyConversionRules: string; - musicReleasePropertyConversionRules: string; - boardgamePropertyConversionRules: string; - bookPropertyConversionRules: string; - movieFolder: string; seriesFolder: string; seasonFolder: string; @@ -78,11 +74,197 @@ export interface MediaDbPluginSettings { boardgameFolder: string; bookFolder: string; - imageDownload: boolean; - imageFolder: string; propertyMappingModels: PropertyMappingModel[]; + + // DEPRECATED: Use propertyMappingModels instead + moviePropertyConversionRules: string; + seriesPropertyConversionRules: string; + seasonPropertyConversionRules: string; + mangaPropertyConversionRules: string; + gamePropertyConversionRules: string; + wikiPropertyConversionRules: string; + musicReleasePropertyConversionRules: string; + boardgamePropertyConversionRules: string; + bookPropertyConversionRules: string; } +/** + * Helper class to get/set settings for a specific media type. + */ +class MediaTypeMappedSettings { + mediaType: MediaType; + + constructor(mediaType: MediaType) { + this.mediaType = mediaType; + } + + getTemplate(settings: MediaDbPluginSettings): string { + switch (this.mediaType) { + case MediaType.Movie: + return settings.movieTemplate; + case MediaType.Series: + return settings.seriesTemplate; + case MediaType.Season: + return settings.seasonTemplate; + case MediaType.ComicManga: + return settings.mangaTemplate; + case MediaType.Game: + return settings.gameTemplate; + case MediaType.Wiki: + return settings.wikiTemplate; + case MediaType.MusicRelease: + return settings.musicReleaseTemplate; + case MediaType.BoardGame: + return settings.boardgameTemplate; + case MediaType.Book: + return settings.bookTemplate; + } + } + + setTemplate(settings: MediaDbPluginSettings, template: string): void { + switch (this.mediaType) { + case MediaType.Movie: + settings.movieTemplate = template; + break; + case MediaType.Series: + settings.seriesTemplate = template; + break; + case MediaType.Season: + settings.seasonTemplate = template; + break; + case MediaType.ComicManga: + settings.mangaTemplate = template; + break; + case MediaType.Game: + settings.gameTemplate = template; + break; + case MediaType.Wiki: + settings.wikiTemplate = template; + break; + case MediaType.MusicRelease: + settings.musicReleaseTemplate = template; + break; + case MediaType.BoardGame: + settings.boardgameTemplate = template; + break; + case MediaType.Book: + settings.bookTemplate = template; + break; + } + } + + getFileNameTemplate(settings: MediaDbPluginSettings): string { + switch (this.mediaType) { + case MediaType.Movie: + return settings.movieFileNameTemplate; + case MediaType.Series: + return settings.seriesFileNameTemplate; + case MediaType.Season: + return settings.seasonFileNameTemplate; + case MediaType.ComicManga: + return settings.mangaFileNameTemplate; + case MediaType.Game: + return settings.gameFileNameTemplate; + case MediaType.Wiki: + return settings.wikiFileNameTemplate; + case MediaType.MusicRelease: + return settings.musicReleaseFileNameTemplate; + case MediaType.BoardGame: + return settings.boardgameFileNameTemplate; + case MediaType.Book: + return settings.bookFileNameTemplate; + } + } + + setFileNameTemplate(settings: MediaDbPluginSettings, template: string): void { + switch (this.mediaType) { + case MediaType.Movie: + settings.movieFileNameTemplate = template; + break; + case MediaType.Series: + settings.seriesFileNameTemplate = template; + break; + case MediaType.Season: + settings.seasonFileNameTemplate = template; + break; + case MediaType.ComicManga: + settings.mangaFileNameTemplate = template; + break; + case MediaType.Game: + settings.gameFileNameTemplate = template; + break; + case MediaType.Wiki: + settings.wikiFileNameTemplate = template; + break; + case MediaType.MusicRelease: + settings.musicReleaseFileNameTemplate = template; + break; + case MediaType.BoardGame: + settings.boardgameFileNameTemplate = template; + break; + case MediaType.Book: + settings.bookFileNameTemplate = template; + break; + } + } + + getFolder(settings: MediaDbPluginSettings): string { + switch (this.mediaType) { + case MediaType.Movie: + return settings.movieFolder; + case MediaType.Series: + return settings.seriesFolder; + case MediaType.Season: + return settings.seasonFolder; + case MediaType.ComicManga: + return settings.mangaFolder; + case MediaType.Game: + return settings.gameFolder; + case MediaType.Wiki: + return settings.wikiFolder; + case MediaType.MusicRelease: + return settings.musicReleaseFolder; + case MediaType.BoardGame: + return settings.boardgameFolder; + case MediaType.Book: + return settings.bookFolder; + } + } + + setFolder(settings: MediaDbPluginSettings, folder: string): void { + switch (this.mediaType) { + case MediaType.Movie: + settings.movieFolder = folder; + break; + case MediaType.Series: + settings.seriesFolder = folder; + break; + case MediaType.Season: + settings.seasonFolder = folder; + break; + case MediaType.ComicManga: + settings.mangaFolder = folder; + break; + case MediaType.Game: + settings.gameFolder = folder; + break; + case MediaType.Wiki: + settings.wikiFolder = folder; + break; + case MediaType.MusicRelease: + settings.musicReleaseFolder = folder; + break; + case MediaType.BoardGame: + settings.boardgameFolder = folder; + break; + case MediaType.Book: + settings.bookFolder = folder; + break; + } + } +} + +// MARK: Defaults const DEFAULT_SETTINGS: MediaDbPluginSettings = { OMDbKey: '', TMDBKey: '', @@ -96,20 +278,25 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = { openNoteInNewTab: true, useDefaultFrontMatter: true, enableTemplaterIntegration: false, - OMDbAPI_disabledMediaTypes: [], - TMDBSeriesAPI_disabledMediaTypes: [], - TMDBSeasonAPI_disabledMediaTypes: [], - TMDBMovieAPI_disabledMediaTypes: [], + imageDownload: false, + imageFolder: 'Media DB/images', + + BoardgameGeekAPI_disabledMediaTypes: [], + ComicVineAPI_disabledMediaTypes: [], + GiantBombAPI_disabledMediaTypes: [], MALAPI_disabledMediaTypes: [], MALAPIManga_disabledMediaTypes: [], - ComicVineAPI_disabledMediaTypes: [], - SteamAPI_disabledMediaTypes: [], MobyGamesAPI_disabledMediaTypes: [], - GiantBombAPI_disabledMediaTypes: [], - WikipediaAPI_disabledMediaTypes: [], - BoardgameGeekAPI_disabledMediaTypes: [], MusicBrainzAPI_disabledMediaTypes: [], + OMDbAPI_disabledMediaTypes: [], OpenLibraryAPI_disabledMediaTypes: [], + SteamAPI_disabledMediaTypes: [], + TMDBMovieAPI_disabledMediaTypes: [], + TMDBSeasonAPI_disabledMediaTypes: [], + TMDBSeriesAPI_disabledMediaTypes: [], + VNDBAPI_disabledMediaTypes: [], + WikipediaAPI_disabledMediaTypes: [], + movieTemplate: '', seriesTemplate: '', seasonTemplate: '', @@ -130,16 +317,6 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = { boardgameFileNameTemplate: '{{ title }} ({{ year }})', bookFileNameTemplate: '{{ title }} ({{ year }})', - moviePropertyConversionRules: '', - seriesPropertyConversionRules: '', - seasonPropertyConversionRules: '', - mangaPropertyConversionRules: '', - gamePropertyConversionRules: '', - wikiPropertyConversionRules: '', - musicReleasePropertyConversionRules: '', - boardgamePropertyConversionRules: '', - bookPropertyConversionRules: '', - movieFolder: 'Media DB/movies', seriesFolder: 'Media DB/series', seasonFolder: 'Media DB/series', @@ -150,9 +327,18 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = { boardgameFolder: 'Media DB/boardgames', bookFolder: 'Media DB/books', - imageDownload: false, - imageFolder: 'Media DB/images', propertyMappingModels: [], + + // DEPRECATED + moviePropertyConversionRules: '', + seriesPropertyConversionRules: '', + seasonPropertyConversionRules: '', + mangaPropertyConversionRules: '', + gamePropertyConversionRules: '', + wikiPropertyConversionRules: '', + musicReleasePropertyConversionRules: '', + boardgamePropertyConversionRules: '', + bookPropertyConversionRules: '', }; export const lockedPropertyMappings: string[] = ['type', 'id', 'dataSource']; @@ -219,6 +405,7 @@ export function ensureWikilinkOnPropertyMappings(settings: MediaDbPluginSettings } } +// MARK: Settings Tab export class MediaDbSettingTab extends PluginSettingTab { plugin: MediaDbPlugin; @@ -229,185 +416,245 @@ export class MediaDbSettingTab extends PluginSettingTab { display(): void { const { containerEl } = this; - containerEl.empty(); - new Setting(containerEl) - .setName('OMDb API key') - .setDesc('API key for "www.omdbapi.com".') - .addText(cb => { - cb.setPlaceholder('API key') - .setValue(this.plugin.settings.OMDbKey) - .onChange(data => { - this.plugin.settings.OMDbKey = data; - void this.plugin.saveSettings(); - }); - }); - new Setting(containerEl) - .setName('TMDB API key') - .setDesc('API key for "https://www.themoviedb.org".') - .addText(cb => { - cb.setPlaceholder('API key') - .setValue(this.plugin.settings.TMDBKey) - .onChange(data => { - this.plugin.settings.TMDBKey = data; - void this.plugin.saveSettings(); - }); - }); + const mediaTypeSettings = MEDIA_TYPES.map(mt => new MediaTypeMappedSettings(mt)); - new Setting(containerEl) - .setName('Moby Games key') - .setDesc('API key for "www.mobygames.com".') - .addText(cb => { - cb.setPlaceholder('API key') - .setValue(this.plugin.settings.MobyGamesKey) - .onChange(data => { - this.plugin.settings.MobyGamesKey = data; - void this.plugin.saveSettings(); - }); - }); + // MARK: General settings + const generalGroup = new SettingGroup(containerEl); - new Setting(containerEl) - .setName('Giant Bomb Key') - .setDesc('API key for "www.giantbomb.com".') - .addText(cb => { - cb.setPlaceholder('API key') - .setValue(this.plugin.settings.GiantBombKey) - .onChange(data => { - this.plugin.settings.GiantBombKey = data; - void this.plugin.saveSettings(); - }); - }); - new Setting(containerEl) - .setName('Comic Vine Key') - .setDesc('API key for "www.comicvine.gamespot.com".') - .addText(cb => { - cb.setPlaceholder('API key') - .setValue(this.plugin.settings.ComicVineKey) - .onChange(data => { - this.plugin.settings.ComicVineKey = data; - void this.plugin.saveSettings(); - }); - }); - new Setting(containerEl) - .setName('Boardgame Geek Key') - .setDesc('API key for "www.boardgamegeek.com".') - .addText(cb => { - cb.setPlaceholder('API key') - .setValue(this.plugin.settings.BoardgameGeekKey) - .onChange(data => { - this.plugin.settings.BoardgameGeekKey = data; - void this.plugin.saveSettings(); - }); - }); + generalGroup.addSetting( + setting => + void setting + .setName('SFW filter') + .setDesc('Only shows SFW results for APIs that offer filtering.') + .addToggle(cb => { + cb.setValue(this.plugin.settings.sfwFilter).onChange(data => { + this.plugin.settings.sfwFilter = data; + void this.plugin.saveSettings(); + }); + }), + ); - new Setting(containerEl) - .setName('SFW filter') - .setDesc('Only shows SFW results for APIs that offer filtering.') - .addToggle(cb => { - cb.setValue(this.plugin.settings.sfwFilter).onChange(data => { - this.plugin.settings.sfwFilter = data; - void this.plugin.saveSettings(); - }); - }); + generalGroup.addSetting( + setting => + void setting + .setName('Resolve {{ tags }} in templates') + .setDesc('Whether to resolve {{ tags }} in templates. The spaces inside the curly braces are important.') + .addToggle(cb => { + cb.setValue(this.plugin.settings.templates).onChange(data => { + this.plugin.settings.templates = data; + void this.plugin.saveSettings(); + }); + }), + ); - new Setting(containerEl) - .setName('Resolve {{ tags }} in templates') - .setDesc('Whether to resolve {{ tags }} in templates. The spaces inside the curly braces are important.') - .addToggle(cb => { - cb.setValue(this.plugin.settings.templates).onChange(data => { - this.plugin.settings.templates = data; - void this.plugin.saveSettings(); - }); - }); + generalGroup.addSetting( + setting => + void setting + .setName('Date format') + .setDesc( + fragWithHTML( + "Your custom date format. Use 'YYYY-MM-DD' for example.
" + + "For more syntax, refer to format reference.
" + + "Your current syntax looks like this: " + + this.plugin.dateFormatter.getPreview() + + '', + ), + ) + .addText(cb => { + cb.setPlaceholder(DEFAULT_SETTINGS.customDateFormat) + .setValue(this.plugin.settings.customDateFormat === DEFAULT_SETTINGS.customDateFormat ? '' : this.plugin.settings.customDateFormat) + .onChange(data => { + const newDateFormat = data ? data : DEFAULT_SETTINGS.customDateFormat; + this.plugin.settings.customDateFormat = newDateFormat; + const previewEl = document.getElementById('media-db-dateformat-preview'); + if (previewEl) { + previewEl.textContent = this.plugin.dateFormatter.getPreview(newDateFormat); // update preview + } + void this.plugin.saveSettings(); + }); + }), + ); - new Setting(containerEl) - .setName('Date format') - .setDesc( - fragWithHTML( - "Your custom date format. Use 'YYYY-MM-DD' for example.
" + - "For more syntax, refer to format reference.
" + - "Your current syntax looks like this: " + - this.plugin.dateFormatter.getPreview() + - '', - ), - ) - .addText(cb => { - cb.setPlaceholder(DEFAULT_SETTINGS.customDateFormat) - .setValue(this.plugin.settings.customDateFormat === DEFAULT_SETTINGS.customDateFormat ? '' : this.plugin.settings.customDateFormat) - .onChange(data => { - const newDateFormat = data ? data : DEFAULT_SETTINGS.customDateFormat; - this.plugin.settings.customDateFormat = newDateFormat; - const previewEl = document.getElementById('media-db-dateformat-preview'); - if (previewEl) { - previewEl.textContent = this.plugin.dateFormatter.getPreview(newDateFormat); // update preview - } - void this.plugin.saveSettings(); - }); - }); + generalGroup.addSetting( + setting => + void setting + .setName('Open note in new tab') + .setDesc('Open the newly created note in a new tab.') + .addToggle(cb => { + cb.setValue(this.plugin.settings.openNoteInNewTab).onChange(data => { + this.plugin.settings.openNoteInNewTab = data; + void this.plugin.saveSettings(); + }); + }), + ); - new Setting(containerEl) - .setName('Open note in new tab') - .setDesc('Open the newly created note in a new tab.') - .addToggle(cb => { - cb.setValue(this.plugin.settings.openNoteInNewTab).onChange(data => { - this.plugin.settings.openNoteInNewTab = data; - void this.plugin.saveSettings(); - }); - }); + generalGroup.addSetting( + setting => + void setting + .setName('Use default front matter') + .setDesc('Whether to use the default front matter. If disabled, the front matter from the template will be used. Same as mapping everything to remove.') + .addToggle(cb => { + cb.setValue(this.plugin.settings.useDefaultFrontMatter).onChange(data => { + this.plugin.settings.useDefaultFrontMatter = data; + void this.plugin.saveSettings(); + // Redraw settings to display/remove the property mappings + this.display(); + }); + }), + ); - new Setting(containerEl) - .setName('Use default front matter') - .setDesc('Whether to use the default front matter. If disabled, the front matter from the template will be used. Same as mapping everything to remove.') - .addToggle(cb => { - cb.setValue(this.plugin.settings.useDefaultFrontMatter).onChange(data => { - this.plugin.settings.useDefaultFrontMatter = data; - void this.plugin.saveSettings(); - // Redraw settings to display/remove the property mappings - this.display(); - }); - }); + generalGroup.addSetting( + setting => + void setting + .setName('Enable Templater integration') + .setDesc( + 'Enable integration with the templater plugin, this also needs templater to be installed. Warning: Templater allows you to execute arbitrary JavaScript code and system commands.', + ) + .addToggle(cb => { + cb.setValue(this.plugin.settings.enableTemplaterIntegration).onChange(data => { + this.plugin.settings.enableTemplaterIntegration = data; + void this.plugin.saveSettings(); + }); + }), + ); - new Setting(containerEl) - .setName('Enable Templater integration') - .setDesc( - 'Enable integration with the templater plugin, this also needs templater to be installed. Warning: Templater allows you to execute arbitrary JavaScript code and system commands.', - ) - .addToggle(cb => { - cb.setValue(this.plugin.settings.enableTemplaterIntegration).onChange(data => { - this.plugin.settings.enableTemplaterIntegration = data; - void this.plugin.saveSettings(); - }); - }); + generalGroup.addSetting( + setting => + void setting + .setName('Download images') + .setDesc('Downloads images for new notes in the folder below') + .addToggle(cb => { + cb.setValue(this.plugin.settings.imageDownload).onChange(data => { + this.plugin.settings.imageDownload = data; + void this.plugin.saveSettings(); + }); + }), + ); - new Setting(containerEl) - .setName('Download images') - .setDesc('Downloads images for new notes in the folder below') - .addToggle(cb => { - cb.setValue(this.plugin.settings.imageDownload).onChange(data => { - this.plugin.settings.imageDownload = data; - void this.plugin.saveSettings(); - }); - }); + generalGroup.addSetting( + setting => + void setting + .setName('Image folder') + .setDesc('Where downloaded images should be stored.') + .addSearch(cb => { + const suggester = new FolderSuggest(this.app, cb.inputEl); + suggester.onSelect(folder => { + cb.setValue(folder.path); + this.plugin.settings.imageFolder = folder.path; + void this.plugin.saveSettings(); + suggester.close(); + }); + cb.setPlaceholder(DEFAULT_SETTINGS.imageFolder) + .setValue(this.plugin.settings.imageFolder) + .onChange(data => { + this.plugin.settings.imageFolder = data; + void this.plugin.saveSettings(); + }); + }), + ); - new Setting(containerEl) - .setName('Image folder') - .setDesc('Where downloaded images should be stored.') - .addSearch(cb => { - const suggester = new FolderSuggest(this.app, cb.inputEl); - suggester.onSelect(folder => { - cb.setValue(folder.path); - this.plugin.settings.imageFolder = folder.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder(DEFAULT_SETTINGS.imageFolder) - .setValue(this.plugin.settings.imageFolder) - .onChange(data => { - this.plugin.settings.imageFolder = data; - void this.plugin.saveSettings(); - }); - }); + // MARK: API keys + const apiKeyGroup = new SettingGroup(containerEl); + apiKeyGroup.setHeading('API Keys'); + + apiKeyGroup.addSetting( + setting => + void setting + .setName('OMDb API key') + .setDesc('API key for "www.omdbapi.com".') + // .addComponent((el) => { + // let component = new SecretComponent(this.app, el); + + // component.setValue(this.plugin.settings.OMDbKey).onChange(data => { + // this.plugin.settings.OMDbKey = data; + // void this.plugin.saveSettings(); + // }); + + // return component; + // }) + .addText(cb => { + cb.setPlaceholder('API key') + .setValue(this.plugin.settings.OMDbKey) + .onChange(data => { + this.plugin.settings.OMDbKey = data; + void this.plugin.saveSettings(); + }); + }), + ); + apiKeyGroup.addSetting( + setting => + void setting + .setName('TMDB API key') + .setDesc('API key for "https://www.themoviedb.org".') + .addText(cb => { + cb.setPlaceholder('API key') + .setValue(this.plugin.settings.TMDBKey) + .onChange(data => { + this.plugin.settings.TMDBKey = data; + void this.plugin.saveSettings(); + }); + }), + ); + apiKeyGroup.addSetting( + setting => + void setting + .setName('Moby Games key') + .setDesc('API key for "www.mobygames.com".') + .addText(cb => { + cb.setPlaceholder('API key') + .setValue(this.plugin.settings.MobyGamesKey) + .onChange(data => { + this.plugin.settings.MobyGamesKey = data; + void this.plugin.saveSettings(); + }); + }), + ); + apiKeyGroup.addSetting( + setting => + void setting + .setName('Giant Bomb Key') + .setDesc('API key for "www.giantbomb.com".') + .addText(cb => { + cb.setPlaceholder('API key') + .setValue(this.plugin.settings.GiantBombKey) + .onChange(data => { + this.plugin.settings.GiantBombKey = data; + void this.plugin.saveSettings(); + }); + }), + ); + apiKeyGroup.addSetting( + setting => + void setting + .setName('Comic Vine Key') + .setDesc('API key for "www.comicvine.gamespot.com".') + .addText(cb => { + cb.setPlaceholder('API key') + .setValue(this.plugin.settings.ComicVineKey) + .onChange(data => { + this.plugin.settings.ComicVineKey = data; + void this.plugin.saveSettings(); + }); + }), + ); + apiKeyGroup.addSetting( + setting => + void setting + .setName('Boardgame Geek Key') + .setDesc('API key for "www.boardgamegeek.com".') + .addText(cb => { + cb.setPlaceholder('API key') + .setValue(this.plugin.settings.BoardgameGeekKey) + .onChange(data => { + this.plugin.settings.BoardgameGeekKey = data; + void this.plugin.saveSettings(); + }); + }), + ); + + // MARK: Media type settings // Create a map to store APIs for each media type const mediaTypeApiMap = new Map(); @@ -422,524 +669,146 @@ export class MediaDbSettingTab extends PluginSettingTab { } } - // Filter out media types with only one API - const filteredMediaTypes = Array.from(mediaTypeApiMap.entries()).filter(([_, apis]) => apis.length > 1); + for (const mediaTypeSetting of mediaTypeSettings) { + const mediaTypeGroup = new SettingGroup(containerEl); + const mediaType = mediaTypeSetting.mediaType; + const mediaTypeName = unCamelCase(mediaTypeSetting.mediaType); + const mediaTypeNameLower = mediaTypeName.toLowerCase(); - // Dynamically create settings based on the filtered media types and their APIs - for (const [mediaType, apis] of filteredMediaTypes) { - new Setting(containerEl).setName(`Select APIs for ${unCamelCase(mediaType)}`).setHeading(); - for (const apiName of apis) { - const api = this.plugin.apiManager.apis.find(api => api.apiName === apiName); - if (api) { - const disabledMediaTypes = api.getDisabledMediaTypes(); - new Setting(containerEl) - .setName(apiName) - .setDesc(`Use ${apiName} API for ${unCamelCase(mediaType)}.`) - .addToggle(cb => { - cb.setValue(!disabledMediaTypes.includes(mediaType)).onChange(data => { - if (data) { - const index = disabledMediaTypes.indexOf(mediaType); - if (index > -1) { - disabledMediaTypes.splice(index, 1); - } - } else { - disabledMediaTypes.push(mediaType); - } + mediaTypeGroup.setHeading(`${mediaTypeName} settings`); + + // Folder + mediaTypeGroup.addSetting( + setting => + void setting + .setName(`Import Folder`) + .setDesc(`Where newly imported ${mediaTypeNameLower} should be placed.`) + .addSearch(cb => { + const suggester = new FolderSuggest(this.app, cb.inputEl); + suggester.onSelect(folder => { + cb.setValue(folder.path); + mediaTypeSetting.setFolder(this.plugin.settings, folder.path); void this.plugin.saveSettings(); + suggester.close(); }); - }); + cb.setPlaceholder(mediaTypeSetting.getFolder(DEFAULT_SETTINGS)) + .setValue(mediaTypeSetting.getFolder(this.plugin.settings)) + .onChange(data => { + mediaTypeSetting.setFolder(this.plugin.settings, data); + void this.plugin.saveSettings(); + }); + }), + ); + + // Template + mediaTypeGroup.addSetting( + setting => + void setting + .setName(`Template`) + .setDesc(`Template file to be used when creating a new note for a ${mediaTypeNameLower}.`) + .addSearch(cb => { + const suggester = new FileSuggest(this.app, cb.inputEl); + suggester.onSelect(file => { + cb.setValue(file.path); + mediaTypeSetting.setTemplate(this.plugin.settings, file.path); + void this.plugin.saveSettings(); + suggester.close(); + }); + cb.setPlaceholder(`Example: ${mediaTypeNameLower}Template.md`) + .setValue(mediaTypeSetting.getTemplate(this.plugin.settings)) + .onChange(data => { + mediaTypeSetting.setTemplate(this.plugin.settings, data); + void this.plugin.saveSettings(); + }); + }), + ); + + // File name template + mediaTypeGroup.addSetting( + setting => + void setting + .setName(`File name template`) + .setDesc(`Template for the file name used when creating a new note for a ${mediaTypeNameLower}.`) + .addText(cb => { + cb.setPlaceholder(`Example: ${mediaTypeSetting.getFileNameTemplate(DEFAULT_SETTINGS)}`) + .setValue(mediaTypeSetting.getFileNameTemplate(this.plugin.settings)) + .onChange(data => { + mediaTypeSetting.setFileNameTemplate(this.plugin.settings, data); + void this.plugin.saveSettings(); + }); + }), + ); + + // APIs + const apis = mediaTypeApiMap.get(mediaType) ?? []; + if (apis.length > 1) { + for (const apiName of apis) { + const api = this.plugin.apiManager.apis.find(api => api.apiName === apiName); + if (api) { + const disabledMediaTypes = api.getDisabledMediaTypes(); + + mediaTypeGroup.addSetting( + setting => + void setting + .setName(apiName) + .setDesc(`Use ${apiName} API for ${unCamelCase(mediaType)}.`) + .addToggle(cb => { + cb.setValue(!disabledMediaTypes.includes(mediaType)).onChange(data => { + if (data) { + const index = disabledMediaTypes.indexOf(mediaType); + if (index != -1) { + disabledMediaTypes.splice(index, 1); + } + } else { + disabledMediaTypes.push(mediaType); + } + void this.plugin.saveSettings(); + }); + }), + ); + } } } } - new Setting(containerEl).setName('New file location').setHeading(); - // region new file location - new Setting(containerEl) - .setName('Movie folder') - .setDesc('Where newly imported movies should be placed.') - .addSearch(cb => { - const suggester = new FolderSuggest(this.app, cb.inputEl); - suggester.onSelect(folder => { - cb.setValue(folder.path); - this.plugin.settings.movieFolder = folder.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder(DEFAULT_SETTINGS.movieFolder) - .setValue(this.plugin.settings.movieFolder) - .onChange(data => { - this.plugin.settings.movieFolder = data; - void this.plugin.saveSettings(); - }); - }); + // MARK: Property mappings - new Setting(containerEl) - .setName('Series folder') - .setDesc('Where newly imported series should be placed.') - .addSearch(cb => { - const suggester = new FolderSuggest(this.app, cb.inputEl); - suggester.onSelect(folder => { - cb.setValue(folder.path); - this.plugin.settings.seriesFolder = folder.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder(DEFAULT_SETTINGS.seriesFolder) - .setValue(this.plugin.settings.seriesFolder) - .onChange(data => { - this.plugin.settings.seriesFolder = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Season folder') - .setDesc('Where newly imported seasons should be placed.') - .addSearch(cb => { - new FolderSuggest(this.app, cb.inputEl); - cb.setPlaceholder(DEFAULT_SETTINGS.seasonFolder) - .setValue(this.plugin.settings.seriesFolder) - .onChange(data => { - this.plugin.settings.seasonFolder = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Comic and manga folder') - .setDesc('Where newly imported comics and manga should be placed.') - .addSearch(cb => { - const suggester = new FolderSuggest(this.app, cb.inputEl); - suggester.onSelect(folder => { - cb.setValue(folder.path); - this.plugin.settings.mangaFolder = folder.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder(DEFAULT_SETTINGS.mangaFolder) - .setValue(this.plugin.settings.mangaFolder) - .onChange(data => { - this.plugin.settings.mangaFolder = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Game folder') - .setDesc('Where newly imported games should be placed.') - .addSearch(cb => { - const suggester = new FolderSuggest(this.app, cb.inputEl); - suggester.onSelect(folder => { - cb.setValue(folder.path); - this.plugin.settings.gameFolder = folder.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder(DEFAULT_SETTINGS.gameFolder) - .setValue(this.plugin.settings.gameFolder) - .onChange(data => { - this.plugin.settings.gameFolder = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Wiki folder') - .setDesc('Where newly imported wiki articles should be placed.') - .addSearch(cb => { - const suggester = new FolderSuggest(this.app, cb.inputEl); - suggester.onSelect(folder => { - cb.setValue(folder.path); - this.plugin.settings.wikiFolder = folder.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder(DEFAULT_SETTINGS.wikiFolder) - .setValue(this.plugin.settings.wikiFolder) - .onChange(data => { - this.plugin.settings.wikiFolder = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Music folder') - .setDesc('Where newly imported music should be placed.') - .addSearch(cb => { - const suggester = new FolderSuggest(this.app, cb.inputEl); - suggester.onSelect(folder => { - cb.setValue(folder.path); - this.plugin.settings.musicReleaseFolder = folder.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder(DEFAULT_SETTINGS.musicReleaseFolder) - .setValue(this.plugin.settings.musicReleaseFolder) - .onChange(data => { - this.plugin.settings.musicReleaseFolder = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Board game folder') - .setDesc('Where newly imported board games should be places.') - .addSearch(cb => { - const suggester = new FolderSuggest(this.app, cb.inputEl); - suggester.onSelect(folder => { - cb.setValue(folder.path); - this.plugin.settings.boardgameFolder = folder.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder(DEFAULT_SETTINGS.boardgameFolder) - .setValue(this.plugin.settings.boardgameFolder) - .onChange(data => { - this.plugin.settings.boardgameFolder = data; - void this.plugin.saveSettings(); - }); - }); - new Setting(containerEl) - .setName('Book folder') - .setDesc('Where newly imported books should be placed.') - .addSearch(cb => { - const suggester = new FolderSuggest(this.app, cb.inputEl); - suggester.onSelect(folder => { - cb.setValue(folder.path); - this.plugin.settings.bookFolder = folder.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder(DEFAULT_SETTINGS.bookFolder) - .setValue(this.plugin.settings.bookFolder) - .onChange(data => { - this.plugin.settings.bookFolder = data; - void this.plugin.saveSettings(); - }); - }); - - // endregion - - new Setting(containerEl).setName('Template settings').setHeading(); - // region templates - new Setting(containerEl) - .setName('Movie template') - .setDesc('Template file to be used when creating a new note for a movie.') - .addSearch(cb => { - const suggester = new FileSuggest(this.app, cb.inputEl); - suggester.onSelect(file => { - cb.setValue(file.path); - this.plugin.settings.movieTemplate = file.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder('Example: movieTemplate.md') - .setValue(this.plugin.settings.movieTemplate) - .onChange(data => { - this.plugin.settings.movieTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Series template') - .setDesc('Template file to be used when creating a new note for a series.') - .addSearch(cb => { - const suggester = new FileSuggest(this.app, cb.inputEl); - suggester.onSelect(file => { - cb.setValue(file.path); - this.plugin.settings.seriesTemplate = file.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder('Example: seriesTemplate.md') - .setValue(this.plugin.settings.seriesTemplate) - .onChange(data => { - this.plugin.settings.seriesTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Season template') - .setDesc('Template file to be used when creating a new note for a season.') - .addSearch(cb => { - new FileSuggest(this.app, cb.inputEl); - cb.setPlaceholder('Example: seasonTemplate.md') - .setValue(this.plugin.settings.seasonTemplate) - .onChange(data => { - this.plugin.settings.seasonTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Manga and Comics template') - .setDesc('Template file to be used when creating a new note for a manga or a comic.') - .addSearch(cb => { - const suggester = new FileSuggest(this.app, cb.inputEl); - suggester.onSelect(file => { - cb.setValue(file.path); - this.plugin.settings.mangaTemplate = file.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder('Example: mangaTemplate.md') - .setValue(this.plugin.settings.mangaTemplate) - .onChange(data => { - this.plugin.settings.mangaTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Game template') - .setDesc('Template file to be used when creating a new note for a game.') - .addSearch(cb => { - const suggester = new FileSuggest(this.app, cb.inputEl); - suggester.onSelect(file => { - cb.setValue(file.path); - this.plugin.settings.gameTemplate = file.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder('Example: gameTemplate.md') - .setValue(this.plugin.settings.gameTemplate) - .onChange(data => { - this.plugin.settings.gameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Wiki template') - .setDesc('Template file to be used when creating a new note for a wiki entry.') - .addSearch(cb => { - const suggester = new FileSuggest(this.app, cb.inputEl); - suggester.onSelect(file => { - cb.setValue(file.path); - this.plugin.settings.wikiTemplate = file.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder('Example: wikiTemplate.md') - .setValue(this.plugin.settings.wikiTemplate) - .onChange(data => { - this.plugin.settings.wikiTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Music release template') - .setDesc('Template file to be used when creating a new note for a music release.') - .addSearch(cb => { - const suggester = new FileSuggest(this.app, cb.inputEl); - suggester.onSelect(file => { - cb.setValue(file.path); - this.plugin.settings.musicReleaseTemplate = file.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder('Example: musicReleaseTemplate.md') - .setValue(this.plugin.settings.musicReleaseTemplate) - .onChange(data => { - this.plugin.settings.musicReleaseTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Board game template') - .setDesc('Template file to be used when creating a new note for a boardgame.') - .addSearch(cb => { - const suggester = new FileSuggest(this.app, cb.inputEl); - suggester.onSelect(file => { - cb.setValue(file.path); - this.plugin.settings.boardgameTemplate = file.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder('Example: boardgameTemplate.md') - .setValue(this.plugin.settings.boardgameTemplate) - .onChange(data => { - this.plugin.settings.boardgameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Book template') - .setDesc('Template file to be used when creating a new note for a book.') - .addSearch(cb => { - const suggester = new FileSuggest(this.app, cb.inputEl); - suggester.onSelect(file => { - cb.setValue(file.path); - this.plugin.settings.bookTemplate = file.path; - void this.plugin.saveSettings(); - suggester.close(); - }); - cb.setPlaceholder('Example: bookTemplate.md') - .setValue(this.plugin.settings.bookTemplate) - .onChange(data => { - this.plugin.settings.bookTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - // endregion - - new Setting(containerEl).setName('File name settings').setHeading(); - // region file name templates - new Setting(containerEl) - .setName('Movie file name template') - .setDesc('Template for the file name used when creating a new note for a movie.') - .addText(cb => { - cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.movieFileNameTemplate}`) - .setValue(this.plugin.settings.movieFileNameTemplate) - .onChange(data => { - this.plugin.settings.movieFileNameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Series file name template') - .setDesc('Template for the file name used when creating a new note for a series.') - .addText(cb => { - cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.seriesFileNameTemplate}`) - .setValue(this.plugin.settings.seriesFileNameTemplate) - .onChange(data => { - this.plugin.settings.seriesFileNameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Season file name template') - .setDesc('Template for the file name used when creating a new note for a season.') - .addText(cb => { - cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.seasonFileNameTemplate}`) - .setValue(this.plugin.settings.seasonFileNameTemplate) - .onChange(data => { - this.plugin.settings.seasonFileNameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Manga and comic file name template') - .setDesc('Template for the file name used when creating a new note for a manga or comic.') - .addText(cb => { - cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.mangaFileNameTemplate}`) - .setValue(this.plugin.settings.mangaFileNameTemplate) - .onChange(data => { - this.plugin.settings.mangaFileNameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Game file name template') - .setDesc('Template for the file name used when creating a new note for a game.') - .addText(cb => { - cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.gameFileNameTemplate}`) - .setValue(this.plugin.settings.gameFileNameTemplate) - .onChange(data => { - this.plugin.settings.gameFileNameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Wiki file name template') - .setDesc('Template for the file name used when creating a new note for a wiki entry.') - .addText(cb => { - cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.wikiFileNameTemplate}`) - .setValue(this.plugin.settings.wikiFileNameTemplate) - .onChange(data => { - this.plugin.settings.wikiFileNameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Music release file name template') - .setDesc('Template for the file name used when creating a new note for a music release.') - .addText(cb => { - cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.musicReleaseFileNameTemplate}`) - .setValue(this.plugin.settings.musicReleaseFileNameTemplate) - .onChange(data => { - this.plugin.settings.musicReleaseFileNameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Board game file name template') - .setDesc('Template for the file name used when creating a new note for a boardgame.') - .addText(cb => { - cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.boardgameFileNameTemplate}`) - .setValue(this.plugin.settings.boardgameFileNameTemplate) - .onChange(data => { - this.plugin.settings.boardgameFileNameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('Book file name template') - .setDesc('Template for the file name used when creating a new note for a book.') - .addText(cb => { - cb.setPlaceholder(`Example: ${DEFAULT_SETTINGS.bookFileNameTemplate}`) - .setValue(this.plugin.settings.bookFileNameTemplate) - .onChange(data => { - this.plugin.settings.bookFileNameTemplate = data; - void this.plugin.saveSettings(); - }); - }); - - // endregion - - // region Property Mappings if (this.plugin.settings.useDefaultFrontMatter) { - new Setting(containerEl).setName('Property mappings').setHeading(); + const mappingGroup = new SettingGroup(containerEl); + mappingGroup.setHeading('Property mappings'); + mappingGroup.addSetting(setting => { + setting + .setName('Property mappings explanation') + .setDesc( + fragWithHTML( + '

Here you can customize how metadata fields are mapped to property names in the front matter of the created notes.

' + + '

You can choose to keep the original name, rename the property, or remove it entirely.

' + + '

Remember to save your changes using the save button for each individual category.

', + ), + ); - const propertyMappingExplanation = containerEl.createEl('div'); - propertyMappingExplanation.innerHTML = ` -

Choose how metadata fields are mapped to property names. The options are:

-
    -
  • default: keep the original name.
  • -
  • remap: rename the property.
  • -
  • remove: remove the property entirely.
  • -
-

- Don't forget to save your changes using the save button for each individual category. -

`; + mount(PropertyMappingModelsComponent, { + target: setting.descEl, + props: { + models: this.plugin.settings.propertyMappingModels.map(x => x.copy()), + save: (model: PropertyMappingModel): void => { + const propertyMappingModels: PropertyMappingModel[] = []; - mount(PropertyMappingModelsComponent, { - target: this.containerEl, - props: { - models: this.plugin.settings.propertyMappingModels.map(x => x.copy()), - save: (model: PropertyMappingModel): void => { - const propertyMappingModels: PropertyMappingModel[] = []; - - for (const model2 of this.plugin.settings.propertyMappingModels) { - if (model2.type === model.type) { - propertyMappingModels.push(model); - } else { - propertyMappingModels.push(model2); + for (const model2 of this.plugin.settings.propertyMappingModels) { + if (model2.type === model.type) { + propertyMappingModels.push(model); + } else { + propertyMappingModels.push(model2); + } } - } - this.plugin.settings.propertyMappingModels = propertyMappingModels; - new Notice(`MDB: Property mappings for ${model.type} saved successfully.`); - void this.plugin.saveSettings(); + this.plugin.settings.propertyMappingModels = propertyMappingModels; + new Notice(`MDB: Property mappings for ${model.type} saved successfully.`); + void this.plugin.saveSettings(); + }, }, - }, + }); }); } - - // endregion } } diff --git a/src/utils/MediaTypeManager.ts b/src/utils/MediaTypeManager.ts index b9e1419..d968573 100644 --- a/src/utils/MediaTypeManager.ts +++ b/src/utils/MediaTypeManager.ts @@ -7,24 +7,25 @@ import { GameModel } from '../models/GameModel'; import type { MediaTypeModel } from '../models/MediaTypeModel'; import { MovieModel } from '../models/MovieModel'; import { MusicReleaseModel } from '../models/MusicReleaseModel'; -import { SeriesModel } from '../models/SeriesModel'; import { SeasonModel } from '../models/SeasonModel'; +import { SeriesModel } from '../models/SeriesModel'; import { WikiModel } from '../models/WikiModel'; import type { MediaDbPluginSettings } from '../settings/Settings'; import { ILLEGAL_FILENAME_CHARACTERS } from './IllegalFilenameCharactersList'; import { MediaType } from './MediaType'; import { replaceTags } from './Utils'; +// All media types in alphabetical order export const MEDIA_TYPES: MediaType[] = [ - MediaType.Movie, - MediaType.Series, - MediaType.Season, - MediaType.ComicManga, - MediaType.Game, - MediaType.Wiki, - MediaType.MusicRelease, MediaType.BoardGame, MediaType.Book, + MediaType.ComicManga, + MediaType.Game, + MediaType.Movie, + MediaType.MusicRelease, + MediaType.Series, + MediaType.Season, + MediaType.Wiki, ]; export class MediaTypeManager { diff --git a/src/utils/ModalHelper.ts b/src/utils/ModalHelper.ts index 79c7c7f..7efba29 100644 --- a/src/utils/ModalHelper.ts +++ b/src/utils/ModalHelper.ts @@ -236,8 +236,8 @@ export class ModalHelper { async createSearchModal(searchModalOptions: SearchModalOptions): Promise<{ searchModalResult: SearchModalResult; searchModal: MediaDbSearchModal }> { const modal = new MediaDbSearchModal(this.plugin, searchModalOptions); const res: SearchModalResult = await new Promise(resolve => { - modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); - modal.setCloseCallback(err => { + modal.setSubmitCb(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); + modal.setCloseCb(err => { if (err) { resolve({ code: ModalResultCode.ERROR, error: err }); } @@ -302,8 +302,8 @@ export class ModalHelper { ): Promise<{ advancedSearchModalResult: AdvancedSearchModalResult; advancedSearchModal: MediaDbAdvancedSearchModal }> { const modal = new MediaDbAdvancedSearchModal(this.plugin, advancedSearchModalOptions); const res: AdvancedSearchModalResult = await new Promise(resolve => { - modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); - modal.setCloseCallback(err => { + modal.setSubmitCb(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); + modal.setCloseCb(err => { if (err) { resolve({ code: ModalResultCode.ERROR, error: err }); } @@ -366,8 +366,8 @@ export class ModalHelper { async createIdSearchModal(idSearchModalOptions: IdSearchModalOptions): Promise<{ idSearchModalResult: IdSearchModalResult; idSearchModal: MediaDbIdSearchModal }> { const modal = new MediaDbIdSearchModal(this.plugin, idSearchModalOptions); const res: IdSearchModalResult = await new Promise(resolve => { - modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); - modal.setCloseCallback(err => { + modal.setSubmitCb(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); + modal.setCloseCb(err => { if (err) { resolve({ code: ModalResultCode.ERROR, error: err }); } @@ -430,9 +430,9 @@ export class ModalHelper { async createSelectModal(selectModalOptions: SelectModalOptions): Promise<{ selectModalResult: SelectModalResult; selectModal: MediaDbSearchResultModal }> { const modal = new MediaDbSearchResultModal(this.plugin, selectModalOptions); const res: SelectModalResult = await new Promise(resolve => { - modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); + modal.setSubmitCb(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); modal.setSkipCallback(() => resolve({ code: ModalResultCode.SKIP })); - modal.setCloseCallback(err => { + modal.setCloseCb(err => { if (err) { resolve({ code: ModalResultCode.ERROR, error: err }); } @@ -493,8 +493,8 @@ export class ModalHelper { //todo: handle attachFile for existing files const modal = new MediaDbPreviewModal(this.plugin, previewModalOptions); const res: PreviewModalResult = await new Promise(resolve => { - modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); - modal.setCloseCallback(err => { + modal.setSubmitCb(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); + modal.setCloseCb(err => { if (err) { resolve({ code: ModalResultCode.ERROR, error: err }); } diff --git a/src/utils/SeasonModalHelper.ts b/src/utils/SeasonModalHelper.ts deleted file mode 100644 index 93002df..0000000 --- a/src/utils/SeasonModalHelper.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { App } from 'obsidian'; -import type { SeasonSelectModalElement } from '../modals/MediaDbSeasonSelectModal'; -import { MediaDbSeasonSelectModal } from '../modals/MediaDbSeasonSelectModal'; - -export async function openSeasonSelectModal(app: App, plugin: any, seasons: SeasonSelectModalElement[]): Promise { - return new Promise(resolve => { - const modal = new MediaDbSeasonSelectModal(plugin, seasons, true); - modal.setSubmitCallback(selected => { - resolve(selected); - }); - modal.setCloseCallback(() => { - resolve(undefined); - }); - modal.open(); - }); -} diff --git a/styles.css b/styles.css index 6d4bb8d..b7351e4 100644 --- a/styles.css +++ b/styles.css @@ -64,55 +64,6 @@ small.media-db-plugin-list-text { margin-bottom: 10px; } -/* region property mappings */ -.media-db-plugin-property-mappings-model-container { - border: 1px solid var(--background-modifier-border); - border-radius: 5px; - padding: 10px; - width: 100%; -} - -.media-db-plugin-property-mappings-container { - margin: 10px 0; - display: flex; - flex-direction: column; - gap: 5px; -} - -.media-db-plugin-property-mapping-element { - display: flex; - flex-direction: row; - gap: 10px; -} - -.media-db-plugin-property-mapping-element-property-name-wrapper { - min-width: 160px; - background: var(--background-modifier-form-field); - padding: 2px 5px; - border-radius: 5px; - - display: flex; - align-items: center; -} - -.media-db-plugin-property-mapping-element-property-name { - margin: 0; -} - -.media-db-plugin-property-mappings-save-button { - margin: 0; -} - -.media-db-plugin-property-mapping-to { - display: flex; - align-items: center; -} - -.media-db-plugin-property-mapping-validation { - color: var(--text-error); - margin-bottom: 5px; -} - .media-db-plugin-button:focus { /*outline: 1px solid white;*/ } @@ -122,5 +73,3 @@ small.media-db-plugin-list-text { border: var(--modal-border-width) solid var(--modal-border-color); padding: var(--size-4-4); } - -/* endregion */ diff --git a/version-bump.mjs b/version-bump.mjs deleted file mode 100644 index 3f6c905..0000000 --- a/version-bump.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { readFileSync, writeFileSync } from 'fs'; - -const targetVersion = process.env.npm_package_version; - -// read minAppVersion from manifest.json and bump version to target version -let manifest = JSON.parse(readFileSync('manifest.json', 'utf8')); -const { minAppVersion } = manifest; -manifest.version = targetVersion; -writeFileSync('manifest.json', JSON.stringify(manifest, null, '\t')); - -// update versions.json with target version and minAppVersion from manifest.json -let versions = JSON.parse(readFileSync('versions.json', 'utf8')); -versions[targetVersion] = minAppVersion; -writeFileSync('versions.json', JSON.stringify(versions, null, '\t'));