From 97cf350a3202a106a316535533fc31498fc58cca Mon Sep 17 00:00:00 2001 From: Moritz Jung Date: Wed, 28 Jan 2026 21:41:06 +0100 Subject: [PATCH] update settings; fix some minor issues --- .gitignore | 3 +- bun.lockb | Bin 181474 -> 187922 bytes package.json | 24 +- src/api/apis/TMDBMovieAPI.ts | 2 + src/api/apis/TMDBSeasonAPI.ts | 8 +- src/api/apis/TMDBSeriesAPI.ts | 2 + src/api/apis/VNDBAPI.ts | 4 + src/main.ts | 19 +- src/modals/MediaDbAdvancedSearchModal.ts | 4 +- src/modals/MediaDbIdSearchModal.ts | 4 +- src/modals/MediaDbPreviewModal.ts | 4 +- src/modals/MediaDbSearchModal.ts | 4 +- src/modals/MediaDbSearchResultModal.ts | 4 +- src/modals/MediaDbSeasonSelectModal.ts | 4 +- src/models/SeasonModel.ts | 2 +- src/settings/PropertyMapping.ts | 6 +- .../PropertyMappingModelComponent.svelte | 317 ++-- .../PropertyMappingModelsComponent.svelte | 6 - src/settings/Settings.ts | 1297 ++++++++--------- src/utils/MediaTypeManager.ts | 17 +- src/utils/ModalHelper.ts | 20 +- src/utils/SeasonModalHelper.ts | 16 - styles.css | 51 - version-bump.mjs | 14 - 24 files changed, 882 insertions(+), 950 deletions(-) delete mode 100644 src/utils/SeasonModalHelper.ts delete mode 100644 version-bump.mjs 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 88a3ee5f70111cd8f58262868a4e3975e46cf3ca..f7a1bfcfb80075b55d3061b0138f7361173df187 100755 GIT binary patch delta 49159 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

PuS4Gy#!B^EqDcB?phY%st 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 U_}N10XM6ukI&9+N>8ASs2lhoLi2wiq 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'));