update settings; fix some minor issues

This commit is contained in:
Moritz Jung 2026-01-28 21:41:06 +01:00
parent abbf10cf3d
commit 97cf350a32
24 changed files with 882 additions and 950 deletions

3
.gitignore vendored
View file

@ -36,4 +36,5 @@ exampleVault/.obsidian/plugins/obsidian-media-db-plugin/*
!exampleVault/.obsidian/plugins/obsidian-media-db-plugin/.hotreload !exampleVault/.obsidian/plugins/obsidian-media-db-plugin/.hotreload
exampleVault/Media DB/* exampleVault/Media DB/*
meta.txt meta.txt
main.js

BIN
bun.lockb

Binary file not shown.

View file

@ -26,26 +26,26 @@
"@happy-dom/global-registrator": "^18.0.1", "@happy-dom/global-registrator": "^18.0.1",
"@lemons_dev/parsinom": "^0.0.12", "@lemons_dev/parsinom": "^0.0.12",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@types/bun": "^1.2.19", "@types/bun": "^1.3.7",
"builtin-modules": "^5.0.0", "builtin-modules": "^5.0.0",
"esbuild": "^0.25.8", "esbuild": "^0.25.12",
"esbuild-plugin-copy-watch": "^2.3.1", "esbuild-plugin-copy-watch": "^2.3.1",
"esbuild-svelte": "^0.9.3", "esbuild-svelte": "^0.9.4",
"eslint": "^9.32.0", "eslint": "^9.39.2",
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.32.0",
"eslint-plugin-only-warn": "^1.1.0", "eslint-plugin-only-warn": "^1.1.0",
"iso-639-2": "^3.0.2", "iso-639-2": "^3.0.2",
"obsidian": "latest", "obsidian": "latest",
"openapi-fetch": "^0.14.0", "openapi-fetch": "^0.14.1",
"openapi-typescript": "^7.8.0", "openapi-typescript": "^7.10.1",
"prettier": "^3.6.2", "prettier": "^3.8.1",
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "^3.4.1",
"string-argv": "^0.3.2", "string-argv": "^0.3.2",
"svelte": "^5.38.0", "svelte": "^5.48.5",
"svelte-check": "^4.3.1", "svelte-check": "^4.3.5",
"svelte-preprocess": "^6.0.3", "svelte-preprocess": "^6.0.3",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.9.2", "typescript": "^5.9.3",
"typescript-eslint": "^8.39.0" "typescript-eslint": "^8.54.0"
} }
} }

View file

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
import createClient from 'openapi-fetch'; import createClient from 'openapi-fetch';
import type MediaDbPlugin from '../../main'; import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel'; import type { MediaTypeModel } from '../../models/MediaTypeModel';

View file

@ -1,9 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
import createClient from 'openapi-fetch'; import createClient from 'openapi-fetch';
import type MediaDbPlugin from '../../main'; import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel'; import type { MediaTypeModel } from '../../models/MediaTypeModel';
import { SeasonModel } from '../../models/SeasonModel';
import { MediaType } from '../../utils/MediaType'; import { MediaType } from '../../utils/MediaType';
import { APIModel } from '../APIModel'; import { APIModel } from '../APIModel';
import { SeasonModel } from '../../models/SeasonModel';
import type { paths } from '../schemas/TMDB'; import type { paths } from '../schemas/TMDB';
export class TMDBSeasonAPI extends APIModel { export class TMDBSeasonAPI extends APIModel {
@ -82,7 +84,9 @@ export class TMDBSeasonAPI extends APIModel {
totalSeasons = detailsData.seasons.length; totalSeasons = detailsData.seasons.length;
} }
} }
} catch {} } catch {
// Ignore errors and assume 0 seasons
}
ret.push( ret.push(
new SeasonModel({ new SeasonModel({

View file

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */
import createClient from 'openapi-fetch'; import createClient from 'openapi-fetch';
import type MediaDbPlugin from '../../main'; import type MediaDbPlugin from '../../main';
import type { MediaTypeModel } from '../../models/MediaTypeModel'; import type { MediaTypeModel } from '../../models/MediaTypeModel';

View file

@ -257,4 +257,8 @@ export class VNDBAPI extends APIModel {
}, },
}); });
} }
getDisabledMediaTypes(): MediaType[] {
return this.plugin.settings.VNDBAPI_disabledMediaTypes;
}
} }

View file

@ -13,15 +13,14 @@ import { MusicBrainzAPI } from './api/apis/MusicBrainzAPI';
import { OMDbAPI } from './api/apis/OMDbAPI'; import { OMDbAPI } from './api/apis/OMDbAPI';
import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI'; import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI';
import { SteamAPI } from './api/apis/SteamAPI'; import { SteamAPI } from './api/apis/SteamAPI';
import { TMDBSeriesAPI } from './api/apis/TMDBSeriesAPI';
import { TMDBSeasonAPI } from './api/apis/TMDBSeasonAPI';
import { TMDBMovieAPI } from './api/apis/TMDBMovieAPI'; import { TMDBMovieAPI } from './api/apis/TMDBMovieAPI';
import { WikipediaAPI } from './api/apis/WikipediaAPI'; import { TMDBSeasonAPI } from './api/apis/TMDBSeasonAPI';
import { ComicVineAPI } from './api/apis/ComicVineAPI'; import { TMDBSeriesAPI } from './api/apis/TMDBSeriesAPI';
import { VNDBAPI } from './api/apis/VNDBAPI'; import { VNDBAPI } from './api/apis/VNDBAPI';
import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal'; import { WikipediaAPI } from './api/apis/WikipediaAPI';
import { ConfirmOverwriteModal } from './modals/ConfirmOverwriteModal'; import { ConfirmOverwriteModal } from './modals/ConfirmOverwriteModal';
import { MediaDbSeasonSelectModal } from './modals/MediaDbSeasonSelectModal'; import type {SeasonSelectModalElement} from './modals/MediaDbSeasonSelectModal';
import { MediaDbSeasonSelectModal } from './modals/MediaDbSeasonSelectModal';
import type { MediaTypeModel } from './models/MediaTypeModel'; import type { MediaTypeModel } from './models/MediaTypeModel';
import { PropertyMapper } from './settings/PropertyMapper'; import { PropertyMapper } from './settings/PropertyMapper';
import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping'; import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping';
@ -271,7 +270,7 @@ export default class MediaDbPlugin extends Plugin {
private async handleSeasonSelectModal(types: string[], selectResults: MediaTypeModel[]): Promise<boolean> { private async handleSeasonSelectModal(types: string[], selectResults: MediaTypeModel[]): Promise<boolean> {
if (types.length === 1 && types[0] === 'season' && selectResults.length === 1 && selectResults[0].dataSource === 'TMDBSeasonAPI') { if (types.length === 1 && types[0] === 'season' && selectResults.length === 1 && selectResults[0].dataSource === 'TMDBSeasonAPI') {
// Use static import for the modal // Use static import for the modal
const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as import('./api/apis/TMDBSeasonAPI').TMDBSeasonAPI; const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as TMDBSeasonAPI;
if (!tmdbSeasonAPI) { if (!tmdbSeasonAPI) {
new Notice('TMDBSeasonAPI not found.'); new Notice('TMDBSeasonAPI not found.');
return true; return true;
@ -296,8 +295,8 @@ export default class MediaDbPlugin extends Plugin {
true, true,
seriesName, seriesName,
); );
const selectedSeasons: any[] = await new Promise(resolve => { const selectedSeasons: SeasonSelectModalElement[] = await new Promise(resolve => {
modal.setSubmitCallback(resolve); modal.setSubmitCb(resolve);
modal.open(); modal.open();
}); });
if (!selectedSeasons || selectedSeasons.length === 0) { if (!selectedSeasons || selectedSeasons.length === 0) {
@ -309,7 +308,7 @@ export default class MediaDbPlugin extends Plugin {
const orig = allSeasons.find(s => s.seasonNumber === season.season_number); const orig = allSeasons.find(s => s.seasonNumber === season.season_number);
if (orig) { if (orig) {
// Fetch full metadata using getById // Fetch full metadata using getById
const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as import('./api/apis/TMDBSeasonAPI').TMDBSeasonAPI; const tmdbSeasonAPI = this.apiManager.getApiByName('TMDBSeasonAPI') as TMDBSeasonAPI;
if (tmdbSeasonAPI) { if (tmdbSeasonAPI) {
const fullMeta = await tmdbSeasonAPI.getById(orig.id); const fullMeta = await tmdbSeasonAPI.getById(orig.id);
await this.createMediaDbNotes([fullMeta]); await this.createMediaDbNotes([fullMeta]);

View file

@ -28,11 +28,11 @@ export class MediaDbAdvancedSearchModal extends Modal {
this.isBusy = false; this.isBusy = false;
} }
setSubmitCallback(submitCallback: (res: AdvancedSearchModalData) => void): void { setSubmitCb(submitCallback: (res: AdvancedSearchModalData) => void): void {
this.submitCallback = submitCallback; this.submitCallback = submitCallback;
} }
setCloseCallback(closeCallback: (err?: Error) => void): void { setCloseCb(closeCallback: (err?: Error) => void): void {
this.closeCallback = closeCallback; this.closeCallback = closeCallback;
} }

View file

@ -28,11 +28,11 @@ export class MediaDbIdSearchModal extends Modal {
this.isBusy = false; this.isBusy = false;
} }
setSubmitCallback(submitCallback: (res: IdSearchModalData, err?: Error) => void): void { setSubmitCb(submitCallback: (res: IdSearchModalData, err?: Error) => void): void {
this.submitCallback = submitCallback; this.submitCallback = submitCallback;
} }
setCloseCallback(closeCallback: (err?: Error) => void): void { setCloseCb(closeCallback: (err?: Error) => void): void {
this.closeCallback = closeCallback; this.closeCallback = closeCallback;
} }

View file

@ -26,11 +26,11 @@ export class MediaDbPreviewModal extends Modal {
this.markdownComponent = new Component(); this.markdownComponent = new Component();
} }
setSubmitCallback(submitCallback: (previewModalData: PreviewModalData) => void): void { setSubmitCb(submitCallback: (previewModalData: PreviewModalData) => void): void {
this.submitCallback = submitCallback; this.submitCallback = submitCallback;
} }
setCloseCallback(closeCallback: (err?: Error) => void): void { setCloseCb(closeCallback: (err?: Error) => void): void {
this.closeCallback = closeCallback; this.closeCallback = closeCallback;
} }

View file

@ -31,11 +31,11 @@ export class MediaDbSearchModal extends Modal {
this.isBusy = false; this.isBusy = false;
} }
setSubmitCallback(submitCallback: (res: SearchModalData) => void): void { setSubmitCb(submitCallback: (res: SearchModalData) => void): void {
this.submitCallback = submitCallback; this.submitCallback = submitCallback;
} }
setCloseCallback(closeCallback: (err?: Error) => void): void { setCloseCb(closeCallback: (err?: Error) => void): void {
this.closeCallback = closeCallback; this.closeCallback = closeCallback;
} }

View file

@ -27,11 +27,11 @@ export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
this.sendCallback = false; this.sendCallback = false;
} }
setSubmitCallback(submitCallback: (res: SelectModalData) => void): void { setSubmitCb(submitCallback: (res: SelectModalData) => void): void {
this.submitCallback = submitCallback; this.submitCallback = submitCallback;
} }
setCloseCallback(closeCallback: (err?: Error) => void): void { setCloseCb(closeCallback: (err?: Error) => void): void {
this.closeCallback = closeCallback; this.closeCallback = closeCallback;
} }

View file

@ -40,11 +40,11 @@ export class MediaDbSeasonSelectModal extends SelectModal<SeasonSelectModalEleme
this.close(); this.close();
} }
setSubmitCallback(cb: (selectedSeasons: SeasonSelectModalElement[]) => void): void { setSubmitCb(cb: (selectedSeasons: SeasonSelectModalElement[]) => void): void {
this.submitCallback = cb; this.submitCallback = cb;
} }
setCloseCallback(cb: (err?: Error) => void): void { setCloseCb(cb: (err?: Error) => void): void {
this.closeCallback = cb; this.closeCallback = cb;
} }
} }

View file

@ -59,7 +59,7 @@ export class SeasonModel extends MediaTypeModel {
migrateObject(this, obj, this); migrateObject(this, obj, this);
if (!obj.hasOwnProperty('userData')) { if (!Object.hasOwn(obj, 'userData')) {
migrateObject(this.userData, obj, this.userData); migrateObject(this.userData, obj, this.userData);
} }

View file

@ -124,14 +124,16 @@ export class PropertyMapping {
if (!this.property || !containsOnlyLettersAndUnderscores(this.property)) { if (!this.property || !containsOnlyLettersAndUnderscores(this.property)) {
return { return {
res: false, res: false,
err: new PropertyMappingValidationError(`Error in property mapping "${this.toString()}": property may not be empty and only contain letters and underscores.`), err: new PropertyMappingValidationError(`Error in property mapping "${this.toString()}": property may not be empty and may only contain letters and underscores.`),
}; };
} }
if (!this.newProperty || !containsOnlyLettersAndUnderscores(this.newProperty)) { if (!this.newProperty || !containsOnlyLettersAndUnderscores(this.newProperty)) {
return { return {
res: false, res: false,
err: new PropertyMappingValidationError(`Error in property mapping "${this.toString()}": new property may not be empty and only contain letters and underscores.`), err: new PropertyMappingValidationError(
`Error in property mapping "${this.toString()}": new property may not be empty and may only contain letters and underscores.`,
),
}; };
} }

View file

@ -2,7 +2,7 @@
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';
interface Props { interface Props {
model: PropertyMappingModel; model: PropertyMappingModel;
save: (model: PropertyMappingModel) => void; save: (model: PropertyMappingModel) => void;
@ -10,70 +10,39 @@
let { model, save }: Props = $props(); let { model, save }: Props = $props();
let unsavedChanges = $state(false);
// svelte-ignore state_referenced_locally
let validationResult: { res: boolean; err?: Error } | undefined = $derived(model.validate()); let validationResult: { res: boolean; err?: Error } | undefined = $derived(model.validate());
function onModelUpdate() {
unsavedChanges = true;
model = model.copy();
}
</script> </script>
<div class="media-db-plugin-property-mappings-model-container"> <div class="media-db-plugin-property-mappings-model-container">
<div class="setting-item-name">{capitalizeFirstLetter(model.type)}</div> <div class="media-db-plugin-property-mappings-model-header">
<div class="setting-item-name">{capitalizeFirstLetter(model.type)}</div>
<table class="media-db-plugin-property-mappings-table"> <div class="media-db-plugin-property-mappings-model-actions">
<thead> {#if unsavedChanges}
<tr> <div class="media-db-plugin-property-mapping-unsaved-changes">Unsaved changes</div>
<th class="col-property">Property</th> {/if}
<th class="col-mapping">Mapping</th>
<th class="col-new-name">New name</th>
<th class="col-wikilink">Wikilink</th>
</tr>
</thead>
<tbody>
{#each model.properties as property}
<tr>
<td class="col-property">
<code>{property.property}</code>
</td>
{#if property.locked} <button
<td class="col-locked" colspan="3"> class="media-db-plugin-property-mappings-save-button {validationResult?.res ? 'mod-cta' : 'mod-muted'}"
<div class="media-db-plugin-property-binding-text">property cannot be remapped</div> onclick={() => {
</td> if (model.validate().res) {
{:else} save(model);
<td class="col-mapping"> unsavedChanges = false;
<select }
class="dropdown" }}
bind:value={property.mapping} >
onchange={() => { Save
model = model.copy(); </button>
}} </div>
> </div>
{#each propertyMappingOptions as remappingOption}
<option value={remappingOption}>
{remappingOption}
</option>
{/each}
</select>
</td>
<td class="col-new-name">
{#if property.mapping === PropertyMappingOption.Map}
<div class="media-db-plugin-property-mapping-to">
<Icon iconName="arrow-right" />
<input class="media-db-plugin-property-mapping-input" type="text" spellcheck="false" bind:value={property.newProperty} />
</div>
{:else}
<span class="media-db-plugin-property-mapping-to-disabled"></span>
{/if}
</td>
<td class="col-wikilink">
<label class="media-db-plugin-property-mapping-wikilink-label" title="Convert value to wikilink ([[value]])">
<input type="checkbox" bind:checked={property.wikilink} />
</label>
</td>
{/if}
</tr>
{/each}
</tbody>
</table>
{#if !validationResult?.res} {#if !validationResult?.res}
<div class="media-db-plugin-property-mapping-validation"> <div class="media-db-plugin-property-mapping-validation">
@ -81,33 +50,169 @@
</div> </div>
{/if} {/if}
<button <div class="media-db-plugin-property-mappings-table-container">
class="media-db-plugin-property-mappings-save-button {validationResult?.res ? 'mod-cta' : 'mod-muted'}" <table class="media-db-plugin-property-mappings-table">
onclick={() => { <thead>
if (model.validate().res) save(model); <tr>
}} <th class="col-property">Property</th>
> <th class="col-mapping">Mapping</th>
Save <th class="col-new-name">New name</th>
</button> <th class="col-wikilink">Wikilink</th>
</tr>
</thead>
<tbody>
{#each model.properties as property}
<tr>
<td class="col-property">
<code>{property.property}</code>
</td>
{#if property.locked}
<td class="col-locked" colspan="3">
<div class="media-db-plugin-property-binding-text">property cannot be remapped</div>
</td>
{:else}
<td class="col-mapping">
<select class="dropdown" bind:value={property.mapping} onchange={() => onModelUpdate()}>
{#each propertyMappingOptions as remappingOption}
<option value={remappingOption}>
{remappingOption}
</option>
{/each}
</select>
</td>
<td class="col-new-name">
{#if property.mapping === PropertyMappingOption.Map}
<div class="media-db-plugin-property-mapping-to">
<Icon iconName="arrow-right" />
<input
class="media-db-plugin-property-mapping-input"
type="text"
spellcheck="false"
bind:value={property.newProperty}
onchange={() => onModelUpdate()}
/>
</div>
{:else}
<span class="media-db-plugin-property-mapping-to-disabled"></span>
{/if}
</td>
<td class="col-wikilink">
<label class="media-db-plugin-property-mapping-wikilink-label" title="Convert value to wikilink ([[value]])">
<input type="checkbox" bind:checked={property.wikilink} onchange={() => onModelUpdate()} />
</label>
</td>
{/if}
</tr>
{/each}
</tbody>
</table>
</div>
</div> </div>
<style> <style>
/* Container */
.media-db-plugin-property-mappings-model-container {
margin-bottom: var(--size-4-8);
}
/* Header and actions */
.media-db-plugin-property-mappings-model-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--size-4-4);
gap: var(--size-4-3);
}
.media-db-plugin-property-mappings-model-header .setting-item-name {
font-weight: var(--font-semibold);
font-size: var(--font-ui-medium);
color: var(--text-normal);
margin: 0;
}
.media-db-plugin-property-mappings-model-actions {
display: flex;
align-items: center;
gap: var(--size-4-3);
}
.media-db-plugin-property-mapping-unsaved-changes {
color: var(--text-warning);
font-size: var(--font-ui-small);
white-space: nowrap;
}
.media-db-plugin-property-mappings-save-button {
white-space: nowrap;
cursor: pointer;
}
.media-db-plugin-property-mappings-save-button.mod-muted {
opacity: 0.5;
cursor: not-allowed;
}
/* Validation error */
.media-db-plugin-property-mapping-validation {
color: var(--text-error);
background: rgba(var(--color-red-rgb), 0.1);
padding: var(--size-4-3) var(--size-4-4);
margin-bottom: var(--size-4-4);
border-left: 3px solid var(--text-error);
font-size: var(--font-ui-small);
line-height: 1.5;
border-radius: var(--radius-s);
}
/* Table container */
.media-db-plugin-property-mappings-table-container {
overflow-x: auto;
}
.media-db-plugin-property-mappings-table { .media-db-plugin-property-mappings-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; border-spacing: 0;
table-layout: fixed; /* prevent overflow from wide cells */ font-size: var(--font-ui-small);
} }
/* remove excessive left padding and keep within container */ /* Table header */
.media-db-plugin-property-mappings-table th, .media-db-plugin-property-mappings-table thead {
.media-db-plugin-property-mappings-table td {
padding: 2px 4px;
border-bottom: 1px solid var(--background-modifier-border); border-bottom: 1px solid var(--background-modifier-border);
}
.media-db-plugin-property-mappings-table th {
padding: var(--size-4-2) var(--size-4-3);
padding-left: 0;
text-align: left;
font-weight: var(--font-semibold);
color: var(--text-muted);
font-size: var(--font-ui-smaller);
text-transform: uppercase;
letter-spacing: 0.02em;
border-bottom: none;
}
/* Table body */
.media-db-plugin-property-mappings-table tbody tr {
transition: background-color 0.1s ease;
}
.media-db-plugin-property-mappings-table td {
padding: var(--size-4-3) var(--size-4-3) var(--size-4-3) 0;
border-bottom: 1px solid var(--background-modifier-border-hover);
vertical-align: middle; vertical-align: middle;
} }
/* column widths */ .media-db-plugin-property-mappings-table tbody tr:last-child td {
border-bottom: none;
}
/* Column widths */
.col-property { .col-property {
width: 25%; width: 25%;
white-space: nowrap; white-space: nowrap;
@ -126,36 +231,66 @@
text-align: center; text-align: center;
} }
/* ensure inner controls don't push table wider than container */ .col-locked {
.media-db-plugin-property-mapping-to { text-align: center;
display: flex; font-style: italic;
align-items: center;
gap: 4px;
min-width: 0;
} }
.media-db-plugin-property-mapping-input, /* Property name styling */
.media-db-plugin-property-mappings-table code {
padding: var(--size-4-1) var(--size-4-2);
margin: 0;
background: var(--code-background);
color: var(--code-normal);
border-radius: var(--radius-s);
font-size: var(--font-ui-smaller);
font-family: var(--font-monospace);
}
/* Locked property text */
.media-db-plugin-property-binding-text {
color: var(--text-muted);
font-size: var(--font-ui-small);
font-style: italic;
}
/* Dropdown select - use Obsidian defaults */
.media-db-plugin-property-mappings-table select.dropdown { .media-db-plugin-property-mappings-table select.dropdown {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
box-sizing: border-box;
} }
.media-db-plugin-property-mapping-wikilink-label { /* Remap input */
display: inline-flex; .media-db-plugin-property-mapping-to {
display: flex;
align-items: center; align-items: center;
gap: 4px; gap: var(--size-4-2);
font-size: 0.95em; min-width: 0;
cursor: pointer; }
.media-db-plugin-property-mapping-input {
flex: 1;
width: 100%;
font-family: var(--font-monospace);
} }
.media-db-plugin-property-mapping-to-disabled { .media-db-plugin-property-mapping-to-disabled {
color: var(--text-muted); color: var(--text-faint);
font-size: var(--font-ui-medium);
} }
/* avoid extra left indentation from <pre> */ /* Wikilink checkbox */
.media-db-plugin-property-mappings-table code { .media-db-plugin-property-mapping-wikilink-label {
padding: 0; display: inline-flex;
margin: 0; align-items: center;
justify-content: center;
cursor: pointer;
padding: var(--size-4-1);
}
.media-db-plugin-property-mapping-wikilink-label input[type='checkbox'] {
cursor: pointer;
width: var(--checkbox-size);
height: var(--checkbox-size);
} }
</style> </style>

View file

@ -17,12 +17,6 @@
<!-- <!--
<pre>{JSON.stringify(models, null, 4)}</pre> <pre>{JSON.stringify(models, null, 4)}</pre>
{ #each ICON_LIST as icon }
<p>
{icon} <Icon iconName="{icon}"/>
</p>
{/each}
--> -->
</div> </div>

File diff suppressed because it is too large Load diff

View file

@ -7,24 +7,25 @@ import { GameModel } from '../models/GameModel';
import type { MediaTypeModel } from '../models/MediaTypeModel'; import type { MediaTypeModel } from '../models/MediaTypeModel';
import { MovieModel } from '../models/MovieModel'; import { MovieModel } from '../models/MovieModel';
import { MusicReleaseModel } from '../models/MusicReleaseModel'; import { MusicReleaseModel } from '../models/MusicReleaseModel';
import { SeriesModel } from '../models/SeriesModel';
import { SeasonModel } from '../models/SeasonModel'; import { SeasonModel } from '../models/SeasonModel';
import { SeriesModel } from '../models/SeriesModel';
import { WikiModel } from '../models/WikiModel'; import { WikiModel } from '../models/WikiModel';
import type { MediaDbPluginSettings } from '../settings/Settings'; import type { MediaDbPluginSettings } from '../settings/Settings';
import { ILLEGAL_FILENAME_CHARACTERS } from './IllegalFilenameCharactersList'; import { ILLEGAL_FILENAME_CHARACTERS } from './IllegalFilenameCharactersList';
import { MediaType } from './MediaType'; import { MediaType } from './MediaType';
import { replaceTags } from './Utils'; import { replaceTags } from './Utils';
// All media types in alphabetical order
export const MEDIA_TYPES: MediaType[] = [ export const MEDIA_TYPES: MediaType[] = [
MediaType.Movie,
MediaType.Series,
MediaType.Season,
MediaType.ComicManga,
MediaType.Game,
MediaType.Wiki,
MediaType.MusicRelease,
MediaType.BoardGame, MediaType.BoardGame,
MediaType.Book, MediaType.Book,
MediaType.ComicManga,
MediaType.Game,
MediaType.Movie,
MediaType.MusicRelease,
MediaType.Series,
MediaType.Season,
MediaType.Wiki,
]; ];
export class MediaTypeManager { export class MediaTypeManager {

View file

@ -236,8 +236,8 @@ export class ModalHelper {
async createSearchModal(searchModalOptions: SearchModalOptions): Promise<{ searchModalResult: SearchModalResult; searchModal: MediaDbSearchModal }> { async createSearchModal(searchModalOptions: SearchModalOptions): Promise<{ searchModalResult: SearchModalResult; searchModal: MediaDbSearchModal }> {
const modal = new MediaDbSearchModal(this.plugin, searchModalOptions); const modal = new MediaDbSearchModal(this.plugin, searchModalOptions);
const res: SearchModalResult = await new Promise(resolve => { const res: SearchModalResult = await new Promise(resolve => {
modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); modal.setSubmitCb(res => resolve({ code: ModalResultCode.SUCCESS, data: res }));
modal.setCloseCallback(err => { modal.setCloseCb(err => {
if (err) { if (err) {
resolve({ code: ModalResultCode.ERROR, error: err }); resolve({ code: ModalResultCode.ERROR, error: err });
} }
@ -302,8 +302,8 @@ export class ModalHelper {
): Promise<{ advancedSearchModalResult: AdvancedSearchModalResult; advancedSearchModal: MediaDbAdvancedSearchModal }> { ): Promise<{ advancedSearchModalResult: AdvancedSearchModalResult; advancedSearchModal: MediaDbAdvancedSearchModal }> {
const modal = new MediaDbAdvancedSearchModal(this.plugin, advancedSearchModalOptions); const modal = new MediaDbAdvancedSearchModal(this.plugin, advancedSearchModalOptions);
const res: AdvancedSearchModalResult = await new Promise(resolve => { const res: AdvancedSearchModalResult = await new Promise(resolve => {
modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); modal.setSubmitCb(res => resolve({ code: ModalResultCode.SUCCESS, data: res }));
modal.setCloseCallback(err => { modal.setCloseCb(err => {
if (err) { if (err) {
resolve({ code: ModalResultCode.ERROR, error: err }); resolve({ code: ModalResultCode.ERROR, error: err });
} }
@ -366,8 +366,8 @@ export class ModalHelper {
async createIdSearchModal(idSearchModalOptions: IdSearchModalOptions): Promise<{ idSearchModalResult: IdSearchModalResult; idSearchModal: MediaDbIdSearchModal }> { async createIdSearchModal(idSearchModalOptions: IdSearchModalOptions): Promise<{ idSearchModalResult: IdSearchModalResult; idSearchModal: MediaDbIdSearchModal }> {
const modal = new MediaDbIdSearchModal(this.plugin, idSearchModalOptions); const modal = new MediaDbIdSearchModal(this.plugin, idSearchModalOptions);
const res: IdSearchModalResult = await new Promise(resolve => { const res: IdSearchModalResult = await new Promise(resolve => {
modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); modal.setSubmitCb(res => resolve({ code: ModalResultCode.SUCCESS, data: res }));
modal.setCloseCallback(err => { modal.setCloseCb(err => {
if (err) { if (err) {
resolve({ code: ModalResultCode.ERROR, error: err }); resolve({ code: ModalResultCode.ERROR, error: err });
} }
@ -430,9 +430,9 @@ export class ModalHelper {
async createSelectModal(selectModalOptions: SelectModalOptions): Promise<{ selectModalResult: SelectModalResult; selectModal: MediaDbSearchResultModal }> { async createSelectModal(selectModalOptions: SelectModalOptions): Promise<{ selectModalResult: SelectModalResult; selectModal: MediaDbSearchResultModal }> {
const modal = new MediaDbSearchResultModal(this.plugin, selectModalOptions); const modal = new MediaDbSearchResultModal(this.plugin, selectModalOptions);
const res: SelectModalResult = await new Promise(resolve => { const res: SelectModalResult = await new Promise(resolve => {
modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); modal.setSubmitCb(res => resolve({ code: ModalResultCode.SUCCESS, data: res }));
modal.setSkipCallback(() => resolve({ code: ModalResultCode.SKIP })); modal.setSkipCallback(() => resolve({ code: ModalResultCode.SKIP }));
modal.setCloseCallback(err => { modal.setCloseCb(err => {
if (err) { if (err) {
resolve({ code: ModalResultCode.ERROR, error: err }); resolve({ code: ModalResultCode.ERROR, error: err });
} }
@ -493,8 +493,8 @@ export class ModalHelper {
//todo: handle attachFile for existing files //todo: handle attachFile for existing files
const modal = new MediaDbPreviewModal(this.plugin, previewModalOptions); const modal = new MediaDbPreviewModal(this.plugin, previewModalOptions);
const res: PreviewModalResult = await new Promise(resolve => { const res: PreviewModalResult = await new Promise(resolve => {
modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res })); modal.setSubmitCb(res => resolve({ code: ModalResultCode.SUCCESS, data: res }));
modal.setCloseCallback(err => { modal.setCloseCb(err => {
if (err) { if (err) {
resolve({ code: ModalResultCode.ERROR, error: err }); resolve({ code: ModalResultCode.ERROR, error: err });
} }

View file

@ -1,16 +0,0 @@
import type { App } from 'obsidian';
import type { SeasonSelectModalElement } from '../modals/MediaDbSeasonSelectModal';
import { MediaDbSeasonSelectModal } from '../modals/MediaDbSeasonSelectModal';
export async function openSeasonSelectModal(app: App, plugin: any, seasons: SeasonSelectModalElement[]): Promise<SeasonSelectModalElement[] | undefined> {
return new Promise(resolve => {
const modal = new MediaDbSeasonSelectModal(plugin, seasons, true);
modal.setSubmitCallback(selected => {
resolve(selected);
});
modal.setCloseCallback(() => {
resolve(undefined);
});
modal.open();
});
}

View file

@ -64,55 +64,6 @@ small.media-db-plugin-list-text {
margin-bottom: 10px; margin-bottom: 10px;
} }
/* region property mappings */
.media-db-plugin-property-mappings-model-container {
border: 1px solid var(--background-modifier-border);
border-radius: 5px;
padding: 10px;
width: 100%;
}
.media-db-plugin-property-mappings-container {
margin: 10px 0;
display: flex;
flex-direction: column;
gap: 5px;
}
.media-db-plugin-property-mapping-element {
display: flex;
flex-direction: row;
gap: 10px;
}
.media-db-plugin-property-mapping-element-property-name-wrapper {
min-width: 160px;
background: var(--background-modifier-form-field);
padding: 2px 5px;
border-radius: 5px;
display: flex;
align-items: center;
}
.media-db-plugin-property-mapping-element-property-name {
margin: 0;
}
.media-db-plugin-property-mappings-save-button {
margin: 0;
}
.media-db-plugin-property-mapping-to {
display: flex;
align-items: center;
}
.media-db-plugin-property-mapping-validation {
color: var(--text-error);
margin-bottom: 5px;
}
.media-db-plugin-button:focus { .media-db-plugin-button:focus {
/*outline: 1px solid white;*/ /*outline: 1px solid white;*/
} }
@ -122,5 +73,3 @@ small.media-db-plugin-list-text {
border: var(--modal-border-width) solid var(--modal-border-color); border: var(--modal-border-width) solid var(--modal-border-color);
padding: var(--size-4-4); padding: var(--size-4-4);
} }
/* endregion */

View file

@ -1,14 +0,0 @@
import { readFileSync, writeFileSync } from 'fs';
const targetVersion = process.env.npm_package_version;
// read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync('manifest.json', 'utf8'));
const { minAppVersion } = manifest;
manifest.version = targetVersion;
writeFileSync('manifest.json', JSON.stringify(manifest, null, '\t'));
// update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync('versions.json', 'utf8'));
versions[targetVersion] = minAppVersion;
writeFileSync('versions.json', JSON.stringify(versions, null, '\t'));