This commit is contained in:
Moritz Jung 2023-05-29 19:46:28 +02:00
commit ed78ffcd64
33 changed files with 468 additions and 364 deletions

View file

@ -1,10 +1,11 @@
import { MediaTypeModel } from '../models/MediaTypeModel';
import { MediaType } from '../utils/MediaType';
export abstract class APIModel {
apiName: string;
apiUrl: string;
apiDescription: string;
types: string[];
types: MediaType[];
/**
* This function should query the api and return a list of matches. The matches should be caped at 20.
@ -15,12 +16,11 @@ export abstract class APIModel {
abstract getById(id: string): Promise<MediaTypeModel>;
hasType(type: string): boolean {
hasType(type: MediaType): boolean {
return this.types.contains(type);
}
// for future use (https://github.com/mProjectsCode/obsidian-media-db-plugin/issues/5)
hasTypeOverlap(types: string[]): boolean {
hasTypeOverlap(types: MediaType[]): boolean {
for (const type of types) {
if (this.hasType(type)) {
return true;

View file

@ -2,8 +2,8 @@ import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { BoardGameModel } from 'src/models/BoardGameModel';
import { debugLog } from '../../utils/Utils';
import { requestUrl } from 'obsidian';
import { MediaType } from '../../utils/MediaType';
export class BoardGameGeekAPI extends APIModel {
plugin: MediaDbPlugin;
@ -15,7 +15,7 @@ export class BoardGameGeekAPI extends APIModel {
this.apiName = 'BoardGameGeekAPI';
this.apiDescription = 'A free API for BoardGameGeek things.';
this.apiUrl = 'https://api.geekdo.com/xmlapi';
this.types = ['boardgames'];
this.types = [MediaType.BoardGame];
}
async searchByTitle(title: string): Promise<MediaTypeModel[]> {
@ -33,13 +33,13 @@ export class BoardGameGeekAPI extends APIModel {
const data = fetchData.text;
const response = new window.DOMParser().parseFromString(data, 'text/xml');
debugLog(response);
console.debug(response);
let ret: MediaTypeModel[] = [];
const ret: MediaTypeModel[] = [];
for (const boardgame of Array.from(response.querySelectorAll('boardgame'))) {
const id = boardgame.attributes.getNamedItem('objectid')!.value;
const title = boardgame.querySelector('name')!.textContent!;
const title = boardgame.querySelector('name[primary=true]')?.textContent ?? boardgame.querySelector('name')!.textContent!;
const year = boardgame.querySelector('yearpublished')?.textContent ?? '';
ret.push(
@ -70,17 +70,22 @@ export class BoardGameGeekAPI extends APIModel {
const data = fetchData.text;
const response = new window.DOMParser().parseFromString(data, 'text/xml');
debugLog(response);
console.debug(response);
const boardgame = response.querySelector('boardgame')!;
const title = boardgame.querySelector('name')!.textContent!;
const title = boardgame.querySelector('name[primary=true]')!.textContent!;
const year = boardgame.querySelector('yearpublished')?.textContent ?? '';
const image = boardgame.querySelector('image')?.textContent ?? undefined;
const onlineRating = Number.parseFloat(boardgame.querySelector('statistics ratings average')?.textContent ?? '');
const onlineRating = Number.parseFloat(boardgame.querySelector('statistics ratings average')?.textContent ?? '0');
const genres = Array.from(boardgame.querySelectorAll('boardgamecategory')).map(n => n!.textContent!);
const complexityRating = Number.parseFloat(boardgame.querySelector('averageweight')?.textContent ?? '0');
const minPlayers = Number.parseFloat(boardgame.querySelector('minplayers')?.textContent ?? '0');
const maxPlayers = Number.parseFloat(boardgame.querySelector('maxplayers')?.textContent ?? '0');
const playtime = (boardgame.querySelector('playingtime')?.textContent ?? 'unknown') + ' minutes';
const publishers = Array.from(boardgame.querySelectorAll('boardgamepublisher')).map(n => n!.textContent!);
const model = new BoardGameModel({
title,
title: title,
englishTitle: title,
year: year === '0' ? '' : year,
dataSource: this.apiName,
@ -89,7 +94,13 @@ export class BoardGameGeekAPI extends APIModel {
genres: genres,
onlineRating: onlineRating,
complexityRating: complexityRating,
minPlayers: minPlayers,
maxPlayers: maxPlayers,
playtime: playtime,
publishers: publishers,
image: image,
released: true,
userData: {

View file

@ -1,7 +1,6 @@
import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { debugLog } from '../../utils/Utils';
// WIP
export class LocGovAPI extends APIModel {
@ -25,14 +24,14 @@ export class LocGovAPI extends APIModel {
const searchUrl = `https://www.loc.gov/search/?q=${encodeURIComponent(title)}&fo=json&c=20`;
const fetchData = await fetch(searchUrl);
debugLog(fetchData);
console.debug(fetchData);
if (fetchData.status !== 200) {
throw Error(`MDB | Received status code ${fetchData.status} from an API.`);
}
const data = await fetchData.json();
debugLog(data);
console.debug(data);
let ret: MediaTypeModel[] = [];
throw new Error('MDB | Under construction, API implementation not finished');
@ -50,7 +49,7 @@ export class LocGovAPI extends APIModel {
}
const data = await fetchData.json();
debugLog(data);
console.debug(data);
const result = data.data;
const type = this.typeMappings.get(result.type.toLowerCase());

View file

@ -3,7 +3,7 @@ import { MediaTypeModel } from '../../models/MediaTypeModel';
import { MovieModel } from '../../models/MovieModel';
import MediaDbPlugin from '../../main';
import { SeriesModel } from '../../models/SeriesModel';
import { debugLog } from '../../utils/Utils';
import { MediaType } from '../../utils/MediaType';
export class MALAPI extends APIModel {
plugin: MediaDbPlugin;
@ -16,7 +16,7 @@ export class MALAPI extends APIModel {
this.apiName = 'MALAPI';
this.apiDescription = 'A free API for Anime. Some results may take a long time to load.';
this.apiUrl = 'https://jikan.moe/';
this.types = ['movie', 'series', 'anime'];
this.types = [MediaType.Movie, MediaType.Series];
this.typeMappings = new Map<string, string>();
this.typeMappings.set('movie', 'movie');
this.typeMappings.set('special', 'special');
@ -30,15 +30,15 @@ export class MALAPI extends APIModel {
const searchUrl = `https://api.jikan.moe/v4/anime?q=${encodeURIComponent(title)}&limit=20${this.plugin.settings.sfwFilter ? '&sfw' : ''}`;
const fetchData = await fetch(searchUrl);
debugLog(fetchData);
console.debug(fetchData);
if (fetchData.status !== 200) {
throw Error(`MDB | Received status code ${fetchData.status} from an API.`);
}
const data = await fetchData.json();
debugLog(data);
console.debug(data);
let ret: MediaTypeModel[] = [];
const ret: MediaTypeModel[] = [];
for (const result of data.data) {
const type = this.typeMappings.get(result.type?.toLowerCase());
@ -85,7 +85,7 @@ export class MALAPI extends APIModel {
async getById(id: string): Promise<MediaTypeModel> {
console.log(`MDB | api "${this.apiName}" queried by ID`);
const searchUrl = `https://api.jikan.moe/v4/anime/${encodeURIComponent(id)}`;
const searchUrl = `https://api.jikan.moe/v4/anime/${encodeURIComponent(id)}/full`;
const fetchData = await fetch(searchUrl);
if (fetchData.status !== 200) {
@ -93,7 +93,7 @@ export class MALAPI extends APIModel {
}
const data = await fetchData.json();
debugLog(data);
console.debug(data);
const result = data.data;
const type = this.typeMappings.get(result.type?.toLowerCase());
@ -111,10 +111,12 @@ export class MALAPI extends APIModel {
producer: result.studios?.map((x: any) => x.name).join(', ') ?? 'unknown',
duration: result.duration ?? 'unknown',
onlineRating: result.score ?? 0,
actors: [],
image: result.images?.jpg?.image_url ?? '',
released: true,
premiere: new Date(result.aired?.from).toLocaleDateString() ?? 'unknown',
streamingServices: result.streaming?.map((x: any) => x.name) ?? [],
userData: {
watched: false,
@ -140,10 +142,12 @@ export class MALAPI extends APIModel {
producer: result.studios?.map((x: any) => x.name).join(', ') ?? 'unknown',
duration: result.duration ?? 'unknown',
onlineRating: result.score ?? 0,
actors: [],
image: result.images?.jpg?.image_url ?? '',
released: true,
premiere: new Date(result.aired?.from).toLocaleDateString() ?? 'unknown',
streamingServices: result.streaming?.map((x: any) => x.name) ?? [],
userData: {
watched: false,
@ -168,6 +172,7 @@ export class MALAPI extends APIModel {
episodes: result.episodes,
duration: result.duration ?? 'unknown',
onlineRating: result.score ?? 0,
streamingServices: result.streaming?.map((x: any) => x.name) ?? [],
image: result.images?.jpg?.image_url ?? '',
released: true,

View file

@ -3,7 +3,8 @@ import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { requestUrl } from 'obsidian';
import { MusicReleaseModel } from '../../models/MusicReleaseModel';
import { contactEmail, debugLog, mediaDbVersion, pluginName } from '../../utils/Utils';
import { contactEmail, mediaDbVersion, pluginName } from '../../utils/Utils';
import { MediaType } from '../../utils/MediaType';
export class MusicBrainzAPI extends APIModel {
plugin: MediaDbPlugin;
@ -15,7 +16,7 @@ export class MusicBrainzAPI extends APIModel {
this.apiName = 'MusicBrainz API';
this.apiDescription = 'Free API for music albums.';
this.apiUrl = 'https://musicbrainz.org/';
this.types = ['music'];
this.types = [MediaType.MusicRelease];
}
async searchByTitle(title: string): Promise<MediaTypeModel[]> {
@ -30,15 +31,15 @@ export class MusicBrainzAPI extends APIModel {
},
});
debugLog(fetchData);
console.debug(fetchData);
if (fetchData.status !== 200) {
throw Error(`MDB | Received status code ${fetchData.status} from an API.`);
}
const data = await fetchData.json;
debugLog(data);
let ret: MediaTypeModel[] = [];
console.debug(data);
const ret: MediaTypeModel[] = [];
for (const result of data['release-groups']) {
ret.push(
@ -76,7 +77,7 @@ export class MusicBrainzAPI extends APIModel {
}
const data = await fetchData.json;
debugLog(data);
console.debug(data);
const result = data;
const model = new MusicReleaseModel({

View file

@ -4,7 +4,7 @@ import { MovieModel } from '../../models/MovieModel';
import MediaDbPlugin from '../../main';
import { SeriesModel } from '../../models/SeriesModel';
import { GameModel } from '../../models/GameModel';
import { debugLog } from '../../utils/Utils';
import { MediaType } from '../../utils/MediaType';
export class OMDbAPI extends APIModel {
plugin: MediaDbPlugin;
@ -17,7 +17,7 @@ export class OMDbAPI extends APIModel {
this.apiName = 'OMDbAPI';
this.apiDescription = 'A free API for Movies, Series and Games.';
this.apiUrl = 'http://www.omdbapi.com/';
this.types = ['movie', 'series'];
this.types = [MediaType.Movie, MediaType.Series, MediaType.Game];
this.typeMappings = new Map<string, string>();
this.typeMappings.set('movie', 'movie');
this.typeMappings.set('series', 'series');
@ -40,15 +40,19 @@ export class OMDbAPI extends APIModel {
const data = await fetchData.json();
if (data.Response === 'False') {
throw Error(`MDB | Received error from ${this.apiName}: ${data.Error}`);
if (data.Error === 'Movie not found!') {
return [];
}
throw Error(`MDB | Received error from ${this.apiName}: \n${JSON.stringify(data, undefined, 4)}`);
}
if (!data.Search) {
return [];
}
debugLog(data.Search);
console.debug(data.Search);
let ret: MediaTypeModel[] = [];
const ret: MediaTypeModel[] = [];
for (const result of data.Search) {
const type = this.typeMappings.get(result.Type.toLowerCase());
@ -108,7 +112,7 @@ export class OMDbAPI extends APIModel {
}
const result = await fetchData.json();
debugLog(result);
console.debug(result);
if (result.Response === 'False') {
throw Error(`MDB | Received error from ${this.apiName}: ${result.Error}`);
@ -133,9 +137,11 @@ export class OMDbAPI extends APIModel {
producer: result.Director ?? 'unknown',
duration: result.Runtime ?? 'unknown',
onlineRating: Number.parseFloat(result.imdbRating ?? 0),
actors: result.Actors?.split(', ') ?? [],
image: result.Poster ?? '',
released: true,
streamingServices: [],
premiere: new Date(result.Released).toLocaleDateString() ?? 'unknown',
userData: {
@ -161,9 +167,11 @@ export class OMDbAPI extends APIModel {
episodes: 0,
duration: result.Runtime ?? 'unknown',
onlineRating: Number.parseFloat(result.imdbRating ?? 0),
actors: result.Actors?.split(', ') ?? [],
image: result.Poster ?? '',
released: true,
streamingServices: [],
airing: false,
airedFrom: new Date(result.Released).toLocaleDateString() ?? 'unknown',
airedTo: 'unknown',

View file

@ -2,7 +2,6 @@ import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { GameModel } from '../../models/GameModel';
import { debugLog } from '../../utils/Utils';
import { requestUrl } from 'obsidian';
import { MediaType } from '../../utils/MediaType';
@ -17,7 +16,7 @@ export class SteamAPI extends APIModel {
this.apiName = 'SteamAPI';
this.apiDescription = 'A free API for all Steam games.';
this.apiUrl = 'http://www.steampowered.com/';
this.types = ['games'];
this.types = [MediaType.Game];
this.typeMappings = new Map<string, string>();
this.typeMappings.set('game', 'game');
}
@ -36,9 +35,9 @@ export class SteamAPI extends APIModel {
const data = await fetchData.json;
debugLog(data);
console.debug(data);
let filteredData = [];
const filteredData = [];
for (const app of data.applist.apps) {
if (app.name.toLowerCase().includes(title.toLowerCase())) {
@ -49,7 +48,7 @@ export class SteamAPI extends APIModel {
}
}
let ret: MediaTypeModel[] = [];
const ret: MediaTypeModel[] = [];
for (const result of filteredData) {
ret.push(
@ -70,7 +69,7 @@ export class SteamAPI extends APIModel {
async getById(id: string): Promise<MediaTypeModel> {
console.log(`MDB | api "${this.apiName}" queried by ID`);
const searchUrl = `http://store.steampowered.com/api/appdetails?appids=${encodeURIComponent(id)}`;
const searchUrl = `http://store.steampowered.com/api/appdetails?appids=${encodeURIComponent(id)}&l=en`;
const fetchData = await requestUrl({
url: searchUrl,
});
@ -79,7 +78,7 @@ export class SteamAPI extends APIModel {
throw Error(`MDB | Received status code ${fetchData.status} from an API.`);
}
debugLog(await fetchData.json);
console.debug(await fetchData.json);
let result: any;
for (const [key, value] of Object.entries(await fetchData.json)) {
@ -94,7 +93,7 @@ export class SteamAPI extends APIModel {
throw Error(`MDB | API returned invalid data.`);
}
debugLog(result);
console.debug(result);
const model = new GameModel({
type: MediaType.Game,

View file

@ -2,7 +2,7 @@ import { APIModel } from '../APIModel';
import { MediaTypeModel } from '../../models/MediaTypeModel';
import MediaDbPlugin from '../../main';
import { WikiModel } from '../../models/WikiModel';
import { debugLog } from '../../utils/Utils';
import { MediaType } from '../../utils/MediaType';
export class WikipediaAPI extends APIModel {
plugin: MediaDbPlugin;
@ -14,7 +14,7 @@ export class WikipediaAPI extends APIModel {
this.apiName = 'Wikipedia API';
this.apiDescription = 'The API behind Wikipedia';
this.apiUrl = 'https://www.wikipedia.com';
this.types = ['wiki'];
this.types = [MediaType.Wiki];
}
async searchByTitle(title: string): Promise<MediaTypeModel[]> {
@ -22,15 +22,15 @@ export class WikipediaAPI extends APIModel {
const searchUrl = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(title)}&srlimit=20&utf8=&format=json&origin=*`;
const fetchData = await fetch(searchUrl);
debugLog(fetchData);
console.debug(fetchData);
if (fetchData.status !== 200) {
throw Error(`MDB | Received status code ${fetchData.status} from an API.`);
}
const data = await fetchData.json();
debugLog(data);
let ret: MediaTypeModel[] = [];
console.debug(data);
const ret: MediaTypeModel[] = [];
for (const result of data.query.search) {
ret.push(
@ -59,7 +59,7 @@ export class WikipediaAPI extends APIModel {
}
const data = await fetchData.json();
debugLog(data);
console.debug(data);
const result: any = Object.entries(data?.query?.pages)[0][1];
const model = new WikiModel({

View file

@ -25,7 +25,7 @@ export default class MediaDbPlugin extends Plugin {
frontMatterRexExpPattern: string = '^(---)\\n[\\s\\S]*?\\n---';
async onload() {
async onload(): Promise<void> {
this.apiManager = new APIManager();
// register APIs
this.apiManager.registerAPI(new OMDbAPI(this));
@ -45,9 +45,10 @@ export default class MediaDbPlugin extends Plugin {
this.addSettingTab(new MediaDbSettingTab(this.app, this));
this.mediaTypeManager.updateTemplates(this.settings);
this.mediaTypeManager.updateFolders(this.settings);
// add icon to the left ribbon
const ribbonIconEl = this.addRibbonIcon('database', 'Add new Media DB entry', (evt: MouseEvent) => this.createEntryWithAdvancedSearchModal());
const ribbonIconEl = this.addRibbonIcon('database', 'Add new Media DB entry', () => this.createEntryWithAdvancedSearchModal());
ribbonIconEl.addClass('obsidian-media-db-plugin-ribbon-class');
this.registerEvent(
@ -135,8 +136,8 @@ export default class MediaDbPlugin extends Plugin {
* - replace the detail query
* - maybe custom link syntax
*/
async createLinkWithSearchModal() {
let apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => {
async createLinkWithSearchModal(): Promise<void> {
const apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => {
return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis);
});
@ -162,7 +163,7 @@ export default class MediaDbPlugin extends Plugin {
}
}
async createEntryWithSearchModal(searchModalOptions?: SearchModalOptions) {
async createEntryWithSearchModal(searchModalOptions?: SearchModalOptions): Promise<void> {
let types: string[] = [];
let apiSearchResults: MediaTypeModel[] = await this.modalHelper.openSearchModal(searchModalOptions ?? {}, async searchModalData => {
types = searchModalData.types;
@ -197,8 +198,8 @@ export default class MediaDbPlugin extends Plugin {
await this.createMediaDbNotes(selectResults);
}
async createEntryWithAdvancedSearchModal() {
let apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => {
async createEntryWithAdvancedSearchModal(): Promise<void> {
const apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal({}, async advancedSearchModalData => {
return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis);
});
@ -253,7 +254,7 @@ export default class MediaDbPlugin extends Plugin {
}
async queryDetails(models: MediaTypeModel[]): Promise<MediaTypeModel[]> {
let detailModels: MediaTypeModel[] = [];
const detailModels: MediaTypeModel[] = [];
for (const model of models) {
try {
detailModels.push(await this.apiManager.queryDetailedInfo(model));
@ -269,7 +270,11 @@ export default class MediaDbPlugin extends Plugin {
try {
console.debug('MDB | creating new note');
let fileContent = await this.generateMediaDbNoteContents(mediaTypeModel, options);
const fileContent = await this.generateMediaDbNoteContents(mediaTypeModel, options);
if (!options.folder) {
options.folder = await this.mediaTypeManager.getFolder(mediaTypeModel, this.app);
}
await this.createNote(this.mediaTypeManager.getFileName(mediaTypeModel), fileContent, options);
} catch (e) {
@ -278,7 +283,7 @@ export default class MediaDbPlugin extends Plugin {
}
}
async generateMediaDbNoteContents(mediaTypeModel: MediaTypeModel, options: CreateNoteOptions) {
async generateMediaDbNoteContents(mediaTypeModel: MediaTypeModel, options: CreateNoteOptions): Promise<string> {
let fileMetadata = this.modelPropertyMapper.convertObject(mediaTypeModel.toMetaDataObject());
let fileContent = '';
const template = options.attachTemplate ? await this.mediaTypeManager.getTemplate(mediaTypeModel, this.app) : '';
@ -295,7 +300,7 @@ export default class MediaDbPlugin extends Plugin {
return { fileMetadata: fileMetadata, fileContent: fileContent };
}
let attachFileMetadata: any = this.getMetadataFromFileCache(fileToAttach);
const attachFileMetadata: any = this.getMetadataFromFileCache(fileToAttach);
fileMetadata = Object.assign(attachFileMetadata, fileMetadata);
let attachFileContent: string = await this.app.vault.read(fileToAttach);
@ -312,7 +317,7 @@ export default class MediaDbPlugin extends Plugin {
return { fileMetadata: fileMetadata, fileContent: fileContent };
}
let templateMetadata: any = this.getMetaDataFromFileContent(template);
const templateMetadata: any = this.getMetaDataFromFileContent(template);
fileMetadata = Object.assign(templateMetadata, fileMetadata);
const regExp = new RegExp(this.frontMatterRexExpPattern);
@ -348,7 +353,7 @@ export default class MediaDbPlugin extends Plugin {
return metadata;
}
getMetadataFromFileCache(file: TFile) {
getMetadataFromFileCache(file: TFile): any {
let metadata: any = this.app.metadataCache.getFileCache(file).frontmatter;
if (metadata) {
metadata = Object.assign({}, metadata); // copy
@ -366,12 +371,9 @@ export default class MediaDbPlugin extends Plugin {
* @param fileContent
* @param options
*/
async createNote(fileName: string, fileContent: string, options: CreateNoteOptions) {
async createNote(fileName: string, fileContent: string, options: CreateNoteOptions): Promise<void> {
// find and possibly create the folder set in settings or passed in folder
const folder = options.folder ?? this.app.vault.getAbstractFileByPath(this.settings.folder);
if (!folder) {
await this.app.vault.createFolder(this.settings.folder);
}
const folder = options.folder ?? this.app.vault.getAbstractFileByPath('/');
fileName = replaceIllegalFileNameCharactersInString(fileName);
const filePath = `${folder.path}/${fileName}.md`;
@ -401,7 +403,7 @@ export default class MediaDbPlugin extends Plugin {
* Update the active note by querying the API again.
* 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) {
async updateActiveNote(onlyMetadata: boolean = false): Promise<void> {
const activeFile: TFile = this.app.workspace.getActiveFile();
if (!activeFile) {
throw new Error('MDB | there is no active note');
@ -416,7 +418,7 @@ export default class MediaDbPlugin extends Plugin {
throw new Error('MDB | active note is not a Media DB entry or is missing metadata');
}
let oldMediaTypeModel = this.mediaTypeManager.createMediaTypeModelFromMediaType(metadata, metadata.type);
const oldMediaTypeModel = this.mediaTypeManager.createMediaTypeModelFromMediaType(metadata, metadata.type);
// console.debug(oldMediaTypeModel);
let newMediaTypeModel = await this.apiManager.queryDetailedInfoById(metadata.id, metadata.dataSource);
@ -436,11 +438,11 @@ export default class MediaDbPlugin extends Plugin {
}
}
async createEntriesFromFolder(folder: TFolder) {
async createEntriesFromFolder(folder: TFolder): Promise<void> {
const erroredFiles: { filePath: string; error: string }[] = [];
let canceled: boolean = false;
const { selectedAPI, titleFieldName, appendContent } = await new Promise<{ selectedAPI: string; titleFieldName: string; appendContent: boolean }>((resolve, reject) => {
const { selectedAPI, titleFieldName, appendContent } = await new Promise<{ selectedAPI: string; titleFieldName: string; appendContent: boolean }>(resolve => {
new MediaDbFolderImportModal(this.app, this, (selectedAPI: string, titleFieldName: string, appendContent: boolean) => {
resolve({ selectedAPI, titleFieldName, appendContent });
}).open();
@ -454,11 +456,11 @@ export default class MediaDbPlugin extends Plugin {
continue;
}
let metadata: any = this.getMetadataFromFileCache(file);
const metadata: any = this.getMetadataFromFileCache(file);
let title = metadata[titleFieldName];
const title = metadata[titleFieldName];
if (!title) {
erroredFiles.push({ filePath: file.path, error: `metadata field \'${titleFieldName}\' not found or empty` });
erroredFiles.push({ filePath: file.path, error: `metadata field '${titleFieldName}' not found or empty` });
continue;
}
@ -474,7 +476,7 @@ export default class MediaDbPlugin extends Plugin {
continue;
}
let { selectModalResult, selectModal } = await this.modalHelper.createSelectModal({ elements: results, skipButton: true, modalTitle: `Results for \'${title}\'` });
const { selectModalResult, selectModal } = await this.modalHelper.createSelectModal({ elements: results, skipButton: true, modalTitle: `Results for '${title}'` });
if (selectModalResult.code === ModalResultCode.ERROR) {
erroredFiles.push({ filePath: file.path, error: selectModalResult.error.message });
@ -513,35 +515,34 @@ export default class MediaDbPlugin extends Plugin {
}
async createErroredFilesReport(erroredFiles: { filePath: string; error: string }[]): Promise<void> {
const title = `bulk import error report ${dateTimeToString(new Date())}`;
const filePath = `${this.settings.folder.replace(/\/$/, '')}/${title}.md`;
const title = `MDB - bulk import error report ${dateTimeToString(new Date())}`;
const filePath = `${title}.md`;
const table = [['file', 'error']].concat(erroredFiles.map(x => [x.filePath, x.error]));
let fileContent = `# ${title}\n\n${markdownTable(table)}`;
const targetFile = await this.app.vault.create(filePath, fileContent);
const fileContent = `# ${title}\n\n${markdownTable(table)}`;
await this.app.vault.create(filePath, fileContent);
}
async loadSettings() {
async loadSettings(): Promise<void> {
// console.log(DEFAULT_SETTINGS);
const diskSettings: MediaDbPluginSettings = await this.loadData();
const defaultSettings: MediaDbPluginSettings = getDefaultSettings(this);
const loadedSettings: MediaDbPluginSettings = Object.assign({}, defaultSettings, diskSettings);
// migrate the settings loaded from the disk to match the structure of the default settings
let newPropertyMappings: PropertyMappingModel[] = [];
const newPropertyMappings: PropertyMappingModel[] = [];
for (const defaultPropertyMappingModel of defaultSettings.propertyMappingModels) {
let newPropertyMappingModel: PropertyMappingModel = loadedSettings.propertyMappingModels.find(x => x.type === defaultPropertyMappingModel.type);
const newPropertyMappingModel: PropertyMappingModel = loadedSettings.propertyMappingModels.find(x => x.type === defaultPropertyMappingModel.type);
if (newPropertyMappingModel === undefined) {
// if the propertyMappingModel exists in the default settings but not the loaded settings, add it
newPropertyMappings.push(defaultPropertyMappingModel);
} else {
// if the propertyMappingModel also exists in the loaded settings, add it from there
let newProperties: PropertyMapping[] = [];
const newProperties: PropertyMapping[] = [];
for (const defaultProperty of defaultPropertyMappingModel.properties) {
let newProperty = newPropertyMappingModel.properties.find(x => x.property === defaultProperty.property);
const newProperty = newPropertyMappingModel.properties.find(x => x.property === defaultProperty.property);
if (newProperty === undefined) {
// default property is an instance
newProperties.push(defaultProperty);
@ -559,8 +560,9 @@ export default class MediaDbPlugin extends Plugin {
this.settings = loadedSettings;
}
async saveSettings() {
async saveSettings(): Promise<void> {
this.mediaTypeManager.updateTemplates(this.settings);
this.mediaTypeManager.updateFolders(this.settings);
await this.saveData(this.settings);
}

View file

@ -38,7 +38,7 @@ export class MediaDbAdvancedSearchModal extends Modal {
this.closeCallback = closeCallback;
}
keyPressCallback(event: KeyboardEvent) {
keyPressCallback(event: KeyboardEvent): void {
if (event.key === 'Enter') {
this.search();
}
@ -66,7 +66,7 @@ export class MediaDbAdvancedSearchModal extends Modal {
}
}
onOpen() {
onOpen(): void {
const { contentEl } = this;
contentEl.createEl('h2', { text: this.title });
@ -123,7 +123,7 @@ export class MediaDbAdvancedSearchModal extends Modal {
});
}
onClose() {
onClose(): void {
this.closeCallback();
const { contentEl } = this;
contentEl.empty();

View file

@ -16,12 +16,12 @@ export class MediaDbFolderImportModal extends Modal {
this.selectedApi = plugin.apiManager.apis[0].apiName;
}
submit() {
submit(): void {
this.onSubmit(this.selectedApi, this.titleFieldName, this.appendContent);
this.close();
}
onOpen() {
onOpen(): void {
const { contentEl } = this;
contentEl.createEl('h2', { text: 'Import folder as Media DB entries' });
@ -90,7 +90,7 @@ export class MediaDbFolderImportModal extends Modal {
});
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -33,7 +33,7 @@ export class MediaDbIdSearchModal extends Modal {
this.closeCallback = closeCallback;
}
keyPressCallback(event: KeyboardEvent) {
keyPressCallback(event: KeyboardEvent): void {
if (event.key === 'Enter') {
this.search();
}
@ -59,7 +59,7 @@ export class MediaDbIdSearchModal extends Modal {
}
}
onOpen() {
onOpen(): void {
const { contentEl } = this;
contentEl.createEl('h2', { text: this.title });
@ -108,7 +108,7 @@ export class MediaDbIdSearchModal extends Modal {
});
}
onClose() {
onClose(): void {
this.closeCallback();
const { contentEl } = this;
contentEl.empty();

View file

@ -40,14 +40,14 @@ export class MediaDbPreviewModal extends Modal {
}
async preview(): Promise<void> {
let { contentEl } = this;
const { contentEl } = this;
contentEl.addClass('media-db-plugin-preview-modal');
contentEl.createEl('h2', { text: this.title });
const previewWrapper = contentEl.createDiv({ cls: 'media-db-plugin-preview-wrapper' });
for (let result of this.elements) {
for (const result of this.elements) {
previewWrapper.createEl('h3', { text: result.englishTitle });
const fileDiv = previewWrapper.createDiv();

View file

@ -1,16 +1,10 @@
import { ButtonComponent, Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
import { MediaTypeModel } from '../models/MediaTypeModel';
import MediaDbPlugin from '../main';
import {
ADVANCED_SEARCH_MODAL_DEFAULT_OPTIONS,
AdvancedSearchModalData,
AdvancedSearchModalOptions,
SEARCH_MODAL_DEFAULT_OPTIONS,
SearchModalData,
SearchModalOptions,
} from '../utils/ModalHelper';
import { SEARCH_MODAL_DEFAULT_OPTIONS, SearchModalData, SearchModalOptions } from '../utils/ModalHelper';
import { MEDIA_TYPES } from '../utils/MediaTypeManager';
import { unCamelCase } from '../utils/Utils';
import { MediaType } from '../utils/MediaType';
export class MediaDbSearchModal extends Modal {
plugin: MediaDbPlugin;
@ -18,7 +12,7 @@ export class MediaDbSearchModal extends Modal {
query: string;
isBusy: boolean;
title: string;
selectedTypes: { name: string; selected: boolean }[];
selectedTypes: { name: MediaType; selected: boolean }[];
searchBtn: ButtonComponent;
@ -47,7 +41,7 @@ export class MediaDbSearchModal extends Modal {
this.closeCallback = closeCallback;
}
keyPressCallback(event: KeyboardEvent) {
keyPressCallback(event: KeyboardEvent): void {
if (event.key === 'Enter') {
this.search();
}
@ -59,7 +53,7 @@ export class MediaDbSearchModal extends Modal {
return;
}
const types: string[] = this.selectedTypes.filter(x => x.selected).map(x => x.name);
const types: MediaType[] = this.selectedTypes.filter(x => x.selected).map(x => x.name);
if (types.length === 0) {
new Notice('MDB | No Type selected');
@ -75,7 +69,7 @@ export class MediaDbSearchModal extends Modal {
}
}
onOpen() {
onOpen(): void {
const { contentEl } = this;
contentEl.createEl('h2', { text: this.title });
@ -130,7 +124,7 @@ export class MediaDbSearchModal extends Modal {
});
}
onClose() {
onClose(): void {
this.closeCallback();
const { contentEl } = this;
contentEl.empty();

View file

@ -40,14 +40,14 @@ export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
}
// Renders each suggestion item.
renderElement(item: MediaTypeModel, el: HTMLElement) {
renderElement(item: MediaTypeModel, el: HTMLElement): void {
el.createEl('div', { text: this.plugin.mediaTypeManager.getFileName(item) });
el.createEl('small', { text: `${item.getSummary()}\n` });
el.createEl('small', { text: `${item.type.toUpperCase() + (item.subType ? ` (${item.subType})` : '')} from ${item.dataSource}` });
}
// Perform action on the selected suggestion.
submit() {
submit(): void {
if (!this.busy) {
this.busy = true;
this.submitButton.setButtonText('Creating entry...');
@ -55,12 +55,12 @@ export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
}
}
skip() {
skip(): void {
this.skipButton.setButtonText('Skipping...');
this.skipCallback();
}
onClose() {
onClose(): void {
this.closeCallback();
}
}

View file

@ -59,7 +59,7 @@ export abstract class SelectModal<T> extends Modal {
abstract skip(): void;
disableAllOtherElements(elementId: number) {
disableAllOtherElements(elementId: number): void {
for (const selectModalElement of this.selectModalElements) {
if (selectModalElement.id !== elementId) {
selectModalElement.setActive(false);
@ -67,7 +67,7 @@ export abstract class SelectModal<T> extends Modal {
}
}
deHighlightAllOtherElements(elementId: number) {
deHighlightAllOtherElements(elementId: number): void {
for (const selectModalElement of this.selectModalElements) {
if (selectModalElement.id !== elementId) {
selectModalElement.setHighlighted(false);
@ -75,7 +75,7 @@ export abstract class SelectModal<T> extends Modal {
}
}
async onOpen() {
async onOpen(): Promise<void> {
const { contentEl, titleEl } = this;
titleEl.createEl('h2', { text: this.title });
@ -122,7 +122,7 @@ export abstract class SelectModal<T> extends Modal {
});
}
activateHighlighted() {
activateHighlighted(): void {
for (const selectModalElement of this.selectModalElements) {
if (selectModalElement.isHighlighted()) {
selectModalElement.setActive(!selectModalElement.isActive());
@ -133,7 +133,7 @@ export abstract class SelectModal<T> extends Modal {
}
}
highlightUp() {
highlightUp(): void {
for (const selectModalElement of this.selectModalElements) {
if (selectModalElement.isHighlighted()) {
this.getPreviousSelectModalElement(selectModalElement).setHighlighted(true);
@ -145,7 +145,7 @@ export abstract class SelectModal<T> extends Modal {
this.selectModalElements.last().setHighlighted(true);
}
highlightDown() {
highlightDown(): void {
for (const selectModalElement of this.selectModalElements) {
if (selectModalElement.isHighlighted()) {
this.getNextSelectModalElement(selectModalElement).setHighlighted(true);

View file

@ -45,7 +45,7 @@ export class SelectModalElement<T> {
return this.highlighted;
}
setHighlighted(value: boolean) {
setHighlighted(value: boolean): void {
this.highlighted = value;
if (this.highlighted) {
this.addClass(this.hoverClass);

View file

@ -5,6 +5,11 @@ import { MediaType } from '../utils/MediaType';
export class BoardGameModel extends MediaTypeModel {
genres: string[];
onlineRating: number;
complexityRating: number;
minPlayers: number;
maxPlayers: number;
playtime: string;
publishers: string[];
image?: string;
released: boolean;
@ -19,8 +24,15 @@ export class BoardGameModel extends MediaTypeModel {
this.genres = undefined;
this.onlineRating = undefined;
this.minPlayers = undefined;
this.maxPlayers = undefined;
this.playtime = undefined;
this.publishers = undefined;
this.complexityRating = undefined;
this.image = undefined;
this.released = undefined;
this.userData = {
played: undefined,
personalRating: undefined,

View file

@ -7,9 +7,11 @@ export class MovieModel extends MediaTypeModel {
producer: string;
duration: string;
onlineRating: number;
actors: string[];
image: string;
released: boolean;
streamingServices: string[];
premiere: string;
userData: {
@ -25,9 +27,13 @@ export class MovieModel extends MediaTypeModel {
this.producer = undefined;
this.duration = undefined;
this.onlineRating = undefined;
this.actors = undefined;
this.image = undefined;
this.released = undefined;
this.streamingServices = undefined;
this.premiere = undefined;
this.userData = {
watched: undefined,
lastWatched: undefined,

View file

@ -17,9 +17,11 @@ export class SeriesModel extends MediaTypeModel {
episodes: number;
duration: string;
onlineRating: number;
actors: string[];
image: string;
released: boolean;
streamingServices: string[];
airing: boolean;
airedFrom: string;
airedTo: string;
@ -38,11 +40,15 @@ export class SeriesModel extends MediaTypeModel {
this.episodes = undefined;
this.duration = undefined;
this.onlineRating = undefined;
this.actors = undefined;
this.image = undefined;
this.released = undefined;
this.streamingServices = undefined;
this.airing = undefined;
this.airedFrom = undefined;
this.airedTo = undefined;
this.userData = {
watched: undefined,
lastWatched: undefined,

View file

@ -40,6 +40,7 @@ export class PropertyMapper {
// @ts-ignore
newObj[propertyMapping.newProperty] = value;
} else if (propertyMapping.mapping === PropertyMappingOption.Remove) {
// do nothing
} else if (propertyMapping.mapping === PropertyMappingOption.Default) {
// @ts-ignore
newObj[key] = value;

View file

@ -9,7 +9,6 @@ import { MEDIA_TYPES } from '../utils/MediaTypeManager';
import { MediaTypeModel } from '../models/MediaTypeModel';
export interface MediaDbPluginSettings {
folder: string;
OMDbKey: string;
sfwFilter: boolean;
useCustomYamlStringifier: boolean;
@ -36,11 +35,17 @@ export interface MediaDbPluginSettings {
musicReleasePropertyConversionRules: string;
boardgamePropertyConversionRules: string;
movieFolder: string;
seriesFolder: string;
gameFolder: string;
wikiFolder: string;
musicReleaseFolder: string;
boardgameFolder: string;
propertyMappingModels: PropertyMappingModel[];
}
const DEFAULT_SETTINGS: MediaDbPluginSettings = {
folder: 'Media DB',
OMDbKey: '',
sfwFilter: true,
useCustomYamlStringifier: true,
@ -67,13 +72,20 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = {
musicReleasePropertyConversionRules: '',
boardgamePropertyConversionRules: '',
movieFolder: 'Media DB/movies',
seriesFolder: 'Media DB/series',
gameFolder: 'Media DB/games',
wikiFolder: 'Media DB/wiki',
musicReleaseFolder: 'Media DB/music',
boardgameFolder: 'Media DB/boardgames',
propertyMappingModels: [],
};
export const lockedPropertyMappings: string[] = ['type', 'id', 'dataSource'];
export function getDefaultSettings(plugin: MediaDbPlugin): MediaDbPluginSettings {
let defaultSettings = DEFAULT_SETTINGS;
const defaultSettings = DEFAULT_SETTINGS;
// construct property mapping defaults
const propertyMappingModels: PropertyMappingModel[] = [];
@ -111,19 +123,6 @@ export class MediaDbSettingTab extends PluginSettingTab {
containerEl.createEl('h2', { text: 'Media DB Plugin Settings' });
new Setting(containerEl)
.setName('New file location')
.setDesc('New media db entries will be placed here.')
.addSearch(cb => {
new FolderSuggest(this.app, cb.inputEl);
cb.setPlaceholder('Example: folder1/folder2')
.setValue(this.plugin.settings.folder)
.onChange(data => {
this.plugin.settings.folder = data;
this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('OMDb API key')
.setDesc('API key for "www.omdbapi.com".')
@ -166,6 +165,87 @@ export class MediaDbSettingTab extends PluginSettingTab {
});
});
containerEl.createEl('h3', { text: 'New File Location' });
// region new file location
new Setting(containerEl)
.setName('Movie Folder')
.setDesc('Where newly imported movies should be places.')
.addSearch(cb => {
new FolderSuggest(this.app, cb.inputEl);
cb.setPlaceholder(DEFAULT_SETTINGS.movieFolder)
.setValue(this.plugin.settings.movieFolder)
.onChange(data => {
this.plugin.settings.movieFolder = data;
this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('Series Folder')
.setDesc('Where newly imported series should be places.')
.addSearch(cb => {
new FolderSuggest(this.app, cb.inputEl);
cb.setPlaceholder(DEFAULT_SETTINGS.seriesFolder)
.setValue(this.plugin.settings.seriesFolder)
.onChange(data => {
this.plugin.settings.seriesFolder = data;
this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('Game Folder')
.setDesc('Where newly imported games should be places.')
.addSearch(cb => {
new FolderSuggest(this.app, cb.inputEl);
cb.setPlaceholder(DEFAULT_SETTINGS.gameFolder)
.setValue(this.plugin.settings.gameFolder)
.onChange(data => {
this.plugin.settings.gameFolder = data;
this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('Wiki Folder')
.setDesc('Where newly imported wiki articles should be places.')
.addSearch(cb => {
new FolderSuggest(this.app, cb.inputEl);
cb.setPlaceholder(DEFAULT_SETTINGS.wikiFolder)
.setValue(this.plugin.settings.wikiFolder)
.onChange(data => {
this.plugin.settings.wikiFolder = data;
this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('Music Folder')
.setDesc('Where newly imported music should be places.')
.addSearch(cb => {
new FolderSuggest(this.app, cb.inputEl);
cb.setPlaceholder(DEFAULT_SETTINGS.musicReleaseFolder)
.setValue(this.plugin.settings.musicReleaseFolder)
.onChange(data => {
this.plugin.settings.musicReleaseFolder = data;
this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('Board Game Folder')
.setDesc('Where newly imported board games should be places.')
.addSearch(cb => {
new FileSuggest(this.app, cb.inputEl);
cb.setPlaceholder(DEFAULT_SETTINGS.boardgameFolder)
.setValue(this.plugin.settings.boardgameFolder)
.onChange(data => {
this.plugin.settings.boardgameFolder = data;
this.plugin.saveSettings();
});
});
// endregion
containerEl.createEl('h3', { text: 'Template Settings' });
// region templates
new Setting(containerEl)
@ -326,7 +406,7 @@ export class MediaDbSettingTab extends PluginSettingTab {
containerEl.createEl('h3', { text: 'Property Mappings' });
let propertyMappingExplanation = containerEl.createEl('div');
const propertyMappingExplanation = containerEl.createEl('div');
propertyMappingExplanation.innerHTML = `
<p>Allow you to remap the metadata fields of newly created media db entries.</p>
<p>
@ -345,8 +425,8 @@ export class MediaDbSettingTab extends PluginSettingTab {
target: this.containerEl,
props: {
models: this.plugin.settings.propertyMappingModels.map(x => x.copy()),
save: (model: PropertyMappingModel) => {
let propertyMappingModels: PropertyMappingModel[] = [];
save: (model: PropertyMappingModel): void => {
const propertyMappingModels: PropertyMappingModel[] = [];
for (const model2 of this.plugin.settings.propertyMappingModels) {
if (model2.type === model.type) {

View file

@ -53,7 +53,7 @@ export class Suggest<T> {
this.setSelectedItem(item, false);
}
setSuggestions(values: T[]) {
setSuggestions(values: T[]): void {
this.containerEl.empty();
const suggestionEls: HTMLDivElement[] = [];
@ -68,14 +68,14 @@ export class Suggest<T> {
this.setSelectedItem(0, false);
}
useSelectedItem(event: MouseEvent | KeyboardEvent) {
useSelectedItem(event: MouseEvent | KeyboardEvent): void {
const currentValue = this.values[this.selectedItem];
if (currentValue) {
this.owner.selectSuggestion(currentValue, event);
}
}
setSelectedItem(selectedIndex: number, scrollIntoView: boolean) {
setSelectedItem(selectedIndex: number, scrollIntoView: boolean): void {
const normalizedIndex = this.suggestions.length > 0 ? wrapAround(selectedIndex, this.suggestions.length) : 0;
const prevSelectedSuggestion = this.suggestions[this.selectedItem];
const selectedSuggestion = this.suggestions[normalizedIndex];
@ -141,7 +141,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
{
name: 'sameWidth',
enabled: true,
fn: ({ state, instance }) => {
fn: ({ state, instance }): void => {
// Note: positioning needs to be calculated twice -
// first pass - positioning it according to the width of the popper
// second pass - position it with the width bound to the reference element

View file

@ -2,7 +2,7 @@ import { MediaDbPluginSettings } from '../settings/Settings';
import { MediaType } from './MediaType';
import { MediaTypeModel } from '../models/MediaTypeModel';
import { replaceTags } from './Utils';
import { App, TFile } from 'obsidian';
import { App, TAbstractFile, TFile, TFolder } from 'obsidian';
import { MovieModel } from '../models/MovieModel';
import { SeriesModel } from '../models/SeriesModel';
import { GameModel } from '../models/GameModel';
@ -15,10 +15,11 @@ export const MEDIA_TYPES: MediaType[] = [MediaType.Movie, MediaType.Series, Medi
export class MediaTypeManager {
mediaFileNameTemplateMap: Map<MediaType, string>;
mediaTemplateMap: Map<MediaType, string>;
mediaFolderMap: Map<MediaType, string>;
constructor() {}
updateTemplates(settings: MediaDbPluginSettings) {
updateTemplates(settings: MediaDbPluginSettings): void {
this.mediaFileNameTemplateMap = new Map<MediaType, string>();
this.mediaFileNameTemplateMap.set(MediaType.Movie, settings.movieFileNameTemplate);
this.mediaFileNameTemplateMap.set(MediaType.Series, settings.seriesFileNameTemplate);
@ -36,11 +37,21 @@ export class MediaTypeManager {
this.mediaTemplateMap.set(MediaType.BoardGame, settings.boardgameTemplate);
}
updateFolders(settings: MediaDbPluginSettings): void {
this.mediaFolderMap = new Map<MediaType, string>();
this.mediaFolderMap.set(MediaType.Movie, settings.movieFolder);
this.mediaFolderMap.set(MediaType.Series, settings.seriesFolder);
this.mediaFolderMap.set(MediaType.Game, settings.gameFolder);
this.mediaFolderMap.set(MediaType.Wiki, settings.wikiFolder);
this.mediaFolderMap.set(MediaType.MusicRelease, settings.musicReleaseFolder);
this.mediaFolderMap.set(MediaType.BoardGame, settings.boardgameFolder);
}
getFileName(mediaTypeModel: MediaTypeModel): string {
return replaceTags(this.mediaFileNameTemplateMap.get(mediaTypeModel.getMediaType()), mediaTypeModel);
}
async getTemplate(mediaTypeModel: MediaTypeModel, app: App) {
async getTemplate(mediaTypeModel: MediaTypeModel, app: App): Promise<string> {
const templateFileName = this.mediaTemplateMap.get(mediaTypeModel.getMediaType());
if (!templateFileName) {
@ -61,6 +72,26 @@ export class MediaTypeManager {
return replaceTags(template, mediaTypeModel);
}
async getFolder(mediaTypeModel: MediaTypeModel, app: App): Promise<TFolder> {
let folderPath = this.mediaFolderMap.get(mediaTypeModel.getMediaType());
if (!folderPath) {
folderPath = `/`;
}
console.log(folderPath);
if (!(await app.vault.adapter.exists(folderPath))) {
await app.vault.createFolder(folderPath);
}
const folder: TAbstractFile = app.vault.getAbstractFileByPath(folderPath);
if (!(folder instanceof TFolder)) {
throw Error(`Expected ${folder} to be instance of TFolder`);
}
return folder;
}
/**
* Takes an object and a MediaType and turns the object into an instance of a MediaTypeModel corresponding to the MediaType passed in.
*

View file

@ -7,6 +7,7 @@ import MediaDbPlugin from '../main';
import { MediaDbPreviewModal } from 'src/modals/MediaDbPreviewModal';
import { CreateNoteOptions } from './Utils';
import { MediaDbSearchModal } from '../modals/MediaDbSearchModal';
import { MediaType } from './MediaType';
export enum ModalResultCode {
SUCCESS,
@ -77,7 +78,7 @@ export interface PreviewModalResult {
*/
export interface SearchModalData {
query: string;
types: string[];
types: MediaType[];
}
/**
@ -124,7 +125,7 @@ export interface PreviewModalData {
*/
export interface SearchModalOptions {
modalTitle?: string;
preselectedTypes?: string[];
preselectedTypes?: MediaType[];
prefilledSearchString?: string;
}
@ -228,7 +229,7 @@ export class ModalHelper {
*/
async createSearchModal(searchModalOptions: SearchModalOptions): Promise<{ searchModalResult: SearchModalResult; searchModal: MediaDbSearchModal }> {
const modal = new MediaDbSearchModal(this.plugin, searchModalOptions);
const res: SearchModalResult = await new Promise((resolve, reject) => {
const res: SearchModalResult = await new Promise(resolve => {
modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res }));
modal.setCloseCallback(err => {
if (err) {
@ -268,8 +269,7 @@ export class ModalHelper {
}
try {
let callbackRes: MediaTypeModel[];
callbackRes = await submitCallback(searchModalResult.data);
const callbackRes: MediaTypeModel[] = await submitCallback(searchModalResult.data);
searchModal.close();
return callbackRes;
} catch (e) {
@ -292,7 +292,7 @@ export class ModalHelper {
advancedSearchModalOptions: AdvancedSearchModalOptions
): Promise<{ advancedSearchModalResult: AdvancedSearchModalResult; advancedSearchModal: MediaDbAdvancedSearchModal }> {
const modal = new MediaDbAdvancedSearchModal(this.plugin, advancedSearchModalOptions);
const res: AdvancedSearchModalResult = await new Promise((resolve, reject) => {
const res: AdvancedSearchModalResult = await new Promise(resolve => {
modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res }));
modal.setCloseCallback(err => {
if (err) {
@ -335,8 +335,7 @@ export class ModalHelper {
}
try {
let callbackRes: MediaTypeModel[];
callbackRes = await submitCallback(advancedSearchModalResult.data);
const callbackRes: MediaTypeModel[] = await submitCallback(advancedSearchModalResult.data);
advancedSearchModal.close();
return callbackRes;
} catch (e) {
@ -357,7 +356,7 @@ export class ModalHelper {
*/
async createIdSearchModal(idSearchModalOptions: IdSearchModalOptions): Promise<{ idSearchModalResult: IdSearchModalResult; idSearchModal: MediaDbIdSearchModal }> {
const modal = new MediaDbIdSearchModal(this.plugin, idSearchModalOptions);
const res: IdSearchModalResult = await new Promise((resolve, reject) => {
const res: IdSearchModalResult = await new Promise(resolve => {
modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res }));
modal.setCloseCallback(err => {
if (err) {
@ -400,8 +399,7 @@ export class ModalHelper {
}
try {
let callbackRes: MediaTypeModel;
callbackRes = await submitCallback(idSearchModalResult.data);
const callbackRes: MediaTypeModel = await submitCallback(idSearchModalResult.data);
idSearchModal.close();
return callbackRes;
} catch (e) {
@ -422,7 +420,7 @@ export class ModalHelper {
*/
async createSelectModal(selectModalOptions: SelectModalOptions): Promise<{ selectModalResult: SelectModalResult; selectModal: MediaDbSearchResultModal }> {
const modal = new MediaDbSearchResultModal(this.plugin, selectModalOptions);
const res: SelectModalResult = await new Promise((resolve, reject) => {
const res: SelectModalResult = await new Promise(resolve => {
modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res }));
modal.setSkipCallback(() => resolve({ code: ModalResultCode.SKIP }));
modal.setCloseCallback(err => {
@ -468,8 +466,7 @@ export class ModalHelper {
}
try {
let callbackRes: MediaTypeModel[];
callbackRes = await submitCallback(selectModalResult.data);
const callbackRes: MediaTypeModel[] = await submitCallback(selectModalResult.data);
selectModal.close();
return callbackRes;
} catch (e) {
@ -483,7 +480,7 @@ export class ModalHelper {
async createPreviewModal(previewModalOptions: PreviewModalOptions): Promise<{ previewModalResult: PreviewModalResult; previewModal: MediaDbPreviewModal }> {
//todo: handle attachFile for existing files
const modal = new MediaDbPreviewModal(this.plugin, previewModalOptions);
const res: PreviewModalResult = await new Promise((resolve, reject) => {
const res: PreviewModalResult = await new Promise(resolve => {
modal.setSubmitCallback(res => resolve({ code: ModalResultCode.SUCCESS, data: res }));
modal.setCloseCallback(err => {
if (err) {
@ -515,8 +512,7 @@ export class ModalHelper {
}
try {
let callbackRes: boolean;
callbackRes = await submitCallback(previewModalResult.data);
const callbackRes: boolean = await submitCallback(previewModalResult.data);
previewModal.close();
return callbackRes;
} catch (e) {

View file

@ -4,7 +4,7 @@ import { TFile, TFolder } from 'obsidian';
export const pluginName: string = 'obsidian-media-db-plugin';
export const contactEmail: string = 'm.projects.code@gmail.com';
export const mediaDbTag: string = 'mediaDB';
export const mediaDbVersion: string = '0.4.1';
export const mediaDbVersion: string = '0.5.0';
export const debug: boolean = true;
export function wrapAround(value: number, size: number): number {
@ -14,16 +14,6 @@ export function wrapAround(value: number, size: number): number {
return mod(value, size);
}
/**
* @deprecated use console.debug instead
* @param o
*/
export function debugLog(o: any): void {
if (debug) {
console.log(o);
}
}
export function containsOnlyLettersAndUnderscores(str: string): boolean {
return /^[a-zA-Z_]+$/.test(str);
}
@ -33,9 +23,7 @@ export function replaceIllegalFileNameCharactersInString(string: string): string
}
export function replaceTags(template: string, mediaTypeModel: MediaTypeModel): string {
const resolvedTemplate = template.replace(new RegExp('{{.*?}}', 'g'), (match: string) => replaceTag(match, mediaTypeModel));
return resolvedTemplate;
return template.replace(new RegExp('{{.*?}}', 'g'), (match: string) => replaceTag(match, mediaTypeModel));
}
function replaceTag(match: string, mediaTypeModel: MediaTypeModel): string {
@ -44,11 +32,11 @@ function replaceTag(match: string, mediaTypeModel: MediaTypeModel): string {
tag = tag.substring(0, tag.length - 2);
tag = tag.trim();
let parts = tag.split(':');
const parts = tag.split(':');
if (parts.length === 1) {
let path = parts[0].split('.');
const path = parts[0].split('.');
let obj = traverseMetaData(path, mediaTypeModel);
const obj = traverseMetaData(path, mediaTypeModel);
if (obj === undefined) {
return '{{ INVALID TEMPLATE TAG - object undefined }}';
@ -56,11 +44,11 @@ function replaceTag(match: string, mediaTypeModel: MediaTypeModel): string {
return obj;
} else if (parts.length === 2) {
let operator = parts[0];
const operator = parts[0];
let path = parts[1].split('.');
const path = parts[1].split('.');
let obj = traverseMetaData(path, mediaTypeModel);
const obj = traverseMetaData(path, mediaTypeModel);
if (obj === undefined) {
return '{{ INVALID TEMPLATE TAG - object undefined }}';
@ -87,7 +75,7 @@ function replaceTag(match: string, mediaTypeModel: MediaTypeModel): string {
function traverseMetaData(path: Array<string>, mediaTypeModel: MediaTypeModel): any {
let o: any = mediaTypeModel;
for (let part of path) {
for (const part of path) {
if (o !== undefined) {
o = o[part];
}
@ -97,12 +85,12 @@ function traverseMetaData(path: Array<string>, mediaTypeModel: MediaTypeModel):
}
export function markdownTable(content: string[][]): string {
let rows = content.length;
const rows = content.length;
if (rows === 0) {
return '';
}
let columns = content[0].length;
const columns = content[0].length;
if (columns === 0) {
return '';
}
@ -112,7 +100,7 @@ export function markdownTable(content: string[][]): string {
}
}
let longestStringInColumns: number[] = [];
const longestStringInColumns: number[] = [];
for (let i = 0; i < columns; i++) {
let longestStringInColumn = 0;
@ -147,36 +135,18 @@ export function markdownTable(content: string[][]): string {
return table;
}
export function dateToString(date: Date) {
export function dateToString(date: Date): string {
return `${date.getMonth() + 1}-${date.getDate()}-${date.getFullYear()}`;
}
export function timeToString(time: Date) {
export function timeToString(time: Date): string {
return `${time.getHours()}-${time.getMinutes()}-${time.getSeconds()}`;
}
export function dateTimeToString(dateTime: Date) {
export function dateTimeToString(dateTime: Date): string {
return `${dateToString(dateTime)} ${timeToString(dateTime)}`;
}
/**
* @deprecated
*/
export class UserCancelError extends Error {
constructor(message: string) {
super(message);
}
}
/**
* @deprecated
*/
export class UserSkipError extends Error {
constructor(message: string) {
super(message);
}
}
// js can't even implement modulo correctly...
export function mod(n: number, m: number): number {
return ((n % m) + m) % m;
@ -202,6 +172,7 @@ export class PropertyMappingNameConflictError extends Error {
* - attachTemplate: whether to attach the template (DEFAULT: false)
* - attachFie: a file to attach (DEFAULT: undefined)
* - openNote: whether to open the note after creation (DEFAULT: false)
* - folder: folder to put the note in
*/
export interface CreateNoteOptions {
attachTemplate?: boolean;