big changes; we have a good TS config now

This commit is contained in:
Moritz Jung 2025-01-14 22:30:28 +01:00
parent cbbaf54b33
commit 4497991344
54 changed files with 750 additions and 671 deletions

View file

@ -1,2 +0,0 @@
npm node_modules
build

View file

@ -1,20 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": { "node": true },
"plugins": ["@typescript-eslint", "only-warn"],
"extends": ["eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended"],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/ban-ts-comment": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-function-return-type": ["warn"]
}
}

View file

@ -112,7 +112,7 @@ Now you select the result you want and the plugin will cast it's magic and creat
### Currently supported APIs: ### Currently supported APIs:
| Name | Description | Supported formats | Authentification | Rate limiting | SFW filter support | | Name | Description | Supported formats | Authentification | Rate limiting | SFW filter support |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| [Jikan](https://jikan.moe/) | Jikan is an API that uses [My Anime List](https://myanimelist.net) and offers metadata for anime. | series, movies, specials, OVAs, manga, manwha, novels | No | 60 per minute and 3 per second | Yes | | [Jikan](https://jikan.moe/) | Jikan is an API that uses [My Anime List](https://myanimelist.net) and offers metadata for anime. | series, movies, specials, OVAs, manga, manwha, novels | No | 60 per minute and 3 per second | Yes |
| [OMDb](https://www.omdbapi.com/) | OMDb is an API that offers metadata for movie, series and games. | series, movies, games | Yes, you can get a free key here [here](https://www.omdbapi.com/apikey.aspx) | 1000 per day | No | | [OMDb](https://www.omdbapi.com/) | OMDb is an API that offers metadata for movie, series and games. | series, movies, games | Yes, you can get a free key here [here](https://www.omdbapi.com/apikey.aspx) | 1000 per day | No |
| [MusicBrainz](https://musicbrainz.org/) | MusicBrainz is an API that offers information about music releases. | music releases | No | 50 per second | No | | [MusicBrainz](https://musicbrainz.org/) | MusicBrainz is an API that offers information about music releases. | music releases | No | 50 per second | No |

View file

@ -1,7 +1,7 @@
import builtins from 'builtin-modules'; import builtins from 'builtin-modules';
import esbuild from 'esbuild'; import esbuild from 'esbuild';
import esbuildSvelte from 'esbuild-svelte'; import esbuildSvelte from 'esbuild-svelte';
import sveltePreprocess from 'svelte-preprocess'; import { sveltePreprocess } from 'svelte-preprocess';
import { getBuildBanner } from 'build/buildBanner'; import { getBuildBanner } from 'build/buildBanner';
const banner = getBuildBanner('Release Build', version => version); const banner = getBuildBanner('Release Build', version => version);
@ -41,7 +41,7 @@ const build = await esbuild.build({
}, },
plugins: [ plugins: [
esbuildSvelte({ esbuildSvelte({
compilerOptions: { css: 'injected', dev: false, sveltePath: 'svelte' }, compilerOptions: { css: 'injected', dev: false },
preprocess: sveltePreprocess(), preprocess: sveltePreprocess(),
filterWarnings: warning => { filterWarnings: warning => {
// we don't want warnings from node modules that we can do nothing about // we don't want warnings from node modules that we can do nothing about

View file

@ -1,7 +1,7 @@
import esbuild from 'esbuild'; import esbuild from 'esbuild';
import copy from 'esbuild-plugin-copy-watch'; import copy from 'esbuild-plugin-copy-watch';
import esbuildSvelte from 'esbuild-svelte'; import esbuildSvelte from 'esbuild-svelte';
import sveltePreprocess from 'svelte-preprocess'; import { sveltePreprocess } from 'svelte-preprocess';
import manifest from '../../manifest.json' assert { type: 'json' }; import manifest from '../../manifest.json' assert { type: 'json' };
import { getBuildBanner } from 'build/buildBanner'; import { getBuildBanner } from 'build/buildBanner';
@ -52,7 +52,7 @@ const context = await esbuild.context({
], ],
}), }),
esbuildSvelte({ esbuildSvelte({
compilerOptions: { css: 'injected', dev: true, sveltePath: 'svelte' }, compilerOptions: { css: 'injected', dev: true },
preprocess: sveltePreprocess(), preprocess: sveltePreprocess(),
filterWarnings: warning => { filterWarnings: warning => {
// we don't want warnings from node modules that we can do nothing about // we don't want warnings from node modules that we can do nothing about

BIN
bun.lockb

Binary file not shown.

54
eslint.config.mjs Normal file
View file

@ -0,0 +1,54 @@
// @ts-check
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import only_warn from 'eslint-plugin-only-warn';
import * as plugin_import from 'eslint-plugin-import';
export default tseslint.config(
{
ignores: ['npm/', 'node_modules/', 'exampleVault/', 'automation/', 'main.js', '*.svelte'],
},
{
files: ['src/**/*.ts'],
extends: [eslint.configs.recommended, ...tseslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.stylisticTypeChecked],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: true,
},
},
plugins: {
// @ts-ignore
'only-warn': only_warn,
import: plugin_import,
},
rules: {
'@typescript-eslint/no-explicit-any': ['warn'],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
],
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports', fixStyle: 'separate-type-imports' }],
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'import/order': [
'error',
{
'newlines-between': 'never',
alphabetize: { order: 'asc', orderImportKind: 'asc', caseInsensitive: true },
},
],
'@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }],
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
'@typescript-eslint/explicit-function-return-type': ['warn'],
'@typescript-eslint/require-await': 'off',
},
},
);

View file

@ -14,8 +14,8 @@
"lint": "eslint --max-warnings=0 src/**", "lint": "eslint --max-warnings=0 src/**",
"lint:fix": "eslint --max-warnings=0 --fix src/**", "lint:fix": "eslint --max-warnings=0 --fix src/**",
"svelte-check": "svelte-check --compiler-warnings \"unused-export-let:ignore\"", "svelte-check": "svelte-check --compiler-warnings \"unused-export-let:ignore\"",
"check": "bun run format:check && bun run tsc && bun run lint && bun run test", "check": "bun run format:check && bun run tsc && bun run test",
"check:fix": "bun run format && bun run tsc && bun run lint:fix && bun run test", "check:fix": "bun run format && bun run tsc && bun run test",
"release": "bun run automation/release.ts", "release": "bun run automation/release.ts",
"stats": "bun run automation/stats.ts" "stats": "bun run automation/stats.ts"
}, },
@ -25,27 +25,24 @@
"devDependencies": { "devDependencies": {
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@lemons_dev/parsinom": "^0.0.12", "@lemons_dev/parsinom": "^0.0.12",
"@happy-dom/global-registrator": "^14.3.6", "@happy-dom/global-registrator": "^14.12.3",
"@tsconfig/svelte": "^5.0.3", "@types/bun": "^1.1.16",
"@types/bun": "^1.0.10", "builtin-modules": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^7.3.1", "esbuild": "^0.24.2",
"@typescript-eslint/parser": "^7.3.1", "esbuild-plugin-copy-watch": "^2.3.1",
"builtin-modules": "^3.3.0", "esbuild-svelte": "^0.8.2",
"esbuild": "^0.20.2", "eslint": "^9.18.0",
"esbuild-plugin-copy-watch": "^2.1.0", "eslint-plugin-import": "^2.31.0",
"esbuild-svelte": "^0.8.0",
"eslint": "^8.57.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-isaacscript": "^3.12.2",
"eslint-plugin-only-warn": "^1.1.0", "eslint-plugin-only-warn": "^1.1.0",
"obsidian": "latest", "obsidian": "latest",
"prettier": "^3.2.5", "prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.2.2", "prettier-plugin-svelte": "^3.3.3",
"string-argv": "^0.3.2", "string-argv": "^0.3.2",
"svelte": "^4.2.12", "svelte": "^5.17.5",
"svelte-check": "^3.6.8", "svelte-check": "^4.1.4",
"svelte-preprocess": "^5.1.3", "svelte-preprocess": "^6.0.3",
"tslib": "^2.6.2", "tslib": "^2.8.1",
"typescript": "^5.4.3" "typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
} }
} }

View file

@ -1,5 +1,6 @@
import { APIModel } from './APIModel'; import { Notice } from 'obsidian';
import { MediaTypeModel } from '../models/MediaTypeModel'; import type { MediaTypeModel } from '../models/MediaTypeModel';
import type { APIModel } from './APIModel';
export class APIManager { export class APIManager {
apis: APIModel[]; apis: APIModel[];
@ -23,7 +24,10 @@ export class APIManager {
try { try {
return await api.searchByTitle(query); return await api.searchByTitle(query);
} catch (e) { } catch (e) {
new Notice(`Error querying ${api.apiName}: ${e}`);
console.warn(e); console.warn(e);
return [];
} }
}); });
@ -35,7 +39,7 @@ export class APIManager {
* *
* @param item * @param item
*/ */
async queryDetailedInfo(item: MediaTypeModel): Promise<MediaTypeModel> { async queryDetailedInfo(item: MediaTypeModel): Promise<MediaTypeModel | undefined> {
return await this.queryDetailedInfoById(item.id, item.dataSource); return await this.queryDetailedInfoById(item.id, item.dataSource);
} }
@ -45,22 +49,31 @@ export class APIManager {
* @param id * @param id
* @param apiName * @param apiName
*/ */
async queryDetailedInfoById(id: string, apiName: string): Promise<MediaTypeModel> { async queryDetailedInfoById(id: string, apiName: string): Promise<MediaTypeModel | undefined> {
for (const api of this.apis) { for (const api of this.apis) {
if (api.apiName === apiName) { if (api.apiName === apiName) {
try {
return api.getById(id); return api.getById(id);
} catch (e) {
new Notice(`Error querying ${api.apiName}: ${e}`);
console.warn(e);
return undefined;
} }
} }
} }
getApiByName(name: string): APIModel { return undefined;
}
getApiByName(name: string): APIModel | undefined {
for (const api of this.apis) { for (const api of this.apis) {
if (api.apiName === name) { if (api.apiName === name) {
return api; return api;
} }
} }
return null; return undefined;
} }
registerAPI(api: APIModel): void { registerAPI(api: APIModel): void {

View file

@ -1,13 +1,13 @@
import { MediaTypeModel } from '../models/MediaTypeModel'; import type MediaDbPlugin from '../main';
import { MediaType } from '../utils/MediaType'; import type { MediaTypeModel } from '../models/MediaTypeModel';
import MediaDbPlugin from '../main'; import type { MediaType } from '../utils/MediaType';
export abstract class APIModel { export abstract class APIModel {
apiName: string; apiName!: string;
apiUrl: string; apiUrl!: string;
apiDescription: string; apiDescription!: string;
types: MediaType[]; types!: MediaType[];
plugin: MediaDbPlugin; plugin!: MediaDbPlugin;
/** /**
* This function should query the api and return a list of matches. The matches should be caped at 20. * This function should query the api and return a list of matches. The matches should be caped at 20.

View file

@ -1,9 +1,9 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { BoardGameModel } from 'src/models/BoardGameModel';
import { requestUrl } from 'obsidian'; import { requestUrl } from 'obsidian';
import { BoardGameModel } from 'src/models/BoardGameModel';
import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class BoardGameGeekAPI extends APIModel { export class BoardGameGeekAPI extends APIModel {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -38,8 +38,8 @@ export class BoardGameGeekAPI extends APIModel {
const ret: MediaTypeModel[] = []; const ret: MediaTypeModel[] = [];
for (const boardgame of Array.from(response.querySelectorAll('boardgame'))) { for (const boardgame of Array.from(response.querySelectorAll('boardgame'))) {
const id = boardgame.attributes.getNamedItem('objectid')!.value; const id = boardgame.attributes.getNamedItem('objectid')?.value;
const title = boardgame.querySelector('name[primary=true]')?.textContent ?? boardgame.querySelector('name')!.textContent!; const title = boardgame.querySelector('name[primary=true]')?.textContent ?? boardgame.querySelector('name')?.textContent ?? undefined;
const year = boardgame.querySelector('yearpublished')?.textContent ?? ''; const year = boardgame.querySelector('yearpublished')?.textContent ?? '';
ret.push( ret.push(
@ -49,7 +49,7 @@ export class BoardGameGeekAPI extends APIModel {
title, title,
englishTitle: title, englishTitle: title,
year, year,
} as BoardGameModel), }),
); );
} }
@ -72,21 +72,29 @@ export class BoardGameGeekAPI extends APIModel {
const response = new window.DOMParser().parseFromString(data, 'text/xml'); const response = new window.DOMParser().parseFromString(data, 'text/xml');
// console.debug(response); // console.debug(response);
const boardgame = response.querySelector('boardgame')!; const boardgame = response.querySelector('boardgame');
const title = boardgame.querySelector('name[primary=true]')!.textContent!; if (!boardgame) {
throw Error(`MDB | Received invalid data from ${this.apiName}.`);
}
const title = boardgame.querySelector('name[primary=true]')?.textContent;
const year = boardgame.querySelector('yearpublished')?.textContent ?? ''; const year = boardgame.querySelector('yearpublished')?.textContent ?? '';
const image = boardgame.querySelector('image')?.textContent ?? undefined; const image = boardgame.querySelector('image')?.textContent ?? undefined;
const onlineRating = Number.parseFloat(boardgame.querySelector('statistics ratings average')?.textContent ?? '0'); const onlineRating = Number.parseFloat(boardgame.querySelector('statistics ratings average')?.textContent ?? '0');
const genres = Array.from(boardgame.querySelectorAll('boardgamecategory')).map(n => n!.textContent!); const genres = Array.from(boardgame.querySelectorAll('boardgamecategory'))
.map(n => n.textContent)
.filter(n => n !== null);
const complexityRating = Number.parseFloat(boardgame.querySelector('averageweight')?.textContent ?? '0'); const complexityRating = Number.parseFloat(boardgame.querySelector('averageweight')?.textContent ?? '0');
const minPlayers = Number.parseFloat(boardgame.querySelector('minplayers')?.textContent ?? '0'); const minPlayers = Number.parseFloat(boardgame.querySelector('minplayers')?.textContent ?? '0');
const maxPlayers = Number.parseFloat(boardgame.querySelector('maxplayers')?.textContent ?? '0'); const maxPlayers = Number.parseFloat(boardgame.querySelector('maxplayers')?.textContent ?? '0');
const playtime = (boardgame.querySelector('playingtime')?.textContent ?? 'unknown') + ' minutes'; const playtime = (boardgame.querySelector('playingtime')?.textContent ?? 'unknown') + ' minutes';
const publishers = Array.from(boardgame.querySelectorAll('boardgamepublisher')).map(n => n!.textContent!); const publishers = Array.from(boardgame.querySelectorAll('boardgamepublisher'))
.map(n => n.textContent)
.filter(n => n !== null);
return new BoardGameModel({ return new BoardGameModel({
title: title, title: title ?? undefined,
englishTitle: title, englishTitle: title ?? undefined,
year: year === '0' ? '' : year, year: year === '0' ? '' : year,
dataSource: this.apiName, dataSource: this.apiName,
url: `https://boardgamegeek.com/boardgame/${id}`, url: `https://boardgamegeek.com/boardgame/${id}`,
@ -107,6 +115,6 @@ export class BoardGameGeekAPI extends APIModel {
played: false, played: false,
personalRating: 0, personalRating: 0,
}, },
} as BoardGameModel); });
} }
} }

View file

@ -1,9 +1,9 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import { requestUrl } from 'obsidian'; import { requestUrl } from 'obsidian';
import type MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class GiantBombAPI extends APIModel { export class GiantBombAPI extends APIModel {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -54,7 +54,7 @@ export class GiantBombAPI extends APIModel {
year: new Date(result.original_release_date).getFullYear().toString(), year: new Date(result.original_release_date).getFullYear().toString(),
dataSource: this.apiName, dataSource: this.apiName,
id: result.guid, id: result.guid,
} as GameModel), }),
); );
} }
@ -104,6 +104,6 @@ export class GiantBombAPI extends APIModel {
personalRating: 0, personalRating: 0,
}, },
} as GameModel); });
} }
} }

View file

@ -1,9 +1,9 @@
import { APIModel } from '../APIModel'; import type MediaDbPlugin from '../../main';
import { MediaTypeModel } from '../../models/MediaTypeModel'; import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MovieModel } from '../../models/MovieModel'; import { MovieModel } from '../../models/MovieModel';
import MediaDbPlugin from '../../main';
import { SeriesModel } from '../../models/SeriesModel'; import { SeriesModel } from '../../models/SeriesModel';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class MALAPI extends APIModel { export class MALAPI extends APIModel {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -52,7 +52,7 @@ export class MALAPI extends APIModel {
year: result.year ?? result.aired?.prop?.from?.year ?? '', year: result.year ?? result.aired?.prop?.from?.year ?? '',
dataSource: this.apiName, dataSource: this.apiName,
id: result.mal_id, id: result.mal_id,
} as MovieModel), }),
); );
} }
if (type === 'movie' || type === 'special') { if (type === 'movie' || type === 'special') {
@ -64,7 +64,7 @@ export class MALAPI extends APIModel {
year: result.year ?? result.aired?.prop?.from?.year ?? '', year: result.year ?? result.aired?.prop?.from?.year ?? '',
dataSource: this.apiName, dataSource: this.apiName,
id: result.mal_id, id: result.mal_id,
} as MovieModel), }),
); );
} else if (type === 'series' || type === 'ova') { } else if (type === 'series' || type === 'ova') {
ret.push( ret.push(
@ -75,7 +75,7 @@ export class MALAPI extends APIModel {
year: result.year ?? result.aired?.prop?.from?.year ?? '', year: result.year ?? result.aired?.prop?.from?.year ?? '',
dataSource: this.apiName, dataSource: this.apiName,
id: result.mal_id, id: result.mal_id,
} as SeriesModel), }),
); );
} }
} }
@ -127,7 +127,7 @@ export class MALAPI extends APIModel {
lastWatched: '', lastWatched: '',
personalRating: 0, personalRating: 0,
}, },
} as MovieModel); });
} }
if (type === 'movie' || type === 'special') { if (type === 'movie' || type === 'special') {
@ -159,7 +159,7 @@ export class MALAPI extends APIModel {
lastWatched: '', lastWatched: '',
personalRating: 0, personalRating: 0,
}, },
} as MovieModel); });
} else if (type === 'series' || type === 'ova') { } else if (type === 'series' || type === 'ova') {
return new SeriesModel({ return new SeriesModel({
subType: type, subType: type,
@ -190,9 +190,9 @@ export class MALAPI extends APIModel {
lastWatched: '', lastWatched: '',
personalRating: 0, personalRating: 0,
}, },
} as SeriesModel); });
} }
return; throw new Error(`MDB | Unknown media type for id ${id}`);
} }
} }

View file

@ -1,8 +1,8 @@
import { APIModel } from '../APIModel'; import type MediaDbPlugin from '../../main';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { MangaModel } from '../../models/MangaModel'; import { MangaModel } from '../../models/MangaModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class MALAPIManga extends APIModel { export class MALAPIManga extends APIModel {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -73,7 +73,7 @@ export class MALAPIManga extends APIModel {
lastWatched: '', lastWatched: '',
personalRating: 0, personalRating: 0,
}, },
} as MangaModel), }),
); );
} }
@ -123,6 +123,6 @@ export class MALAPIManga extends APIModel {
lastWatched: '', lastWatched: '',
personalRating: 0, personalRating: 0,
}, },
} as MangaModel); });
} }
} }

View file

@ -1,10 +1,10 @@
import { APIModel } from '../APIModel';
import { Notice } from 'obsidian'; import { Notice } from 'obsidian';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import { requestUrl } from 'obsidian'; import { requestUrl } from 'obsidian';
import type MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class MobyGamesAPI extends APIModel { export class MobyGamesAPI extends APIModel {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -23,9 +23,7 @@ export class MobyGamesAPI extends APIModel {
console.log(`MDB | api "${this.apiName}" queried by Title`); console.log(`MDB | api "${this.apiName}" queried by Title`);
if (!this.plugin.settings.MobyGamesKey) { if (!this.plugin.settings.MobyGamesKey) {
console.error(new Error(`MDB | API key for ${this.apiName} missing.`)); throw new Error(`MDB | API key for ${this.apiName} missing.`);
new Notice(`MediaDB | API key for ${this.apiName} missing.`);
return [];
} }
const searchUrl = `${this.apiUrl}/games?title=${encodeURIComponent(title)}&api_key=${this.plugin.settings.MobyGamesKey}`; const searchUrl = `${this.apiUrl}/games?title=${encodeURIComponent(title)}&api_key=${this.plugin.settings.MobyGamesKey}`;
@ -68,7 +66,6 @@ export class MobyGamesAPI extends APIModel {
console.log(`MDB | api "${this.apiName}" queried by ID`); console.log(`MDB | api "${this.apiName}" queried by ID`);
if (!this.plugin.settings.MobyGamesKey) { if (!this.plugin.settings.MobyGamesKey) {
new Notice(`MediaDB | API key for ${this.apiName} missing.`);
throw Error(`MDB | API key for ${this.apiName} missing.`); throw Error(`MDB | API key for ${this.apiName} missing.`);
} }
@ -79,7 +76,6 @@ export class MobyGamesAPI extends APIModel {
console.debug(fetchData); console.debug(fetchData);
if (fetchData.status !== 200) { if (fetchData.status !== 200) {
new Notice(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`);
throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`); throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`);
} }
@ -109,6 +105,6 @@ export class MobyGamesAPI extends APIModel {
personalRating: 0, personalRating: 0,
}, },
} as GameModel); });
} }
} }

View file

@ -1,10 +1,10 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { requestUrl } from 'obsidian'; import { requestUrl } from 'obsidian';
import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MusicReleaseModel } from '../../models/MusicReleaseModel'; import { MusicReleaseModel } from '../../models/MusicReleaseModel';
import { contactEmail, mediaDbVersion, pluginName } from '../../utils/Utils';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { contactEmail, mediaDbVersion, pluginName } from '../../utils/Utils';
import { APIModel } from '../APIModel';
export class MusicBrainzAPI extends APIModel { export class MusicBrainzAPI extends APIModel {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -55,7 +55,7 @@ export class MusicBrainzAPI extends APIModel {
artists: result['artist-credit'].map((a: any) => a.name), artists: result['artist-credit'].map((a: any) => a.name),
subType: result['primary-type'], subType: result['primary-type'],
} as MusicReleaseModel), }),
); );
} }
@ -97,6 +97,6 @@ export class MusicBrainzAPI extends APIModel {
userData: { userData: {
personalRating: 0, personalRating: 0,
}, },
} as MusicReleaseModel); });
} }
} }

View file

@ -1,11 +1,11 @@
import { APIModel } from '../APIModel';
import { Notice } from 'obsidian'; import { Notice } from 'obsidian';
import { MediaTypeModel } from '../../models/MediaTypeModel'; import type MediaDbPlugin from '../../main';
import { MovieModel } from '../../models/MovieModel';
import MediaDbPlugin from '../../main';
import { SeriesModel } from '../../models/SeriesModel';
import { GameModel } from '../../models/GameModel'; import { GameModel } from '../../models/GameModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MovieModel } from '../../models/MovieModel';
import { SeriesModel } from '../../models/SeriesModel';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class OMDbAPI extends APIModel { export class OMDbAPI extends APIModel {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -30,9 +30,7 @@ export class OMDbAPI extends APIModel {
console.log(`MDB | api "${this.apiName}" queried by Title`); console.log(`MDB | api "${this.apiName}" queried by Title`);
if (!this.plugin.settings.OMDbKey) { if (!this.plugin.settings.OMDbKey) {
console.error(new Error(`MDB | API key for ${this.apiName} missing.`)); throw new Error(`MDB | API key for ${this.apiName} missing.`);
new Notice(`MediaDB | API key for ${this.apiName} missing.`);
return [];
} }
const searchUrl = `https://www.omdbapi.com/?s=${encodeURIComponent(title)}&apikey=${this.plugin.settings.OMDbKey}`; const searchUrl = `https://www.omdbapi.com/?s=${encodeURIComponent(title)}&apikey=${this.plugin.settings.OMDbKey}`;
@ -76,7 +74,7 @@ export class OMDbAPI extends APIModel {
year: result.Year, year: result.Year,
dataSource: this.apiName, dataSource: this.apiName,
id: result.imdbID, id: result.imdbID,
} as MovieModel), }),
); );
} else if (type === 'series') { } else if (type === 'series') {
ret.push( ret.push(
@ -87,7 +85,7 @@ export class OMDbAPI extends APIModel {
year: result.Year, year: result.Year,
dataSource: this.apiName, dataSource: this.apiName,
id: result.imdbID, id: result.imdbID,
} as SeriesModel), }),
); );
} else if (type === 'game') { } else if (type === 'game') {
ret.push( ret.push(
@ -98,7 +96,7 @@ export class OMDbAPI extends APIModel {
year: result.Year, year: result.Year,
dataSource: this.apiName, dataSource: this.apiName,
id: result.imdbID, id: result.imdbID,
} as GameModel), }),
); );
} }
} }
@ -110,7 +108,6 @@ export class OMDbAPI extends APIModel {
console.log(`MDB | api "${this.apiName}" queried by ID`); console.log(`MDB | api "${this.apiName}" queried by ID`);
if (!this.plugin.settings.OMDbKey) { if (!this.plugin.settings.OMDbKey) {
new Notice(`MediaDB | API key for ${this.apiName} missing.`);
throw Error(`MDB | API key for ${this.apiName} missing.`); throw Error(`MDB | API key for ${this.apiName} missing.`);
} }
@ -118,11 +115,9 @@ export class OMDbAPI extends APIModel {
const fetchData = await fetch(searchUrl); const fetchData = await fetch(searchUrl);
if (fetchData.status === 401) { if (fetchData.status === 401) {
new Notice(`MDB | Authentication for ${this.apiName} failed. Check the API key.`);
throw Error(`MDB | Authentication for ${this.apiName} failed. Check the API key.`); throw Error(`MDB | Authentication for ${this.apiName} failed. Check the API key.`);
} }
if (fetchData.status !== 200) { if (fetchData.status !== 200) {
new Notice(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`);
throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`); throw Error(`MDB | Received status code ${fetchData.status} from ${this.apiName}.`);
} }
@ -130,7 +125,6 @@ export class OMDbAPI extends APIModel {
// console.debug(result); // console.debug(result);
if (result.Response === 'False') { if (result.Response === 'False') {
new Notice(`MDB | Received error from ${this.apiName}: ${result.Error}`);
throw Error(`MDB | Received error from ${this.apiName}: ${result.Error}`); throw Error(`MDB | Received error from ${this.apiName}: ${result.Error}`);
} }
@ -168,7 +162,7 @@ export class OMDbAPI extends APIModel {
lastWatched: '', lastWatched: '',
personalRating: 0, personalRating: 0,
}, },
} as MovieModel); });
} else if (type === 'series') { } else if (type === 'series') {
return new SeriesModel({ return new SeriesModel({
type: type, type: type,
@ -200,7 +194,7 @@ export class OMDbAPI extends APIModel {
lastWatched: '', lastWatched: '',
personalRating: 0, personalRating: 0,
}, },
} as SeriesModel); });
} else if (type === 'game') { } else if (type === 'game') {
return new GameModel({ return new GameModel({
type: type, type: type,
@ -224,9 +218,9 @@ export class OMDbAPI extends APIModel {
played: false, played: false,
personalRating: 0, personalRating: 0,
}, },
} as GameModel); });
} }
return; throw new Error(`MDB | Unknown media type for id ${id}`);
} }
} }

View file

@ -1,8 +1,8 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { BookModel } from 'src/models/BookModel'; import { BookModel } from 'src/models/BookModel';
import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class OpenLibraryAPI extends APIModel { export class OpenLibraryAPI extends APIModel {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -42,7 +42,7 @@ export class OpenLibraryAPI extends APIModel {
dataSource: this.apiName, dataSource: this.apiName,
id: result.key, id: result.key,
author: result.author_name ?? 'unknown', author: result.author_name ?? 'unknown',
} as BookModel), }),
); );
} }
@ -87,6 +87,6 @@ export class OpenLibraryAPI extends APIModel {
lastRead: '', lastRead: '',
personalRating: 0, personalRating: 0,
}, },
} as BookModel); });
} }
} }

View file

@ -1,9 +1,9 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import { requestUrl } from 'obsidian'; import { requestUrl } from 'obsidian';
import type MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class SteamAPI extends APIModel { export class SteamAPI extends APIModel {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -49,7 +49,7 @@ export class SteamAPI extends APIModel {
year: '', year: '',
dataSource: this.apiName, dataSource: this.apiName,
id: result.appid, id: result.appid,
} as GameModel), }),
); );
} }
@ -94,8 +94,8 @@ export class SteamAPI extends APIModel {
url: `https://store.steampowered.com/app/${result.steam_appid}`, url: `https://store.steampowered.com/app/${result.steam_appid}`,
id: result.steam_appid, id: result.steam_appid,
developers: result['developers'], developers: result.developers,
publishers: result['publishers'], publishers: result.publishers,
genres: result.genres?.map((x: any) => x.description) ?? [], genres: result.genres?.map((x: any) => x.description) ?? [],
onlineRating: Number.parseFloat(result.metacritic?.score ?? 0), onlineRating: Number.parseFloat(result.metacritic?.score ?? 0),
image: result.header_image ?? '', image: result.header_image ?? '',
@ -107,6 +107,6 @@ export class SteamAPI extends APIModel {
played: false, played: false,
personalRating: 0, personalRating: 0,
}, },
} as GameModel); });
} }
} }

View file

@ -1,8 +1,8 @@
import { APIModel } from '../APIModel'; import type MediaDbPlugin from '../../main';
import { MediaTypeModel } from '../../models/MediaTypeModel'; import type { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { WikiModel } from '../../models/WikiModel'; import { WikiModel } from '../../models/WikiModel';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel';
export class WikipediaAPI extends APIModel { export class WikipediaAPI extends APIModel {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -42,7 +42,7 @@ export class WikipediaAPI extends APIModel {
year: '', year: '',
dataSource: this.apiName, dataSource: this.apiName,
id: result.pageid, id: result.pageid,
} as WikiModel), }),
); );
} }
@ -73,10 +73,10 @@ export class WikipediaAPI extends APIModel {
id: result.pageid, id: result.pageid,
wikiUrl: result.fullurl, wikiUrl: result.fullurl,
lastUpdated: this.plugin.dateFormatter.format(result.touched, this.apiDateFormat), lastUpdated: this.plugin.dateFormatter.format(result.touched, this.apiDateFormat) ?? undefined,
length: result.length, length: result.length,
userData: {}, userData: {},
} as WikiModel); });
} }
} }

View file

@ -1,33 +1,28 @@
import { MarkdownView, Notice, parseYaml, Plugin, stringifyYaml, TFile, TFolder } from 'obsidian'; import { MarkdownView, Notice, parseYaml, Plugin, stringifyYaml, TFile, TFolder } from 'obsidian';
import { getDefaultSettings, MediaDbPluginSettings, MediaDbSettingTab } from './settings/Settings'; import type { MediaType } from 'src/utils/MediaType';
import { APIManager } from './api/APIManager'; import { APIManager } from './api/APIManager';
import { MediaTypeModel } from './models/MediaTypeModel'; import { BoardGameGeekAPI } from './api/apis/BoardGameGeekAPI';
import { import { GiantBombAPI } from './api/apis/GiantBombAPI';
CreateNoteOptions,
dateTimeToString,
markdownTable,
replaceIllegalFileNameCharactersInString,
unCamelCase,
hasTemplaterPlugin,
useTemplaterPluginInFile,
} from './utils/Utils';
import { OMDbAPI } from './api/apis/OMDbAPI';
import { MALAPI } from './api/apis/MALAPI'; import { MALAPI } from './api/apis/MALAPI';
import { MALAPIManga } from './api/apis/MALAPIManga'; import { MALAPIManga } from './api/apis/MALAPIManga';
import { WikipediaAPI } from './api/apis/WikipediaAPI';
import { MusicBrainzAPI } from './api/apis/MusicBrainzAPI'; import { MusicBrainzAPI } from './api/apis/MusicBrainzAPI';
import { OMDbAPI } from './api/apis/OMDbAPI';
import type { MediaTypeModel } from './models/MediaTypeModel';
import type { MediaDbPluginSettings } from './settings/Settings';
import { getDefaultSettings, MediaDbSettingTab } from './settings/Settings';
import { MEDIA_TYPES, MediaTypeManager } from './utils/MediaTypeManager'; import { MEDIA_TYPES, MediaTypeManager } from './utils/MediaTypeManager';
import type { SearchModalOptions } from './utils/ModalHelper';
import type { CreateNoteOptions } from './utils/Utils';
import { dateTimeToString, markdownTable, replaceIllegalFileNameCharactersInString, unCamelCase, hasTemplaterPlugin, useTemplaterPluginInFile } from './utils/Utils';
import { WikipediaAPI } from './api/apis/WikipediaAPI';
import { SteamAPI } from './api/apis/SteamAPI'; import { SteamAPI } from './api/apis/SteamAPI';
import { BoardGameGeekAPI } from './api/apis/BoardGameGeekAPI';
import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI'; import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI';
import { MobyGamesAPI } from './api/apis/MobyGamesAPI'; import { MobyGamesAPI } from './api/apis/MobyGamesAPI';
import { GiantBombAPI } from './api/apis/GiantBombAPI';
import { PropertyMapper } from './settings/PropertyMapper'; import { PropertyMapper } from './settings/PropertyMapper';
import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal'; import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal';
import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping'; import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping';
import { ModalHelper, ModalResultCode, SearchModalOptions } from './utils/ModalHelper'; import { ModalHelper, ModalResultCode } from './utils/ModalHelper';
import { DateFormatter } from './utils/DateFormatter'; import { DateFormatter } from './utils/DateFormatter';
import { MediaType } from 'src/utils/MediaType';
export type Metadata = Record<string, unknown>; export type Metadata = Record<string, unknown>;
@ -38,12 +33,12 @@ export interface MediaTypeModelObj {
} }
export default class MediaDbPlugin extends Plugin { export default class MediaDbPlugin extends Plugin {
settings: MediaDbPluginSettings; settings!: MediaDbPluginSettings;
apiManager: APIManager; apiManager!: APIManager;
mediaTypeManager: MediaTypeManager; mediaTypeManager!: MediaTypeManager;
modelPropertyMapper: PropertyMapper; modelPropertyMapper!: PropertyMapper;
modalHelper: ModalHelper; modalHelper!: ModalHelper;
dateFormatter: DateFormatter; dateFormatter!: DateFormatter;
frontMatterRexExpPattern: string = '^(---)\\n[\\s\\S]*?\\n---'; frontMatterRexExpPattern: string = '^(---)\\n[\\s\\S]*?\\n---';
@ -165,7 +160,7 @@ export default class MediaDbPlugin extends Plugin {
* - maybe custom link syntax * - maybe custom link syntax
*/ */
async createLinkWithSearchModal(): Promise<void> { async createLinkWithSearchModal(): Promise<void> {
const apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => { const apiSearchResults = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => {
return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis); return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis);
}); });
@ -173,7 +168,7 @@ export default class MediaDbPlugin extends Plugin {
return; return;
} }
const selectResults: MediaTypeModel[] = await this.modalHelper.openSelectModal({ elements: apiSearchResults, multiSelect: false }, async selectModalData => { const selectResults = await this.modalHelper.openSelectModal({ elements: apiSearchResults, multiSelect: false }, async selectModalData => {
return await this.queryDetails(selectModalData.selected); return await this.queryDetails(selectModalData.selected);
}); });
@ -193,7 +188,7 @@ export default class MediaDbPlugin extends Plugin {
async createEntryWithSearchModal(searchModalOptions?: SearchModalOptions): Promise<void> { async createEntryWithSearchModal(searchModalOptions?: SearchModalOptions): Promise<void> {
let types: string[] = []; let types: string[] = [];
let apiSearchResults: MediaTypeModel[] = await this.modalHelper.openSearchModal(searchModalOptions ?? {}, async searchModalData => { let apiSearchResults = await this.modalHelper.openSearchModal(searchModalOptions ?? {}, async searchModalData => {
types = searchModalData.types; types = searchModalData.types;
const apis = this.apiManager.apis.filter(x => x.hasTypeOverlap(searchModalData.types)).map(x => x.apiName); const apis = this.apiManager.apis.filter(x => x.hasTypeOverlap(searchModalData.types)).map(x => x.apiName);
try { try {
@ -214,12 +209,13 @@ export default class MediaDbPlugin extends Plugin {
apiSearchResults = apiSearchResults.filter(x => types.contains(x.type)); apiSearchResults = apiSearchResults.filter(x => types.contains(x.type));
let selectResults: MediaTypeModel[]; let selectResults: MediaTypeModel[];
let proceed: boolean; let proceed: boolean = false;
while (!proceed) { while (!proceed) {
selectResults = await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => { selectResults =
(await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
return await this.queryDetails(selectModalData.selected); return await this.queryDetails(selectModalData.selected);
}); })) ?? [];
if (!selectResults) { if (!selectResults) {
return; return;
} }
@ -229,11 +225,11 @@ export default class MediaDbPlugin extends Plugin {
}); });
} }
await this.createMediaDbNotes(selectResults); await this.createMediaDbNotes(selectResults!);
} }
async createEntryWithAdvancedSearchModal(): Promise<void> { async createEntryWithAdvancedSearchModal(): Promise<void> {
const apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => { const apiSearchResults = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => {
return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis); return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis);
}); });
@ -243,12 +239,13 @@ export default class MediaDbPlugin extends Plugin {
} }
let selectResults: MediaTypeModel[]; let selectResults: MediaTypeModel[];
let proceed: boolean; let proceed: boolean = false;
while (!proceed) { while (!proceed) {
selectResults = await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => { selectResults =
(await this.modalHelper.openSelectModal({ elements: apiSearchResults }, async selectModalData => {
return await this.queryDetails(selectModalData.selected); return await this.queryDetails(selectModalData.selected);
}); })) ?? [];
if (!selectResults) { if (!selectResults) {
return; return;
} }
@ -258,12 +255,12 @@ export default class MediaDbPlugin extends Plugin {
}); });
} }
await this.createMediaDbNotes(selectResults); await this.createMediaDbNotes(selectResults!);
} }
async createEntryWithIdSearchModal(): Promise<void> { async createEntryWithIdSearchModal(): Promise<void> {
let idSearchResult: MediaTypeModel; let idSearchResult: MediaTypeModel | undefined = undefined;
let proceed: boolean; let proceed: boolean = false;
while (!proceed) { while (!proceed) {
idSearchResult = await this.modalHelper.openIdSearchModal({}, async idSearchModalData => { idSearchResult = await this.modalHelper.openIdSearchModal({}, async idSearchModalData => {
@ -278,6 +275,9 @@ export default class MediaDbPlugin extends Plugin {
}); });
} }
if (!idSearchResult) {
return;
}
await this.createMediaDbNoteFromModel(idSearchResult, { attachTemplate: true, openNote: true }); await this.createMediaDbNoteFromModel(idSearchResult, { attachTemplate: true, openNote: true });
} }
@ -290,11 +290,9 @@ export default class MediaDbPlugin extends Plugin {
async queryDetails(models: MediaTypeModel[]): Promise<MediaTypeModel[]> { async queryDetails(models: MediaTypeModel[]): Promise<MediaTypeModel[]> {
const detailModels: MediaTypeModel[] = []; const detailModels: MediaTypeModel[] = [];
for (const model of models) { for (const model of models) {
try { const res = await this.apiManager.queryDetailedInfo(model);
detailModels.push(await this.apiManager.queryDetailedInfo(model)); if (res) {
} catch (e) { detailModels.push(res);
console.warn(e);
new Notice(e.toString());
} }
} }
return detailModels; return detailModels;
@ -319,7 +317,7 @@ export default class MediaDbPlugin extends Plugin {
} }
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
new Notice(e.toString()); new Notice(`${e}`);
} }
} }
@ -383,7 +381,7 @@ export default class MediaDbPlugin extends Plugin {
// Updating a previous file // Updating a previous file
if (options.attachFile) { if (options.attachFile) {
const previousMetadata = this.app.metadataCache.getFileCache(options.attachFile).frontmatter; const previousMetadata = this.app.metadataCache.getFileCache(options.attachFile)?.frontmatter ?? {};
// Use contents (below front matter) from previous file // Use contents (below front matter) from previous file
fileContent = await this.app.vault.read(options.attachFile); fileContent = await this.app.vault.read(options.attachFile);
@ -443,7 +441,7 @@ export default class MediaDbPlugin extends Plugin {
return { fileMetadata: fileMetadata, fileContent: fileContent }; return { fileMetadata: fileMetadata, fileContent: fileContent };
} }
async attachTemplate(fileMetadata: Metadata, fileContent: string, template: string): Promise<{ fileMetadata: Metadata; fileContent: string }> { async attachTemplate(fileMetadata: Metadata, fileContent: string, template: string | undefined): Promise<{ fileMetadata: Metadata; fileContent: string }> {
if (!template) { if (!template) {
return { fileMetadata: fileMetadata, fileContent: fileContent }; return { fileMetadata: fileMetadata, fileContent: fileContent };
} }
@ -486,7 +484,7 @@ export default class MediaDbPlugin extends Plugin {
} }
getMetadataFromFileCache(file: TFile): Metadata { getMetadataFromFileCache(file: TFile): Metadata {
const metadata: Metadata | undefined = this.app.metadataCache.getFileCache(file).frontmatter; const metadata: Metadata | undefined = this.app.metadataCache.getFileCache(file)?.frontmatter;
return structuredClone(metadata ?? {}); return structuredClone(metadata ?? {});
} }
@ -501,6 +499,10 @@ export default class MediaDbPlugin extends Plugin {
// find and possibly create the folder set in settings or passed in folder // find and possibly create the folder set in settings or passed in folder
const folder = options.folder ?? this.app.vault.getAbstractFileByPath('/'); const folder = options.folder ?? this.app.vault.getAbstractFileByPath('/');
if (!folder || !(folder instanceof TFolder)) {
throw new Error('MDB | invalid folder');
}
fileName = replaceIllegalFileNameCharactersInString(fileName); fileName = replaceIllegalFileNameCharactersInString(fileName);
const filePath = `${folder.path}/${fileName}.md`; const filePath = `${folder.path}/${fileName}.md`;
@ -519,7 +521,7 @@ export default class MediaDbPlugin extends Plugin {
const activeLeaf = this.app.workspace.getUnpinnedLeaf(); const activeLeaf = this.app.workspace.getUnpinnedLeaf();
if (!activeLeaf) { if (!activeLeaf) {
console.warn('MDB | no active leaf, not opening newly created note'); console.warn('MDB | no active leaf, not opening newly created note');
return; return targetFile;
} }
await activeLeaf.openFile(targetFile, { state: { mode: 'source' } }); await activeLeaf.openFile(targetFile, { state: { mode: 'source' } });
} }
@ -532,7 +534,7 @@ export default class MediaDbPlugin extends Plugin {
* Tries to read the type, id and dataSource of the active note. If successful it will query the api, delete the old note and create a new one. * Tries to read the type, id and dataSource of the active note. If successful it will query the api, delete the old note and create a new one.
*/ */
async updateActiveNote(onlyMetadata: boolean = false): Promise<void> { async updateActiveNote(onlyMetadata: boolean = false): Promise<void> {
const activeFile: TFile = this.app.workspace.getActiveFile(); const activeFile = this.app.workspace.getActiveFile() ?? undefined;
if (!activeFile) { if (!activeFile) {
throw new Error('MDB | there is no active note'); throw new Error('MDB | there is no active note');
} }
@ -560,9 +562,9 @@ export default class MediaDbPlugin extends Plugin {
// console.debug(newMediaTypeModel); // console.debug(newMediaTypeModel);
if (onlyMetadata) { if (onlyMetadata) {
await this.createMediaDbNoteFromModel(newMediaTypeModel, { attachFile: activeFile, folder: activeFile.parent, openNote: true }); await this.createMediaDbNoteFromModel(newMediaTypeModel, { attachFile: activeFile, folder: activeFile.parent ?? undefined, openNote: true });
} else { } else {
await this.createMediaDbNoteFromModel(newMediaTypeModel, { attachTemplate: true, folder: activeFile.parent, openNote: true }); await this.createMediaDbNoteFromModel(newMediaTypeModel, { attachTemplate: true, folder: activeFile.parent ?? undefined, openNote: true });
} }
} }
@ -596,7 +598,7 @@ export default class MediaDbPlugin extends Plugin {
try { try {
results = await this.apiManager.query(title, [selectedAPI]); results = await this.apiManager.query(title, [selectedAPI]);
} catch (e) { } catch (e) {
erroredFiles.push({ filePath: file.path, error: e.toString() }); erroredFiles.push({ filePath: file.path, error: `${e}` });
continue; continue;
} }
if (!results || results.length === 0) { if (!results || results.length === 0) {
@ -631,7 +633,7 @@ export default class MediaDbPlugin extends Plugin {
} }
const detailedResults = await this.queryDetails(selectModalResult.data.selected); const detailedResults = await this.queryDetails(selectModalResult.data.selected);
await this.createMediaDbNotes(detailedResults, appendContent ? file : null); await this.createMediaDbNotes(detailedResults, appendContent ? file : undefined);
selectModal.close(); selectModal.close();
} }
@ -661,7 +663,7 @@ export default class MediaDbPlugin extends Plugin {
// migrate the settings loaded from the disk to match the structure of the default settings // migrate the settings loaded from the disk to match the structure of the default settings
const newPropertyMappings: PropertyMappingModel[] = []; const newPropertyMappings: PropertyMappingModel[] = [];
for (const defaultPropertyMappingModel of defaultSettings.propertyMappingModels) { for (const defaultPropertyMappingModel of defaultSettings.propertyMappingModels) {
const newPropertyMappingModel: PropertyMappingModel = loadedSettings.propertyMappingModels.find(x => x.type === defaultPropertyMappingModel.type); const newPropertyMappingModel = loadedSettings.propertyMappingModels.find(x => x.type === defaultPropertyMappingModel.type);
if (newPropertyMappingModel === undefined) { if (newPropertyMappingModel === undefined) {
// if the propertyMappingModel exists in the default settings but not the loaded settings, add it // if the propertyMappingModel exists in the default settings but not the loaded settings, add it
newPropertyMappings.push(defaultPropertyMappingModel); newPropertyMappings.push(defaultPropertyMappingModel);

View file

@ -1,7 +1,9 @@
import { ButtonComponent, Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian'; import type { ButtonComponent } from 'obsidian';
import { MediaTypeModel } from '../models/MediaTypeModel'; import { Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
import MediaDbPlugin from '../main'; import type MediaDbPlugin from '../main';
import { ADVANCED_SEARCH_MODAL_DEFAULT_OPTIONS, AdvancedSearchModalData, AdvancedSearchModalOptions } from '../utils/ModalHelper'; import type { MediaTypeModel } from '../models/MediaTypeModel';
import type { AdvancedSearchModalData, AdvancedSearchModalOptions } from '../utils/ModalHelper';
import { ADVANCED_SEARCH_MODAL_DEFAULT_OPTIONS } from '../utils/ModalHelper';
export class MediaDbAdvancedSearchModal extends Modal { export class MediaDbAdvancedSearchModal extends Modal {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -9,9 +11,9 @@ export class MediaDbAdvancedSearchModal extends Modal {
query: string; query: string;
isBusy: boolean; isBusy: boolean;
title: string; title: string;
selectedApis: { name: string; selected: boolean }[]; selectedApis: string[];
searchBtn: ButtonComponent; searchBtn?: ButtonComponent;
submitCallback?: (res: AdvancedSearchModalData) => void; submitCallback?: (res: AdvancedSearchModalData) => void;
closeCallback?: (err?: Error) => void; closeCallback?: (err?: Error) => void;
@ -22,12 +24,9 @@ export class MediaDbAdvancedSearchModal extends Modal {
this.plugin = plugin; this.plugin = plugin;
this.selectedApis = []; this.selectedApis = [];
this.title = advancedSearchModalOptions.modalTitle; this.title = advancedSearchModalOptions.modalTitle ?? '';
this.query = advancedSearchModalOptions.prefilledSearchString; this.query = advancedSearchModalOptions.prefilledSearchString ?? '';
this.isBusy = false;
for (const api of this.plugin.apiManager.apis) {
this.selectedApis.push({ name: api.apiName, selected: advancedSearchModalOptions.preselectedAPIs.contains(api.apiName) });
}
} }
setSubmitCallback(submitCallback: (res: AdvancedSearchModalData) => void): void { setSubmitCallback(submitCallback: (res: AdvancedSearchModalData) => void): void {
@ -44,13 +43,13 @@ export class MediaDbAdvancedSearchModal extends Modal {
} }
} }
async search(): Promise<MediaTypeModel[]> { async search(): Promise<void> {
if (!this.query || this.query.length < 3) { if (!this.query || this.query.length < 3) {
new Notice('MDB | Query too short'); new Notice('MDB | Query too short');
return; return;
} }
const apis: string[] = this.selectedApis.filter(x => x.selected).map(x => x.name); const apis: string[] = this.selectedApis;
if (apis.length === 0) { if (apis.length === 0) {
new Notice('MDB | No API selected'); new Notice('MDB | No API selected');
@ -59,10 +58,10 @@ export class MediaDbAdvancedSearchModal extends Modal {
if (!this.isBusy) { if (!this.isBusy) {
this.isBusy = true; this.isBusy = true;
this.searchBtn.setDisabled(false); this.searchBtn?.setDisabled(false);
this.searchBtn.setButtonText('Searching...'); this.searchBtn?.setButtonText('Searching...');
this.submitCallback({ query: this.query, apis: apis }); this.submitCallback?.({ query: this.query, apis: apis });
} }
} }
@ -97,9 +96,13 @@ export class MediaDbAdvancedSearchModal extends Modal {
const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper); const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper);
apiToggleComponent.setTooltip(api.apiName); apiToggleComponent.setTooltip(api.apiName);
apiToggleComponent.setValue(this.selectedApis.find(x => x.name === api.apiName).selected); apiToggleComponent.setValue(this.selectedApis.some(x => x === api.apiName));
apiToggleComponent.onChange(value => { apiToggleComponent.onChange(value => {
this.selectedApis.find(x => x.name === api.apiName).selected = value; if (value) {
this.selectedApis.push(api.apiName);
} else {
this.selectedApis = this.selectedApis.filter(x => x !== api.apiName);
}
}); });
apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl); apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl);
} }
@ -124,7 +127,7 @@ export class MediaDbAdvancedSearchModal extends Modal {
} }
onClose(): void { onClose(): void {
this.closeCallback(); this.closeCallback?.();
const { contentEl } = this; const { contentEl } = this;
contentEl.empty(); contentEl.empty();
} }

View file

@ -1,11 +1,12 @@
import { App, ButtonComponent, DropdownComponent, Modal, Setting, TextComponent, ToggleComponent } from 'obsidian'; import type { App, ButtonComponent } from 'obsidian';
import MediaDbPlugin from '../main'; import { DropdownComponent, Modal, Setting, TextComponent, ToggleComponent } from 'obsidian';
import type MediaDbPlugin from '../main';
export class MediaDbFolderImportModal extends Modal { export class MediaDbFolderImportModal extends Modal {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
onSubmit: (selectedAPI: string, titleFieldName: string, appendContent: boolean) => void; onSubmit: (selectedAPI: string, titleFieldName: string, appendContent: boolean) => void;
selectedApi: string; selectedApi: string;
searchBtn: ButtonComponent; searchBtn?: ButtonComponent;
titleFieldName: string; titleFieldName: string;
appendContent: boolean; appendContent: boolean;
@ -14,6 +15,8 @@ export class MediaDbFolderImportModal extends Modal {
this.plugin = plugin; this.plugin = plugin;
this.onSubmit = onSubmit; this.onSubmit = onSubmit;
this.selectedApi = plugin.apiManager.apis[0].apiName; this.selectedApi = plugin.apiManager.apis[0].apiName;
this.titleFieldName = '';
this.appendContent = false;
} }
submit(): void { submit(): void {

View file

@ -1,7 +1,9 @@
import { ButtonComponent, DropdownComponent, Modal, Notice, Setting, TextComponent } from 'obsidian'; import type { ButtonComponent } from 'obsidian';
import { MediaTypeModel } from '../models/MediaTypeModel'; import { DropdownComponent, Modal, Notice, Setting, TextComponent } from 'obsidian';
import MediaDbPlugin from '../main'; import type MediaDbPlugin from '../main';
import { ID_SEARCH_MODAL_DEFAULT_OPTIONS, IdSearchModalData, IdSearchModalOptions } from '../utils/ModalHelper'; import type { MediaTypeModel } from '../models/MediaTypeModel';
import type { IdSearchModalData, IdSearchModalOptions } from '../utils/ModalHelper';
import { ID_SEARCH_MODAL_DEFAULT_OPTIONS } from '../utils/ModalHelper';
export class MediaDbIdSearchModal extends Modal { export class MediaDbIdSearchModal extends Modal {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -11,7 +13,7 @@ export class MediaDbIdSearchModal extends Modal {
title: string; title: string;
selectedApi: string; selectedApi: string;
searchBtn: ButtonComponent; searchBtn?: ButtonComponent;
submitCallback?: (res: IdSearchModalData, err?: Error) => void; submitCallback?: (res: IdSearchModalData, err?: Error) => void;
closeCallback?: (err?: Error) => void; closeCallback?: (err?: Error) => void;
@ -21,8 +23,10 @@ export class MediaDbIdSearchModal extends Modal {
super(plugin.app); super(plugin.app);
this.plugin = plugin; this.plugin = plugin;
this.title = idSearchModalOptions.modalTitle; this.title = idSearchModalOptions.modalTitle ?? '';
this.selectedApi = idSearchModalOptions.preselectedAPI || plugin.apiManager.apis[0].apiName; this.selectedApi = idSearchModalOptions.preselectedAPI || plugin.apiManager.apis[0].apiName;
this.query = '';
this.isBusy = false;
} }
setSubmitCallback(submitCallback: (res: IdSearchModalData, err?: Error) => void): void { setSubmitCallback(submitCallback: (res: IdSearchModalData, err?: Error) => void): void {
@ -39,7 +43,7 @@ export class MediaDbIdSearchModal extends Modal {
} }
} }
async search(): Promise<MediaTypeModel> { async search(): Promise<void> {
if (!this.query) { if (!this.query) {
new Notice('MDB | no Id entered'); new Notice('MDB | no Id entered');
return; return;
@ -52,10 +56,10 @@ export class MediaDbIdSearchModal extends Modal {
if (!this.isBusy) { if (!this.isBusy) {
this.isBusy = true; this.isBusy = true;
this.searchBtn.setDisabled(false); this.searchBtn?.setDisabled(false);
this.searchBtn.setButtonText('Searching...'); this.searchBtn?.setButtonText('Searching...');
this.submitCallback({ query: this.query, api: this.selectedApi }); this.submitCallback?.({ query: this.query, api: this.selectedApi });
} }
} }
@ -109,7 +113,7 @@ export class MediaDbIdSearchModal extends Modal {
} }
onClose(): void { onClose(): void {
this.closeCallback(); this.closeCallback?.();
const { contentEl } = this; const { contentEl } = this;
contentEl.empty(); contentEl.empty();
} }

View file

@ -1,22 +1,19 @@
import { ButtonComponent, Component, MarkdownRenderer, Modal, Setting } from 'obsidian'; import type { ButtonComponent } from 'obsidian';
import MediaDbPlugin from 'src/main'; import { Component, MarkdownRenderer, Modal, Setting } from 'obsidian';
import { MediaTypeModel } from 'src/models/MediaTypeModel'; import type MediaDbPlugin from 'src/main';
import { PREVIEW_MODAL_DEFAULT_OPTIONS, PreviewModalData, PreviewModalOptions } from '../utils/ModalHelper'; import type { MediaTypeModel } from 'src/models/MediaTypeModel';
import { CreateNoteOptions } from '../utils/Utils'; import type { PreviewModalData, PreviewModalOptions } from '../utils/ModalHelper';
import { PREVIEW_MODAL_DEFAULT_OPTIONS } from '../utils/ModalHelper';
export class MediaDbPreviewModal extends Modal { export class MediaDbPreviewModal extends Modal {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
createNoteOptions: CreateNoteOptions;
elements: MediaTypeModel[]; elements: MediaTypeModel[];
isBusy: boolean;
title: string; title: string;
cancelButton: ButtonComponent;
submitButton: ButtonComponent;
markdownComponent: Component; markdownComponent: Component;
submitCallback: (previewModalData: PreviewModalData) => void; submitCallback?: (previewModalData: PreviewModalData) => void;
closeCallback: (err?: Error) => void; closeCallback?: (err?: Error) => void;
constructor(plugin: MediaDbPlugin, previewModalOptions: PreviewModalOptions) { constructor(plugin: MediaDbPlugin, previewModalOptions: PreviewModalOptions) {
previewModalOptions = Object.assign({}, PREVIEW_MODAL_DEFAULT_OPTIONS, previewModalOptions); previewModalOptions = Object.assign({}, PREVIEW_MODAL_DEFAULT_OPTIONS, previewModalOptions);
@ -24,8 +21,8 @@ export class MediaDbPreviewModal extends Modal {
super(plugin.app); super(plugin.app);
this.plugin = plugin; this.plugin = plugin;
this.title = previewModalOptions.modalTitle; this.title = previewModalOptions.modalTitle ?? '';
this.elements = previewModalOptions.elements; this.elements = previewModalOptions.elements ?? [];
this.markdownComponent = new Component(); this.markdownComponent = new Component();
} }
@ -70,14 +67,12 @@ export class MediaDbPreviewModal extends Modal {
btn.setButtonText('Cancel'); btn.setButtonText('Cancel');
btn.onClick(() => this.close()); btn.onClick(() => this.close());
btn.buttonEl.addClass('media-db-plugin-button'); btn.buttonEl.addClass('media-db-plugin-button');
this.cancelButton = btn;
}); });
bottomSettingRow.addButton(btn => { bottomSettingRow.addButton(btn => {
btn.setButtonText('Ok'); btn.setButtonText('Ok');
btn.setCta(); btn.setCta();
btn.onClick(() => this.submitCallback({ confirmed: true })); btn.onClick(() => this.submitCallback?.({ confirmed: true }));
btn.buttonEl.addClass('media-db-plugin-button'); btn.buttonEl.addClass('media-db-plugin-button');
this.submitButton = btn;
}); });
} }
@ -87,6 +82,6 @@ export class MediaDbPreviewModal extends Modal {
onClose(): void { onClose(): void {
this.markdownComponent.unload(); this.markdownComponent.unload();
this.closeCallback(); this.closeCallback?.();
} }
} }

View file

@ -1,10 +1,12 @@
import { ButtonComponent, Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian'; import type { ButtonComponent } from 'obsidian';
import { MediaTypeModel } from '../models/MediaTypeModel'; import { Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
import MediaDbPlugin from '../main'; import type MediaDbPlugin from '../main';
import { SEARCH_MODAL_DEFAULT_OPTIONS, SearchModalData, SearchModalOptions } from '../utils/ModalHelper'; import type { MediaTypeModel } from '../models/MediaTypeModel';
import type { MediaType } from '../utils/MediaType';
import { MEDIA_TYPES } from '../utils/MediaTypeManager'; import { MEDIA_TYPES } from '../utils/MediaTypeManager';
import type { SearchModalData, SearchModalOptions } from '../utils/ModalHelper';
import { SEARCH_MODAL_DEFAULT_OPTIONS } from '../utils/ModalHelper';
import { unCamelCase } from '../utils/Utils'; import { unCamelCase } from '../utils/Utils';
import { MediaType } from '../utils/MediaType';
export class MediaDbSearchModal extends Modal { export class MediaDbSearchModal extends Modal {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -12,9 +14,9 @@ export class MediaDbSearchModal extends Modal {
query: string; query: string;
isBusy: boolean; isBusy: boolean;
title: string; title: string;
selectedTypes: { name: MediaType; selected: boolean }[]; selectedTypes: MediaType[];
searchBtn: ButtonComponent; searchBtn?: ButtonComponent;
submitCallback?: (res: SearchModalData) => void; submitCallback?: (res: SearchModalData) => void;
closeCallback?: (err?: Error) => void; closeCallback?: (err?: Error) => void;
@ -24,13 +26,10 @@ export class MediaDbSearchModal extends Modal {
super(plugin.app); super(plugin.app);
this.plugin = plugin; this.plugin = plugin;
this.selectedTypes = []; this.selectedTypes = [...(searchModalOptions.preselectedTypes ?? [])];
this.title = searchModalOptions.modalTitle; this.title = searchModalOptions.modalTitle ?? '';
this.query = searchModalOptions.prefilledSearchString; this.query = searchModalOptions.prefilledSearchString ?? '';
this.isBusy = false;
for (const mediaType of MEDIA_TYPES) {
this.selectedTypes.push({ name: mediaType, selected: searchModalOptions.preselectedTypes.contains(mediaType) });
}
} }
setSubmitCallback(submitCallback: (res: SearchModalData) => void): void { setSubmitCallback(submitCallback: (res: SearchModalData) => void): void {
@ -47,13 +46,13 @@ export class MediaDbSearchModal extends Modal {
} }
} }
async search(): Promise<MediaTypeModel[]> { async search(): Promise<void> {
if (!this.query || this.query.length < 3) { if (!this.query || this.query.length < 3) {
new Notice('MDB | Query too short'); new Notice('MDB | Query too short');
return; return;
} }
const types: MediaType[] = this.selectedTypes.filter(x => x.selected).map(x => x.name); const types: MediaType[] = this.selectedTypes;
if (types.length === 0) { if (types.length === 0) {
new Notice('MDB | No Type selected'); new Notice('MDB | No Type selected');
@ -62,10 +61,10 @@ export class MediaDbSearchModal extends Modal {
if (!this.isBusy) { if (!this.isBusy) {
this.isBusy = true; this.isBusy = true;
this.searchBtn.setDisabled(false); this.searchBtn?.setDisabled(false);
this.searchBtn.setButtonText('Searching...'); this.searchBtn?.setButtonText('Searching...');
this.submitCallback({ query: this.query, types: types }); this.submitCallback?.({ query: this.query, types: types });
} }
} }
@ -76,7 +75,7 @@ export class MediaDbSearchModal extends Modal {
const placeholder = 'Search by title'; const placeholder = 'Search by title';
const searchComponent = new TextComponent(contentEl); const searchComponent = new TextComponent(contentEl);
let currentToggle: ToggleComponent = null; let currentToggle: ToggleComponent | undefined = undefined;
searchComponent.inputEl.style.width = '100%'; searchComponent.inputEl.style.width = '100%';
searchComponent.setPlaceholder(placeholder); searchComponent.setPlaceholder(placeholder);
@ -100,7 +99,7 @@ export class MediaDbSearchModal extends Modal {
const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper); const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper);
apiToggleComponent.setTooltip(unCamelCase(mediaType)); apiToggleComponent.setTooltip(unCamelCase(mediaType));
apiToggleComponent.setValue(this.selectedTypes.find(x => x.name === mediaType).selected); apiToggleComponent.setValue(this.selectedTypes.contains(mediaType));
if (apiToggleComponent.getValue()) { if (apiToggleComponent.getValue()) {
currentToggle = apiToggleComponent; currentToggle = apiToggleComponent;
} }
@ -108,13 +107,13 @@ export class MediaDbSearchModal extends Modal {
if (value) { if (value) {
if (currentToggle && currentToggle !== apiToggleComponent) { if (currentToggle && currentToggle !== apiToggleComponent) {
currentToggle.setValue(false); currentToggle.setValue(false);
this.selectedTypes.find(x => x.name === mediaType).selected = false; this.selectedTypes = this.selectedTypes.filter(x => x !== mediaType);
} }
currentToggle = apiToggleComponent; currentToggle = apiToggleComponent;
this.selectedTypes.find(x => x.name === mediaType).selected = true; this.selectedTypes.push(mediaType);
} else { } else {
currentToggle = null; currentToggle = undefined;
this.selectedTypes.find(x => x.name === mediaType).selected = false; this.selectedTypes = this.selectedTypes.filter(x => x !== mediaType);
} }
}); });
apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl); apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl);
@ -140,7 +139,7 @@ export class MediaDbSearchModal extends Modal {
} }
onClose(): void { onClose(): void {
this.closeCallback(); this.closeCallback?.();
const { contentEl } = this; const { contentEl } = this;
contentEl.empty(); contentEl.empty();
} }

View file

@ -1,7 +1,8 @@
import { MediaTypeModel } from '../models/MediaTypeModel'; import type MediaDbPlugin from '../main';
import MediaDbPlugin from '../main'; import type { MediaTypeModel } from '../models/MediaTypeModel';
import type { SelectModalData, SelectModalOptions } from '../utils/ModalHelper';
import { SELECT_MODAL_OPTIONS_DEFAULT } from '../utils/ModalHelper';
import { SelectModal } from './SelectModal'; import { SelectModal } from './SelectModal';
import { SELECT_MODAL_OPTIONS_DEFAULT, SelectModalData, SelectModalOptions } from '../utils/ModalHelper';
export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> { export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -9,18 +10,18 @@ export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
busy: boolean; busy: boolean;
sendCallback: boolean; sendCallback: boolean;
submitCallback: (res: SelectModalData) => void; submitCallback?: (res: SelectModalData) => void;
closeCallback: (err?: Error) => void; closeCallback?: (err?: Error) => void;
skipCallback: () => void; skipCallback?: () => void;
constructor(plugin: MediaDbPlugin, selectModalOptions: SelectModalOptions) { constructor(plugin: MediaDbPlugin, selectModalOptions: SelectModalOptions) {
selectModalOptions = Object.assign({}, SELECT_MODAL_OPTIONS_DEFAULT, selectModalOptions); selectModalOptions = Object.assign({}, SELECT_MODAL_OPTIONS_DEFAULT, selectModalOptions);
super(plugin.app, selectModalOptions.elements, selectModalOptions.multiSelect); super(plugin.app, selectModalOptions.elements ?? [], selectModalOptions.multiSelect);
this.plugin = plugin; this.plugin = plugin;
this.title = selectModalOptions.modalTitle; this.title = selectModalOptions.modalTitle ?? '';
this.description = 'Select one or multiple search results.'; this.description = 'Select one or multiple search results.';
this.addSkipButton = selectModalOptions.skipButton; this.addSkipButton = selectModalOptions.skipButton ?? false;
this.busy = false; this.busy = false;
@ -50,17 +51,17 @@ export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
submit(): void { submit(): void {
if (!this.busy) { if (!this.busy) {
this.busy = true; this.busy = true;
this.submitButton.setButtonText('Creating entry...'); this.submitButton?.setButtonText('Creating entry...');
this.submitCallback({ selected: this.selectModalElements.filter(x => x.isActive()).map(x => x.value) }); this.submitCallback?.({ selected: this.selectModalElements.filter(x => x.isActive()).map(x => x.value) });
} }
} }
skip(): void { skip(): void {
this.skipButton.setButtonText('Skipping...'); this.skipButton?.setButtonText('Skipping...');
this.skipCallback(); this.skipCallback?.();
} }
onClose(): void { onClose(): void {
this.closeCallback(); this.closeCallback?.();
} }
} }

View file

@ -1,6 +1,7 @@
import { App, ButtonComponent, Modal, Setting } from 'obsidian'; import type { App, ButtonComponent } from 'obsidian';
import { SelectModalElement } from './SelectModalElement'; import { Modal, Setting } from 'obsidian';
import { mod } from '../utils/Utils'; import { mod } from '../utils/Utils';
import { SelectModalElement } from './SelectModalElement';
export abstract class SelectModal<T> extends Modal { export abstract class SelectModal<T> extends Modal {
allowMultiSelect: boolean; allowMultiSelect: boolean;
@ -142,7 +143,7 @@ export abstract class SelectModal<T> extends Modal {
} }
// nothing is highlighted // nothing is highlighted
this.selectModalElements.last().setHighlighted(true); this.selectModalElements.last()?.setHighlighted(true);
} }
highlightDown(): void { highlightDown(): void {
@ -154,20 +155,20 @@ export abstract class SelectModal<T> extends Modal {
} }
// nothing is highlighted // nothing is highlighted
this.selectModalElements.first().setHighlighted(true); this.selectModalElements.first()?.setHighlighted(true);
} }
private getNextSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> { private getNextSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> {
let nextId = selectModalElement.id + 1; let nextId = selectModalElement.id + 1;
nextId = mod(nextId, this.selectModalElements.length); nextId = mod(nextId, this.selectModalElements.length);
return this.selectModalElements.filter(x => x.id === nextId).first(); return this.selectModalElements.find(x => x.id === nextId)!;
} }
private getPreviousSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> { private getPreviousSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> {
let nextId = selectModalElement.id - 1; let nextId = selectModalElement.id - 1;
nextId = mod(nextId, this.selectModalElements.length); nextId = mod(nextId, this.selectModalElements.length);
return this.selectModalElements.filter(x => x.id === nextId).first(); return this.selectModalElements.find(x => x.id === nextId)!;
} }
} }

View file

@ -1,4 +1,4 @@
import { SelectModal } from './SelectModal'; import type { SelectModal } from './SelectModal';
export class SelectModalElement<T> { export class SelectModalElement<T> {
selectModal: SelectModal<T>; selectModal: SelectModal<T>;
@ -35,6 +35,8 @@ export class SelectModalElement<T> {
this.element.on('mouseleave', '#' + this.getHTMLId(), () => { this.element.on('mouseleave', '#' + this.getHTMLId(), () => {
this.setHighlighted(false); this.setHighlighted(false);
}); });
this.highlighted = false;
} }
getHTMLId(): string { getHTMLId(): string {

View file

@ -1,6 +1,8 @@
import { MediaTypeModel } from './MediaTypeModel';
import { mediaDbTag, migrateObject } from '../utils/Utils';
import { MediaType } from '../utils/MediaType'; import { MediaType } from '../utils/MediaType';
import { mediaDbTag, migrateObject, type ModelToData } from '../utils/Utils';
import { MediaTypeModel } from './MediaTypeModel';
export type BoardGameData = ModelToData<BoardGameModel>;
export class BoardGameModel extends MediaTypeModel { export class BoardGameModel extends MediaTypeModel {
genres: string[]; genres: string[];
@ -19,23 +21,23 @@ export class BoardGameModel extends MediaTypeModel {
personalRating: number; personalRating: number;
}; };
constructor(obj: any = {}) { constructor(obj: BoardGameData) {
super(); super();
this.genres = undefined; this.genres = [];
this.onlineRating = undefined; this.onlineRating = 0;
this.minPlayers = undefined; this.complexityRating = 0;
this.maxPlayers = undefined; this.minPlayers = 0;
this.playtime = undefined; this.maxPlayers = 0;
this.publishers = undefined; this.playtime = '';
this.complexityRating = undefined; this.publishers = [];
this.image = undefined; this.image = '';
this.released = undefined; this.released = false;
this.userData = { this.userData = {
played: undefined, played: false,
personalRating: undefined, personalRating: 0,
}; };
migrateObject(this, obj, this); migrateObject(this, obj, this);

View file

@ -1,6 +1,8 @@
import { MediaTypeModel } from './MediaTypeModel';
import { mediaDbTag, migrateObject } from '../utils/Utils';
import { MediaType } from '../utils/MediaType'; import { MediaType } from '../utils/MediaType';
import { mediaDbTag, migrateObject, type ModelToData } from '../utils/Utils';
import { MediaTypeModel } from './MediaTypeModel';
export type BookData = ModelToData<BookModel>;
export class BookModel extends MediaTypeModel { export class BookModel extends MediaTypeModel {
author: string; author: string;
@ -8,7 +10,6 @@ export class BookModel extends MediaTypeModel {
pages: number; pages: number;
image: string; image: string;
onlineRating: number; onlineRating: number;
english_title: string;
isbn: number; isbn: number;
isbn13: number; isbn13: number;
@ -20,22 +21,23 @@ export class BookModel extends MediaTypeModel {
personalRating: number; personalRating: number;
}; };
constructor(obj: any = {}) { constructor(obj: BookData) {
super(); super();
this.author = undefined; this.author = '';
this.pages = undefined; this.plot = '';
this.image = undefined; this.pages = 0;
this.onlineRating = undefined; this.image = '';
this.isbn = undefined; this.onlineRating = 0;
this.isbn13 = undefined; this.isbn = 0;
this.isbn13 = 0;
this.released = undefined; this.released = false;
this.userData = { this.userData = {
read: undefined, read: false,
lastRead: undefined, lastRead: '',
personalRating: undefined, personalRating: 0,
}; };
migrateObject(this, obj, this); migrateObject(this, obj, this);

View file

@ -1,6 +1,8 @@
import { MediaTypeModel } from './MediaTypeModel';
import { mediaDbTag, migrateObject } from '../utils/Utils';
import { MediaType } from '../utils/MediaType'; import { MediaType } from '../utils/MediaType';
import { mediaDbTag, migrateObject, type ModelToData } from '../utils/Utils';
import { MediaTypeModel } from './MediaTypeModel';
export type GameData = ModelToData<GameModel>;
export class GameModel extends MediaTypeModel { export class GameModel extends MediaTypeModel {
developers: string[]; developers: string[];
@ -17,19 +19,21 @@ export class GameModel extends MediaTypeModel {
personalRating: number; personalRating: number;
}; };
constructor(obj: any = {}) { constructor(obj: GameData) {
super(); super();
this.developers = undefined; this.developers = [];
this.publishers = undefined; this.publishers = [];
this.genres = undefined; this.genres = [];
this.onlineRating = undefined; this.onlineRating = 0;
this.image = undefined; this.image = '';
this.released = undefined;
this.releaseDate = undefined; this.released = false;
this.releaseDate = '';
this.userData = { this.userData = {
played: undefined, played: false,
personalRating: undefined, personalRating: 0,
}; };
migrateObject(this, obj, this); migrateObject(this, obj, this);

View file

@ -1,19 +1,12 @@
import { MediaTypeModel } from './MediaTypeModel';
import { mediaDbTag, migrateObject } from '../utils/Utils';
import { MediaType } from '../utils/MediaType'; import { MediaType } from '../utils/MediaType';
import { mediaDbTag, migrateObject, type ModelToData } from '../utils/Utils';
import { MediaTypeModel } from './MediaTypeModel';
export type MangaData = ModelToData<MangaModel>;
export class MangaModel extends MediaTypeModel { export class MangaModel extends MediaTypeModel {
type: string;
subType: string;
title: string;
plot: string; plot: string;
englishTitle: string;
alternateTitles: string[]; alternateTitles: string[];
year: string;
dataSource: string;
url: string;
id: string;
genres: string[]; genres: string[];
authors: string[]; authors: string[];
chapters: number; chapters: number;
@ -32,27 +25,27 @@ export class MangaModel extends MediaTypeModel {
personalRating: number; personalRating: number;
}; };
constructor(obj: any = {}) { constructor(obj: MangaData) {
super(); super();
this.plot = undefined; this.plot = '';
this.genres = undefined; this.alternateTitles = [];
this.authors = undefined; this.genres = [];
this.alternateTitles = undefined; this.authors = [];
this.chapters = undefined; this.chapters = 0;
this.volumes = undefined; this.volumes = 0;
this.onlineRating = undefined; this.onlineRating = 0;
this.image = undefined; this.image = '';
this.released = undefined; this.released = false;
this.status = undefined; this.status = '';
this.publishedFrom = undefined; this.publishedFrom = '';
this.publishedTo = undefined; this.publishedTo = '';
this.userData = { this.userData = {
watched: undefined, watched: false,
lastWatched: undefined, lastWatched: '',
personalRating: undefined, personalRating: 0,
}; };
migrateObject(this, obj, this); migrateObject(this, obj, this);

View file

@ -1,4 +1,4 @@
import { MediaType } from '../utils/MediaType'; import type { MediaType } from '../utils/MediaType';
export abstract class MediaTypeModel { export abstract class MediaTypeModel {
type: string; type: string;
@ -13,14 +13,14 @@ export abstract class MediaTypeModel {
userData: object; userData: object;
protected constructor() { protected constructor() {
this.type = undefined; this.type = '';
this.subType = undefined; this.subType = '';
this.title = undefined; this.title = '';
this.englishTitle = undefined; this.englishTitle = '';
this.year = undefined; this.year = '';
this.dataSource = undefined; this.dataSource = '';
this.url = undefined; this.url = '';
this.id = undefined; this.id = '';
this.userData = {}; this.userData = {};
} }

View file

@ -1,6 +1,8 @@
import { MediaTypeModel } from './MediaTypeModel';
import { mediaDbTag, migrateObject } from '../utils/Utils';
import { MediaType } from '../utils/MediaType'; import { MediaType } from '../utils/MediaType';
import { mediaDbTag, migrateObject, type ModelToData } from '../utils/Utils';
import { MediaTypeModel } from './MediaTypeModel';
export type MovieData = ModelToData<MovieModel>;
export class MovieModel extends MediaTypeModel { export class MovieModel extends MediaTypeModel {
plot: string; plot: string;
@ -23,27 +25,27 @@ export class MovieModel extends MediaTypeModel {
personalRating: number; personalRating: number;
}; };
constructor(obj: any = {}) { constructor(obj: MovieData) {
super(); super();
this.plot = undefined; this.plot = '';
this.genres = undefined; this.genres = [];
this.director = undefined; this.director = [];
this.writer = undefined; this.writer = [];
this.studio = undefined; this.studio = [];
this.duration = undefined; this.duration = '';
this.onlineRating = undefined; this.onlineRating = 0;
this.actors = undefined; this.actors = [];
this.image = undefined; this.image = '';
this.released = undefined; this.released = false;
this.streamingServices = undefined; this.streamingServices = [];
this.premiere = undefined; this.premiere = '';
this.userData = { this.userData = {
watched: undefined, watched: false,
lastWatched: undefined, lastWatched: '',
personalRating: undefined, personalRating: 0,
}; };
migrateObject(this, obj, this); migrateObject(this, obj, this);

View file

@ -1,35 +1,28 @@
import { MediaTypeModel } from './MediaTypeModel';
import { mediaDbTag, migrateObject } from '../utils/Utils';
import { MediaType } from '../utils/MediaType'; import { MediaType } from '../utils/MediaType';
import { mediaDbTag, migrateObject, type ModelToData } from '../utils/Utils';
import { MediaTypeModel } from './MediaTypeModel';
export type MusicReleaseData = ModelToData<MusicReleaseModel>;
export class MusicReleaseModel extends MediaTypeModel { export class MusicReleaseModel extends MediaTypeModel {
type: string;
subType: string;
title: string;
englishTitle: string;
year: string;
dataSource: string;
url: string;
id: string;
image: string;
genres: string[]; genres: string[];
artists: string[]; artists: string[];
image: string;
rating: number; rating: number;
userData: { userData: {
personalRating: number; personalRating: number;
}; };
constructor(obj: any = {}) { constructor(obj: MusicReleaseData) {
super(); super();
this.genres = undefined; this.genres = [];
this.artists = undefined; this.artists = [];
this.image = undefined; this.image = '';
this.rating = undefined; this.rating = 0;
this.userData = { this.userData = {
personalRating: undefined, personalRating: 0,
}; };
migrateObject(this, obj, this); migrateObject(this, obj, this);

View file

@ -1,17 +1,10 @@
import { MediaTypeModel } from './MediaTypeModel';
import { mediaDbTag, migrateObject } from '../utils/Utils';
import { MediaType } from '../utils/MediaType'; import { MediaType } from '../utils/MediaType';
import { mediaDbTag, migrateObject, type ModelToData } from '../utils/Utils';
import { MediaTypeModel } from './MediaTypeModel';
export type SeriesData = ModelToData<SeriesModel>;
export class SeriesModel extends MediaTypeModel { export class SeriesModel extends MediaTypeModel {
type: string;
subType: string;
title: string;
englishTitle: string;
year: string;
dataSource: string;
url: string;
id: string;
plot: string; plot: string;
genres: string[]; genres: string[];
writer: string[]; writer: string[];
@ -34,29 +27,29 @@ export class SeriesModel extends MediaTypeModel {
personalRating: number; personalRating: number;
}; };
constructor(obj: any = {}) { constructor(obj: SeriesData) {
super(); super();
this.plot = undefined; this.plot = '';
this.genres = undefined; this.genres = [];
this.writer = undefined; this.writer = [];
this.studio = undefined; this.studio = [];
this.episodes = undefined; this.episodes = 0;
this.duration = undefined; this.duration = '';
this.onlineRating = undefined; this.onlineRating = 0;
this.actors = undefined; this.actors = [];
this.image = undefined; this.image = '';
this.released = undefined; this.released = false;
this.streamingServices = undefined; this.streamingServices = [];
this.airing = undefined; this.airing = false;
this.airedFrom = undefined; this.airedFrom = '';
this.airedTo = undefined; this.airedTo = '';
this.userData = { this.userData = {
watched: undefined, watched: false,
lastWatched: undefined, lastWatched: '',
personalRating: undefined, personalRating: 0,
}; };
migrateObject(this, obj, this); migrateObject(this, obj, this);

View file

@ -1,17 +1,10 @@
import { MediaTypeModel } from './MediaTypeModel';
import { mediaDbTag, migrateObject } from '../utils/Utils';
import { MediaType } from '../utils/MediaType'; import { MediaType } from '../utils/MediaType';
import { mediaDbTag, migrateObject, type ModelToData } from '../utils/Utils';
import { MediaTypeModel } from './MediaTypeModel';
export type WikiData = ModelToData<WikiModel>;
export class WikiModel extends MediaTypeModel { export class WikiModel extends MediaTypeModel {
type: string;
subType: string;
title: string;
englishTitle: string;
year: string;
dataSource: string;
url: string;
id: string;
wikiUrl: string; wikiUrl: string;
lastUpdated: string; lastUpdated: string;
length: number; length: number;
@ -19,13 +12,13 @@ export class WikiModel extends MediaTypeModel {
userData: Record<string, unknown>; userData: Record<string, unknown>;
constructor(obj: any = {}) { constructor(obj: WikiData) {
super(); super();
this.wikiUrl = undefined; this.wikiUrl = '';
this.lastUpdated = undefined; this.lastUpdated = '';
this.length = undefined; this.length = 0;
this.article = undefined; this.article = '';
this.userData = {}; this.userData = {};
migrateObject(this, obj, this); migrateObject(this, obj, this);

View file

@ -4,13 +4,16 @@
import { setIcon } from 'obsidian'; import { setIcon } from 'obsidian';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
export let iconName: string = ''; interface Props {
export let iconSize: number = 20; iconName?: string;
}
let iconEl: HTMLElement; let { iconName = '' }: Props = $props();
let iconEl: HTMLElement | undefined = $state();
onMount(() => { onMount(() => {
setIcon(iconEl, iconName, iconSize); setIcon(iconEl!, iconName);
}); });
</script> </script>

View file

@ -1,6 +1,6 @@
import { PropertyMappingOption } from './PropertyMapping'; import type MediaDbPlugin from '../main';
import { MEDIA_TYPES } from '../utils/MediaTypeManager'; import { MEDIA_TYPES } from '../utils/MediaTypeManager';
import MediaDbPlugin from '../main'; import { PropertyMappingOption } from './PropertyMapping';
export class PropertyMapper { export class PropertyMapper {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
@ -66,7 +66,7 @@ export class PropertyMapper {
return obj; return obj;
} }
const propertyMappings = this.plugin.settings.propertyMappingModels.find(x => x.type === obj.type).properties; const propertyMappings = this.plugin.settings.propertyMappingModels.find(x => x.type === obj.type)?.properties ?? [];
const originalObj: Record<string, unknown> = {}; const originalObj: Record<string, unknown> = {};

View file

@ -1,5 +1,5 @@
import type { MediaType } from '../utils/MediaType';
import { containsOnlyLettersAndUnderscores, PropertyMappingNameConflictError, PropertyMappingValidationError } from '../utils/Utils'; import { containsOnlyLettersAndUnderscores, PropertyMappingNameConflictError, PropertyMappingValidationError } from '../utils/Utils';
import { MediaType } from '../utils/MediaType';
export enum PropertyMappingOption { export enum PropertyMappingOption {
Default = 'default', Default = 'default',

View file

@ -1,18 +1,22 @@
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { PropertyMappingModel, PropertyMappingOption, propertyMappingOptions } from './PropertyMapping'; import { PropertyMappingModel, PropertyMappingOption, propertyMappingOptions } from './PropertyMapping';
import { capitalizeFirstLetter } from '../utils/Utils'; import { capitalizeFirstLetter } from '../utils/Utils';
import Icon from './Icon.svelte'; import Icon from './Icon.svelte';
export let model: PropertyMappingModel; interface Props {
export let save: (model: PropertyMappingModel) => void; model: PropertyMappingModel;
save: (model: PropertyMappingModel) => void;
let validationResult: { res: boolean; err?: Error };
$: modelChanged(model);
function modelChanged(model: PropertyMappingModel) {
validationResult = model.validate();
} }
let { model, save }: Props = $props();
let validationResult: { res: boolean; err?: Error } | undefined = $state();
$effect(() => {
validationResult = model.validate();
});
</script> </script>
<div class="media-db-plugin-property-mappings-model-container"> <div class="media-db-plugin-property-mappings-model-container">
@ -51,7 +55,7 @@
{/if} {/if}
<button <button
class="media-db-plugin-property-mappings-save-button {validationResult?.res ? 'mod-cta' : 'mod-muted'}" class="media-db-plugin-property-mappings-save-button {validationResult?.res ? 'mod-cta' : 'mod-muted'}"
on:click={() => { onclick={() => {
if (model.validate().res) save(model); if (model.validate().res) save(model);
}} }}
>Save >Save

View file

@ -2,8 +2,12 @@
import { PropertyMappingModel } from './PropertyMapping'; import { PropertyMappingModel } from './PropertyMapping';
import PropertyMappingModelComponent from './PropertyMappingModelComponent.svelte'; import PropertyMappingModelComponent from './PropertyMappingModelComponent.svelte';
export let models: PropertyMappingModel[] = []; interface Props {
export let save: (model: PropertyMappingModel) => void; models?: PropertyMappingModel[];
save: (model: PropertyMappingModel) => void;
}
let { models = [], save }: Props = $props();
</script> </script>
<div class="setting-item" style="display: flex; gap: 10px; flex-direction: column; align-items: stretch;"> <div class="setting-item" style="display: flex; gap: 10px; flex-direction: column; align-items: stretch;">

View file

@ -1,13 +1,14 @@
import { App, Notice, PluginSettingTab, Setting } from 'obsidian'; import type { App } from 'obsidian';
import { Notice, PluginSettingTab, Setting } from 'obsidian';
import MediaDbPlugin from '../main'; import { mount } from 'svelte';
import { FolderSuggest } from './suggesters/FolderSuggest'; import type MediaDbPlugin from '../main';
import { FileSuggest } from './suggesters/FileSuggest'; import type { MediaTypeModel } from '../models/MediaTypeModel';
import PropertyMappingModelsComponent from './PropertyMappingModelsComponent.svelte';
import { PropertyMapping, PropertyMappingModel, PropertyMappingOption } from './PropertyMapping';
import { MEDIA_TYPES } from '../utils/MediaTypeManager'; import { MEDIA_TYPES } from '../utils/MediaTypeManager';
import { MediaTypeModel } from '../models/MediaTypeModel';
import { fragWithHTML } from '../utils/Utils'; import { fragWithHTML } from '../utils/Utils';
import { PropertyMapping, PropertyMappingModel, PropertyMappingOption } from './PropertyMapping';
import PropertyMappingModelsComponent from './PropertyMappingModelsComponent.svelte';
import { FileSuggest } from './suggesters/FileSuggest';
import { FolderSuggest } from './suggesters/FolderSuggest';
export interface MediaDbPluginSettings { export interface MediaDbPluginSettings {
OMDbKey: string; OMDbKey: string;
@ -254,7 +255,10 @@ export class MediaDbSettingTab extends PluginSettingTab {
.onChange(data => { .onChange(data => {
const newDateFormat = data ? data : DEFAULT_SETTINGS.customDateFormat; const newDateFormat = data ? data : DEFAULT_SETTINGS.customDateFormat;
this.plugin.settings.customDateFormat = newDateFormat; this.plugin.settings.customDateFormat = newDateFormat;
document.getElementById('media-db-dateformat-preview').textContent = this.plugin.dateFormatter.getPreview(newDateFormat); // update preview const previewEl = document.getElementById('media-db-dateformat-preview');
if (previewEl) {
previewEl.textContent = this.plugin.dateFormatter.getPreview(newDateFormat); // update preview
}
void this.plugin.saveSettings(); void this.plugin.saveSettings();
}); });
}); });
@ -689,7 +693,7 @@ export class MediaDbSettingTab extends PluginSettingTab {
Don't forget to save your changes using the save button for each individual category. Don't forget to save your changes using the save button for each individual category.
</p>`; </p>`;
new PropertyMappingModelsComponent({ mount(PropertyMappingModelsComponent, {
target: this.containerEl, target: this.containerEl,
props: { props: {
models: this.plugin.settings.propertyMappingModels.map(x => x.copy()), models: this.plugin.settings.propertyMappingModels.map(x => x.copy()),

View file

@ -1,5 +1,6 @@
import type { TAbstractFile } from 'obsidian';
import { TFile } from 'obsidian';
import { TextInputSuggest } from './Suggest'; import { TextInputSuggest } from './Suggest';
import { TAbstractFile, TFile } from 'obsidian';
export class FileSuggest extends TextInputSuggest<TFile> { export class FileSuggest extends TextInputSuggest<TFile> {
getSuggestions(inputStr: string): TFile[] { getSuggestions(inputStr: string): TFile[] {

View file

@ -1,6 +1,7 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes // Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
import { TAbstractFile, TFolder } from 'obsidian'; import type { TAbstractFile } from 'obsidian';
import { TFolder } from 'obsidian';
import { TextInputSuggest } from './Suggest'; import { TextInputSuggest } from './Suggest';
export class FolderSuggest extends TextInputSuggest<TFolder> { export class FolderSuggest extends TextInputSuggest<TFolder> {

View file

@ -1,28 +1,34 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes // Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
import { App, ISuggestOwner, Scope } from 'obsidian'; import type { Instance as PopperInstance } from '@popperjs/core';
import { createPopper, Instance as PopperInstance } from '@popperjs/core'; import { createPopper } from '@popperjs/core';
import type { App, ISuggestOwner } from 'obsidian';
import { Scope } from 'obsidian';
import { wrapAround } from 'src/utils/Utils'; import { wrapAround } from 'src/utils/Utils';
export class Suggest<T> { export class Suggest<T> {
private owner: ISuggestOwner<T>; private owner: ISuggestOwner<T>;
private values: T[]; private values: T[];
private suggestions: HTMLDivElement[]; private suggestions: HTMLElement[];
private selectedItem: number; private selectedItem: number;
private containerEl: HTMLElement; private containerEl: HTMLElement;
constructor(owner: ISuggestOwner<T>, containerEl: HTMLElement, scope: Scope) { constructor(owner: ISuggestOwner<T>, containerEl: HTMLElement, scope: Scope) {
this.owner = owner; this.owner = owner;
this.containerEl = containerEl; this.containerEl = containerEl;
this.values = [];
this.suggestions = [];
this.selectedItem = 0;
containerEl.on('click', '.suggestion-item', this.onSuggestionClick.bind(this)); containerEl.on('click', '.suggestion-item', (e, el) => this.onSuggestionClick(e, el));
containerEl.on('mousemove', '.suggestion-item', this.onSuggestionMouseover.bind(this)); containerEl.on('mousemove', '.suggestion-item', (e, el) => this.onSuggestionMouseover(e, el));
scope.register([], 'ArrowUp', event => { scope.register([], 'ArrowUp', event => {
if (!event.isComposing) { if (!event.isComposing) {
this.setSelectedItem(this.selectedItem - 1, true); this.setSelectedItem(this.selectedItem - 1, true);
return false; return false;
} }
return undefined;
}); });
scope.register([], 'ArrowDown', event => { scope.register([], 'ArrowDown', event => {
@ -30,6 +36,7 @@ export class Suggest<T> {
this.setSelectedItem(this.selectedItem + 1, true); this.setSelectedItem(this.selectedItem + 1, true);
return false; return false;
} }
return undefined;
}); });
scope.register([], 'Enter', event => { scope.register([], 'Enter', event => {
@ -37,10 +44,11 @@ export class Suggest<T> {
this.useSelectedItem(event); this.useSelectedItem(event);
return false; return false;
} }
return undefined;
}); });
} }
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void { onSuggestionClick(event: MouseEvent, el: HTMLElement): void {
event.preventDefault(); event.preventDefault();
const item = this.suggestions.indexOf(el); const item = this.suggestions.indexOf(el);
@ -48,7 +56,7 @@ export class Suggest<T> {
this.useSelectedItem(event); this.useSelectedItem(event);
} }
onSuggestionMouseover(_event: MouseEvent, el: HTMLDivElement): void { onSuggestionMouseover(_event: MouseEvent, el: HTMLElement): void {
const item = this.suggestions.indexOf(el); const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false); this.setSelectedItem(item, false);
} }
@ -95,7 +103,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
protected app: App; protected app: App;
protected inputEl: HTMLInputElement; protected inputEl: HTMLInputElement;
private popper: PopperInstance; private popper?: PopperInstance;
private scope: Scope; private scope: Scope;
private suggestEl: HTMLElement; private suggestEl: HTMLElement;
private suggest: Suggest<T>; private suggest: Suggest<T>;
@ -126,13 +134,13 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
if (suggestions.length > 0) { if (suggestions.length > 0) {
this.suggest.setSuggestions(suggestions); this.suggest.setSuggestions(suggestions);
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
this.open((<any>this.app).dom.appContainerEl, this.inputEl); this.open((this.app as any).dom.appContainerEl, this.inputEl);
} }
} }
open(container: HTMLElement, inputEl: HTMLElement): void { open(container: HTMLElement, inputEl: HTMLElement): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>this.app).keymap.pushScope(this.scope); (this.app as any).keymap.pushScope(this.scope);
container.appendChild(this.suggestEl); container.appendChild(this.suggestEl);
this.popper = createPopper(inputEl, this.suggestEl, { this.popper = createPopper(inputEl, this.suggestEl, {
@ -162,7 +170,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
close(): void { close(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>this.app).keymap.popScope(this.scope); (this.app as any).keymap.popScope(this.scope);
this.suggest.setSuggestions([]); this.suggest.setSuggestions([]);
this.popper?.destroy(); this.popper?.destroy();

View file

@ -1,16 +1,17 @@
import { MediaDbPluginSettings } from '../settings/Settings'; import type { App, TAbstractFile, TFile } from 'obsidian';
import { MediaType } from './MediaType'; import { TFolder } from 'obsidian';
import { MediaTypeModel } from '../models/MediaTypeModel';
import { replaceTags } from './Utils';
import { App, TAbstractFile, TFile, TFolder } from 'obsidian';
import { MovieModel } from '../models/MovieModel';
import { SeriesModel } from '../models/SeriesModel';
import { MangaModel } from '../models/MangaModel';
import { GameModel } from '../models/GameModel';
import { WikiModel } from '../models/WikiModel';
import { MusicReleaseModel } from '../models/MusicReleaseModel';
import { BoardGameModel } from '../models/BoardGameModel'; import { BoardGameModel } from '../models/BoardGameModel';
import { BookModel } from '../models/BookModel'; import { BookModel } from '../models/BookModel';
import { GameModel } from '../models/GameModel';
import { MangaModel } from '../models/MangaModel';
import type { MediaTypeModel } from '../models/MediaTypeModel';
import { MovieModel } from '../models/MovieModel';
import { MusicReleaseModel } from '../models/MusicReleaseModel';
import { SeriesModel } from '../models/SeriesModel';
import type { MediaDbPluginSettings } from '../settings/Settings';
import { MediaType } from './MediaType';
import { replaceTags } from './Utils';
import { WikiModel } from '../models/WikiModel';
export const MEDIA_TYPES: MediaType[] = [ export const MEDIA_TYPES: MediaType[] = [
MediaType.Movie, MediaType.Movie,
@ -28,7 +29,11 @@ export class MediaTypeManager {
mediaTemplateMap: Map<MediaType, string>; mediaTemplateMap: Map<MediaType, string>;
mediaFolderMap: Map<MediaType, string>; mediaFolderMap: Map<MediaType, string>;
constructor() {} constructor() {
this.mediaFileNameTemplateMap = new Map<MediaType, string>();
this.mediaTemplateMap = new Map<MediaType, string>();
this.mediaFolderMap = new Map<MediaType, string>();
}
updateTemplates(settings: MediaDbPluginSettings): void { updateTemplates(settings: MediaDbPluginSettings): void {
this.mediaFileNameTemplateMap = new Map<MediaType, string>(); this.mediaFileNameTemplateMap = new Map<MediaType, string>();
@ -66,7 +71,7 @@ export class MediaTypeManager {
getFileName(mediaTypeModel: MediaTypeModel): string { getFileName(mediaTypeModel: MediaTypeModel): string {
// Ignore undefined tags since some search APIs do not return all properties in the model and produce clean file names even if errors occur // Ignore undefined tags since some search APIs do not return all properties in the model and produce clean file names even if errors occur
return replaceTags(this.mediaFileNameTemplateMap.get(mediaTypeModel.getMediaType()), mediaTypeModel, true); return replaceTags(this.mediaFileNameTemplateMap.get(mediaTypeModel.getMediaType())!, mediaTypeModel, true);
} }
async getTemplate(mediaTypeModel: MediaTypeModel, app: App): Promise<string> { async getTemplate(mediaTypeModel: MediaTypeModel, app: App): Promise<string> {
@ -76,7 +81,7 @@ export class MediaTypeManager {
return ''; return '';
} }
let templateFile = app.vault.getAbstractFileByPath(templateFilePath); let templateFile = app.vault.getAbstractFileByPath(templateFilePath) ?? undefined;
// WARNING: This was previously selected by filename, but that could lead to collisions and unwanted effects. // WARNING: This was previously selected by filename, but that could lead to collisions and unwanted effects.
// This now falls back to the previous method if no file is found // This now falls back to the previous method if no file is found
@ -107,7 +112,7 @@ export class MediaTypeManager {
if (!(await app.vault.adapter.exists(folderPath))) { if (!(await app.vault.adapter.exists(folderPath))) {
await app.vault.createFolder(folderPath); await app.vault.createFolder(folderPath);
} }
const folder: TAbstractFile = app.vault.getAbstractFileByPath(folderPath); const folder = app.vault.getAbstractFileByPath(folderPath);
if (!(folder instanceof TFolder)) { if (!(folder instanceof TFolder)) {
throw Error(`Expected ${folder} to be instance of TFolder`); throw Error(`Expected ${folder} to be instance of TFolder`);
@ -141,6 +146,6 @@ export class MediaTypeManager {
return new BookModel(obj); return new BookModel(obj);
} }
return undefined; throw new Error(`Unknown media type: ${mediaType}`);
} }
} }

View file

@ -1,12 +1,12 @@
import { Notice } from 'obsidian';
import { MediaDbPreviewModal } from 'src/modals/MediaDbPreviewModal';
import type MediaDbPlugin from '../main';
import { MediaDbAdvancedSearchModal } from '../modals/MediaDbAdvancedSearchModal'; import { MediaDbAdvancedSearchModal } from '../modals/MediaDbAdvancedSearchModal';
import { MediaDbIdSearchModal } from '../modals/MediaDbIdSearchModal'; import { MediaDbIdSearchModal } from '../modals/MediaDbIdSearchModal';
import { MediaTypeModel } from '../models/MediaTypeModel';
import { MediaDbSearchResultModal } from '../modals/MediaDbSearchResultModal';
import { Notice } from 'obsidian';
import MediaDbPlugin from '../main';
import { MediaDbPreviewModal } from 'src/modals/MediaDbPreviewModal';
import { MediaDbSearchModal } from '../modals/MediaDbSearchModal'; import { MediaDbSearchModal } from '../modals/MediaDbSearchModal';
import { MediaType } from './MediaType'; import { MediaDbSearchResultModal } from '../modals/MediaDbSearchResultModal';
import type { MediaTypeModel } from '../models/MediaTypeModel';
import type { MediaType } from './MediaType';
export enum ModalResultCode { export enum ModalResultCode {
SUCCESS = 'SUCCESS', SUCCESS = 'SUCCESS',
@ -15,60 +15,59 @@ export enum ModalResultCode {
ERROR = 'ERROR', ERROR = 'ERROR',
} }
type ModalResult<T> =
| {
code: ModalResultCode.CLOSE;
}
| {
code: ModalResultCode.ERROR;
error: Error;
}
| {
code: ModalResultCode.SUCCESS;
data: T;
};
type SkippableModalResult<T> =
| ModalResult<T>
| {
code: ModalResultCode.SKIP;
};
/** /**
* Object containing the data {@link ModalHelper.createSearchModal} returns. * Object containing the data {@link ModalHelper.createSearchModal} returns.
* On {@link ModalResultCode.SUCCESS} this contains {@link SearchModalData}. * On {@link ModalResultCode.SUCCESS} this contains {@link SearchModalData}.
* On {@link ModalResultCode.ERROR} this contains a reference to that error. * On {@link ModalResultCode.ERROR} this contains a reference to that error.
*/ */
export interface SearchModalResult { export type SearchModalResult = ModalResult<SearchModalData>;
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
data?: SearchModalData;
error?: Error;
}
/** /**
* Object containing the data {@link ModalHelper.createAdvancedSearchModal} returns. * Object containing the data {@link ModalHelper.createAdvancedSearchModal} returns.
* On {@link ModalResultCode.SUCCESS} this contains {@link AdvancedSearchModalData}. * On {@link ModalResultCode.SUCCESS} this contains {@link AdvancedSearchModalData}.
* On {@link ModalResultCode.ERROR} this contains a reference to that error. * On {@link ModalResultCode.ERROR} this contains a reference to that error.
*/ */
export interface AdvancedSearchModalResult { export type AdvancedSearchModalResult = ModalResult<AdvancedSearchModalData>;
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
data?: AdvancedSearchModalData;
error?: Error;
}
/** /**
* Object containing the data {@link ModalHelper.createIdSearchModal} returns. * Object containing the data {@link ModalHelper.createIdSearchModal} returns.
* On {@link ModalResultCode.SUCCESS} this contains {@link IdSearchModalData}. * On {@link ModalResultCode.SUCCESS} this contains {@link IdSearchModalData}.
* On {@link ModalResultCode.ERROR} this contains a reference to that error. * On {@link ModalResultCode.ERROR} this contains a reference to that error.
*/ */
export interface IdSearchModalResult { export type IdSearchModalResult = ModalResult<IdSearchModalData>;
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
data?: IdSearchModalData;
error?: Error;
}
/** /**
* Object containing the data {@link ModalHelper.createSelectModal} returns. * Object containing the data {@link ModalHelper.createSelectModal} returns.
* On {@link ModalResultCode.SUCCESS} this contains {@link SelectModalData}. * On {@link ModalResultCode.SUCCESS} this contains {@link SelectModalData}.
* On {@link ModalResultCode.ERROR} this contains a reference to that error. * On {@link ModalResultCode.ERROR} this contains a reference to that error.
*/ */
export interface SelectModalResult { export type SelectModalResult = SkippableModalResult<SelectModalData>;
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.SKIP | ModalResultCode.ERROR;
data?: SelectModalData;
error?: Error;
}
/** /**
* Object containing the data {@link ModalHelper.createPreviewModal} returns. * Object containing the data {@link ModalHelper.createPreviewModal} returns.
* On {@link ModalResultCode.SUCCESS} this contains {@link PreviewModalData}. * On {@link ModalResultCode.SUCCESS} this contains {@link PreviewModalData}.
* On {@link ModalResultCode.ERROR} this contains a reference to that error. * On {@link ModalResultCode.ERROR} this contains a reference to that error.
*/ */
export interface PreviewModalResult { export type PreviewModalResult = ModalResult<PreviewModalData>;
code: ModalResultCode.SUCCESS | ModalResultCode.CLOSE | ModalResultCode.ERROR;
data?: PreviewModalData;
error?: Error;
}
/** /**
* The data the search modal returns. * The data the search modal returns.
@ -248,7 +247,10 @@ export class ModalHelper {
* @param submitCallback the callback that gets executed after the modal has been submitted, but after it has been closed * @param submitCallback the callback that gets executed after the modal has been submitted, but after it has been closed
* @returns the user input or nothing and a reference to the modal. * @returns the user input or nothing and a reference to the modal.
*/ */
async openSearchModal(searchModalOptions: SearchModalOptions, submitCallback: (searchModalData: SearchModalData) => Promise<MediaTypeModel[]>): Promise<MediaTypeModel[]> { async openSearchModal(
searchModalOptions: SearchModalOptions,
submitCallback: (searchModalData: SearchModalData) => Promise<MediaTypeModel[]>,
): Promise<MediaTypeModel[] | undefined> {
const { searchModalResult, searchModal } = await this.createSearchModal(searchModalOptions); const { searchModalResult, searchModal } = await this.createSearchModal(searchModalOptions);
console.debug(`MDB | searchModal closed with code ${searchModalResult.code}`); console.debug(`MDB | searchModal closed with code ${searchModalResult.code}`);
@ -271,7 +273,7 @@ export class ModalHelper {
return callbackRes; return callbackRes;
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
new Notice(e.toString()); new Notice(`${e}`);
searchModal.close(); searchModal.close();
return undefined; return undefined;
} }
@ -314,7 +316,7 @@ export class ModalHelper {
async openAdvancedSearchModal( async openAdvancedSearchModal(
advancedSearchModalOptions: AdvancedSearchModalOptions, advancedSearchModalOptions: AdvancedSearchModalOptions,
submitCallback: (advancedSearchModalData: AdvancedSearchModalData) => Promise<MediaTypeModel[]>, submitCallback: (advancedSearchModalData: AdvancedSearchModalData) => Promise<MediaTypeModel[]>,
): Promise<MediaTypeModel[]> { ): Promise<MediaTypeModel[] | undefined> {
const { advancedSearchModalResult, advancedSearchModal } = await this.createAdvancedSearchModal(advancedSearchModalOptions); const { advancedSearchModalResult, advancedSearchModal } = await this.createAdvancedSearchModal(advancedSearchModalOptions);
console.debug(`MDB | advencedSearchModal closed with code ${advancedSearchModalResult.code}`); console.debug(`MDB | advencedSearchModal closed with code ${advancedSearchModalResult.code}`);
@ -337,7 +339,7 @@ export class ModalHelper {
return callbackRes; return callbackRes;
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
new Notice(e.toString()); new Notice(`${e}`);
advancedSearchModal.close(); advancedSearchModal.close();
return undefined; return undefined;
} }
@ -377,8 +379,8 @@ export class ModalHelper {
*/ */
async openIdSearchModal( async openIdSearchModal(
idSearchModalOptions: IdSearchModalOptions, idSearchModalOptions: IdSearchModalOptions,
submitCallback: (idSearchModalData: IdSearchModalData) => Promise<MediaTypeModel>, submitCallback: (idSearchModalData: IdSearchModalData) => Promise<MediaTypeModel | undefined>,
): Promise<MediaTypeModel> { ): Promise<MediaTypeModel | undefined> {
const { idSearchModalResult, idSearchModal } = await this.createIdSearchModal(idSearchModalOptions); const { idSearchModalResult, idSearchModal } = await this.createIdSearchModal(idSearchModalOptions);
console.debug(`MDB | idSearchModal closed with code ${idSearchModalResult.code}`); console.debug(`MDB | idSearchModal closed with code ${idSearchModalResult.code}`);
@ -396,12 +398,12 @@ export class ModalHelper {
} }
try { try {
const callbackRes: MediaTypeModel = await submitCallback(idSearchModalResult.data); const callbackRes = await submitCallback(idSearchModalResult.data);
idSearchModal.close(); idSearchModal.close();
return callbackRes; return callbackRes;
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
new Notice(e.toString()); new Notice(`${e}`);
idSearchModal.close(); idSearchModal.close();
return undefined; return undefined;
} }
@ -440,7 +442,10 @@ export class ModalHelper {
* @param submitCallback the callback that gets executed after the modal has been submitted, but before it has been closed * @param submitCallback the callback that gets executed after the modal has been submitted, but before it has been closed
* @returns the user input or nothing and a reference to the modal. * @returns the user input or nothing and a reference to the modal.
*/ */
async openSelectModal(selectModalOptions: SelectModalOptions, submitCallback: (selectModalData: SelectModalData) => Promise<MediaTypeModel[]>): Promise<MediaTypeModel[]> { async openSelectModal(
selectModalOptions: SelectModalOptions,
submitCallback: (selectModalData: SelectModalData) => Promise<MediaTypeModel[]>,
): Promise<MediaTypeModel[] | undefined> {
const { selectModalResult, selectModal } = await this.createSelectModal(selectModalOptions); const { selectModalResult, selectModal } = await this.createSelectModal(selectModalOptions);
console.debug(`MDB | selectModal closed with code ${selectModalResult.code}`); console.debug(`MDB | selectModal closed with code ${selectModalResult.code}`);
@ -468,7 +473,7 @@ export class ModalHelper {
return callbackRes; return callbackRes;
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
new Notice(e.toString()); new Notice(`${e}`);
selectModal.close(); selectModal.close();
return; return;
} }
@ -500,12 +505,12 @@ export class ModalHelper {
console.warn(previewModalResult.error); console.warn(previewModalResult.error);
new Notice(previewModalResult.error.toString()); new Notice(previewModalResult.error.toString());
previewModal.close(); previewModal.close();
return undefined; return true;
} }
if (previewModalResult.code === ModalResultCode.CLOSE) { if (previewModalResult.code === ModalResultCode.CLOSE) {
// modal is already being closed // modal is already being closed
return undefined; return true;
} }
try { try {
@ -514,9 +519,9 @@ export class ModalHelper {
return callbackRes; return callbackRes;
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
new Notice(e.toString()); new Notice(`${e}`);
previewModal.close(); previewModal.close();
return; return true;
} }
} }
} }

View file

@ -1,5 +1,5 @@
import { MediaTypeModel } from '../models/MediaTypeModel'; import type { TFile, TFolder, App } from 'obsidian';
import { TFile, TFolder, App } from 'obsidian'; import type { MediaTypeModel } from '../models/MediaTypeModel';
export const pluginName: string = 'obsidian-media-db-plugin'; export const pluginName: string = 'obsidian-media-db-plugin';
export const contactEmail: string = 'm.projects.code@gmail.com'; export const contactEmail: string = 'm.projects.code@gmail.com';
@ -82,7 +82,7 @@ function replaceTag(match: string, mediaTypeModel: MediaTypeModel, ignoreUndefin
return '{{ INVALID TEMPLATE TAG }}'; return '{{ INVALID TEMPLATE TAG }}';
} }
function traverseMetaData(path: Array<string>, mediaTypeModel: MediaTypeModel): any { function traverseMetaData(path: string[], mediaTypeModel: MediaTypeModel): any {
let o: any = mediaTypeModel; let o: any = mediaTypeModel;
for (const part of path) { for (const part of path) {
@ -226,7 +226,11 @@ export function hasTemplaterPlugin(app: App): boolean {
export async function useTemplaterPluginInFile(app: App, file: TFile): Promise<void> { export async function useTemplaterPluginInFile(app: App, file: TFile): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const templater = (app as any).plugins.plugins['templater-obsidian']; const templater = (app as any).plugins.plugins['templater-obsidian'];
if (templater && !templater?.settings['trigger_on_file_creation']) { if (templater && !templater?.settings.trigger_on_file_creation) {
await templater.templater.overwrite_file_commands(file); await templater.templater.overwrite_file_commands(file);
} }
} }
export type ModelToData<T> = {
[K in keyof T as T[K] extends Function ? never : K]?: T[K];
};

View file

@ -2,14 +2,22 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"module": "ESNext", "module": "ESNext",
"target": "ES6", "target": "ESNext",
"allowJs": true, "allowJs": true,
"checkJs": true,
"noImplicitAny": true, "noImplicitAny": true,
"strict": true,
"strictNullChecks": true,
"noImplicitReturns": true,
"moduleResolution": "node", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"isolatedModules": true, "isolatedModules": true,
"skipLibCheck": true,
"verbatimModuleSyntax": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"sourceMap": true,
"lib": ["DOM", "ESNext"], "lib": ["DOM", "ESNext"],
"types": ["svelte"],
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },
"include": ["src/**/*.ts", "tests/**/*.ts"] "include": ["src/**/*.ts", "tests/**/*.ts"]