From b5d5cc5ca273ebb7659e6e009ca86f54e34f9d32 Mon Sep 17 00:00:00 2001 From: mProjectsCode Date: Wed, 28 Sep 2022 23:43:59 +0200 Subject: [PATCH 01/21] first prove of concept O_O implementation --- src/api/apis/WikipediaAPI.ts | 1 + src/main.ts | 78 +++++++++++++++++++++++++- src/modals/MediaDbSearchResultModal.ts | 4 +- src/modals/SelectModal.ts | 4 +- 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/api/apis/WikipediaAPI.ts b/src/api/apis/WikipediaAPI.ts index 835e225..cd73890 100644 --- a/src/api/apis/WikipediaAPI.ts +++ b/src/api/apis/WikipediaAPI.ts @@ -66,6 +66,7 @@ export class WikipediaAPI extends APIModel { englishTitle: result.title, year: '', dataSource: this.apiName, + url: result.fullurl, id: result.pageid, wikiUrl: result.fullurl, diff --git a/src/main.ts b/src/main.ts index c6f62a9..f5913fd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import {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 {APIManager} from './api/APIManager'; import {MediaTypeModel} from './models/MediaTypeModel'; @@ -88,6 +88,78 @@ export default class MediaDbPlugin extends Plugin { return true; }, }); + // register link insert command + this.addCommand({ + id: 'add-media-db-link', + name: 'Add a link.', + checkCallback: (checking: boolean) => { + if (!this.app.workspace.getActiveFile()) { + return false; + } + if (!checking) { + this.createLinkWithSearchModal(); + } + return true; + }, + }); + } + + /** + * first very simple approach + * - replace the detail query + * - maybe custom link syntax + */ + async createLinkWithSearchModal() { + let results: MediaTypeModel[] = []; + + const {advancedSearchOptions, advancedSearchModal} = await this.openMediaDbAdvancedSearchModal(); + if (!advancedSearchOptions) { + advancedSearchModal.close(); + return; + } + + let apiSearchResults: MediaTypeModel[] = undefined; + try { + apiSearchResults = await this.apiManager.query(advancedSearchOptions.query, advancedSearchOptions.apis); + } catch (e) { + console.warn(e); + new Notice(e.toString()); + advancedSearchModal.close(); + return; + } + + advancedSearchModal.close(); + + const {selectRes, selectModal} = await this.openMediaDbSelectModal(apiSearchResults, false, false); + if (!selectRes) { + selectModal.close(); + return; + } + + // TODO: let's try to not query details for this + try { + results = await this.queryDetails(selectRes); + } catch (e) { + console.warn(e); + new Notice(e.toString()); + selectModal.close(); + return; + } + + selectModal.close(); + + if (!results || results.length < 1) { + return; + } + + const link = `[${results[0].title}](${results[0].url})` + + const view = this.app.workspace.getActiveViewOfType(MarkdownView); + + // Make sure the user is editing a Markdown file. + if (view) { + view.editor.replaceRange(link, view.editor.getCursor()); + } } async createEntryWithSearchModal() { @@ -473,8 +545,8 @@ export default class MediaDbPlugin extends Plugin { return {idSearchOptions: res, idSearchModal: modal}; } - async openMediaDbSelectModal(resultsToDisplay: MediaTypeModel[], skipButton: boolean = false): Promise<{ selectRes: MediaTypeModel[], selectModal: MediaDbSearchResultModal }> { - const modal = new MediaDbSearchResultModal(this, resultsToDisplay, skipButton); + async openMediaDbSelectModal(resultsToDisplay: MediaTypeModel[], skipButton: boolean = false, allowMultiSelect: boolean = true): Promise<{ selectRes: MediaTypeModel[], selectModal: MediaDbSearchResultModal }> { + const modal = new MediaDbSearchResultModal(this, resultsToDisplay, skipButton, allowMultiSelect); const res: MediaTypeModel[] = await new Promise((resolve, reject) => { modal.setSubmitCallback(res => resolve(res)); modal.setSkipCallback(() => resolve([])); diff --git a/src/modals/MediaDbSearchResultModal.ts b/src/modals/MediaDbSearchResultModal.ts index 53e19bc..4ffe578 100644 --- a/src/modals/MediaDbSearchResultModal.ts +++ b/src/modals/MediaDbSearchResultModal.ts @@ -12,8 +12,8 @@ export class MediaDbSearchResultModal extends SelectModal { sendCallback: boolean; - constructor(plugin: MediaDbPlugin, elements: MediaTypeModel[], skipButton: boolean) { - super(plugin.app, elements); + constructor(plugin: MediaDbPlugin, elements: MediaTypeModel[], skipButton: boolean, allowMultiSelect: boolean = true) { + super(plugin.app, elements, allowMultiSelect); this.plugin = plugin; this.title = 'Search Results'; diff --git a/src/modals/SelectModal.ts b/src/modals/SelectModal.ts index 761f3b8..07f189c 100644 --- a/src/modals/SelectModal.ts +++ b/src/modals/SelectModal.ts @@ -16,9 +16,9 @@ export abstract class SelectModal extends Modal { selectModalElements: SelectModalElement[]; - protected constructor(app: App, elements: T[]) { + protected constructor(app: App, elements: T[], allowMultiSelect: boolean = true) { super(app); - this.allowMultiSelect = true; + this.allowMultiSelect = allowMultiSelect; this.title = ''; this.description = ''; From 25b899daa51ae90092c53b4fdd176a587a3189a5 Mon Sep 17 00:00:00 2001 From: mProjectsCode Date: Wed, 28 Sep 2022 23:51:10 +0200 Subject: [PATCH 02/21] some TODOs for tomorrow --- src/main.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index f5913fd..784811c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -106,8 +106,9 @@ export default class MediaDbPlugin extends Plugin { /** * first very simple approach - * - replace the detail query - * - maybe custom link syntax + * TODO: + * - replace the detail query + * - maybe custom link syntax */ async createLinkWithSearchModal() { let results: MediaTypeModel[] = []; @@ -166,6 +167,9 @@ export default class MediaDbPlugin extends Plugin { } + /** + * TODO: further refactor: extract it into own method, pass the action (api query) as lambda as well as an options object + */ async createEntryWithAdvancedSearchModal() { let results: MediaTypeModel[] = []; From 5aa91085091a845b1d0df9c12e060a206a1ddbef Mon Sep 17 00:00:00 2001 From: mProjectsCode Date: Sat, 1 Oct 2022 21:50:59 +0200 Subject: [PATCH 03/21] Modal helper --- src/api/apis/BoardGameGeekAPI.ts | 9 +- src/main.ts | 169 ++++++------------------------- src/utils/ModalHelper.ts | 135 ++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 144 deletions(-) create mode 100644 src/utils/ModalHelper.ts diff --git a/src/api/apis/BoardGameGeekAPI.ts b/src/api/apis/BoardGameGeekAPI.ts index 4996eb6..57c5a5b 100644 --- a/src/api/apis/BoardGameGeekAPI.ts +++ b/src/api/apis/BoardGameGeekAPI.ts @@ -79,17 +79,16 @@ export class BoardGameGeekAPI extends APIModel { const genres = Array.from(boardgame.querySelectorAll('boardgamecategory')).map(n => n!.textContent!); const model = new BoardGameModel({ - type: MediaType.BoardGame, title, englishTitle: title, year: year === '0' ? '' : year, dataSource: this.apiName, url: `https://boardgamegeek.com/boardgame/${id}`, - id, + id: id, - genres, - onlineRating, - image, + genres: genres, + onlineRating: onlineRating, + image: image, released: true, userData: { diff --git a/src/main.ts b/src/main.ts index 784811c..f3507ba 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,10 +4,8 @@ import {APIManager} from './api/APIManager'; import {MediaTypeModel} from './models/MediaTypeModel'; import {dateTimeToString, markdownTable, replaceIllegalFileNameCharactersInString, UserCancelError, UserSkipError} from './utils/Utils'; import {OMDbAPI} from './api/apis/OMDbAPI'; -import {MediaDbAdvancedSearchModal} from './modals/MediaDbAdvancedSearchModal'; import {MediaDbSearchResultModal} from './modals/MediaDbSearchResultModal'; import {MALAPI} from './api/apis/MALAPI'; -import {MediaDbIdSearchModal} from './modals/MediaDbIdSearchModal'; import {WikipediaAPI} from './api/apis/WikipediaAPI'; import {MusicBrainzAPI} from './api/apis/MusicBrainzAPI'; import {MediaTypeManager} from './utils/MediaTypeManager'; @@ -17,12 +15,14 @@ import {PropertyMapper} from './settings/PropertyMapper'; import {YAMLConverter} from './utils/YAMLConverter'; import {MediaDbFolderImportModal} from './modals/MediaDbFolderImportModal'; import {PropertyMapping, PropertyMappingModel} from './settings/PropertyMapping'; +import {ModalHelper} from './utils/ModalHelper'; export default class MediaDbPlugin extends Plugin { settings: MediaDbPluginSettings; apiManager: APIManager; mediaTypeManager: MediaTypeManager; modelPropertyMapper: PropertyMapper; + modalHelper: ModalHelper; frontMatterRexExpPattern: string = '^(---)\\n[\\s\\S]*?\\n---'; @@ -39,6 +39,7 @@ export default class MediaDbPlugin extends Plugin { this.mediaTypeManager = new MediaTypeManager(); this.modelPropertyMapper = new PropertyMapper(this); + this.modalHelper = new ModalHelper(this); await this.loadSettings(); // register the settings tab @@ -91,7 +92,7 @@ export default class MediaDbPlugin extends Plugin { // register link insert command this.addCommand({ id: 'add-media-db-link', - name: 'Add a link.', + name: 'Insert link', checkCallback: (checking: boolean) => { if (!this.app.workspace.getActiveFile()) { return false; @@ -111,49 +112,24 @@ export default class MediaDbPlugin extends Plugin { * - maybe custom link syntax */ async createLinkWithSearchModal() { - let results: MediaTypeModel[] = []; - const {advancedSearchOptions, advancedSearchModal} = await this.openMediaDbAdvancedSearchModal(); - if (!advancedSearchOptions) { - advancedSearchModal.close(); + let apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal(async (advancedSearchOptions) => { + return await this.apiManager.query(advancedSearchOptions.query, advancedSearchOptions.apis); + }); + + if (!apiSearchResults) { return; } - let apiSearchResults: MediaTypeModel[] = undefined; - try { - apiSearchResults = await this.apiManager.query(advancedSearchOptions.query, advancedSearchOptions.apis); - } catch (e) { - console.warn(e); - new Notice(e.toString()); - advancedSearchModal.close(); + const selectResults: MediaTypeModel[] = await this.modalHelper.openSelectModal(apiSearchResults, async (selectedMediaTypeModels) => { + return await this.queryDetails(selectedMediaTypeModels); + }); + + if (!selectResults || selectResults.length < 1) { return; } - advancedSearchModal.close(); - - const {selectRes, selectModal} = await this.openMediaDbSelectModal(apiSearchResults, false, false); - if (!selectRes) { - selectModal.close(); - return; - } - - // TODO: let's try to not query details for this - try { - results = await this.queryDetails(selectRes); - } catch (e) { - console.warn(e); - new Notice(e.toString()); - selectModal.close(); - return; - } - - selectModal.close(); - - if (!results || results.length < 1) { - return; - } - - const link = `[${results[0].title}](${results[0].url})` + const link = `[${selectResults[0].title}](${selectResults[0].url})` const view = this.app.workspace.getActiveViewOfType(MarkdownView); @@ -171,71 +147,35 @@ export default class MediaDbPlugin extends Plugin { * TODO: further refactor: extract it into own method, pass the action (api query) as lambda as well as an options object */ async createEntryWithAdvancedSearchModal() { - let results: MediaTypeModel[] = []; + const apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal(async (advancedSearchOptions) => { + return await this.apiManager.query(advancedSearchOptions.query, advancedSearchOptions.apis); + }); - const {advancedSearchOptions, advancedSearchModal} = await this.openMediaDbAdvancedSearchModal(); - if (!advancedSearchOptions) { - advancedSearchModal.close(); + if (!apiSearchResults) { return; } - let apiSearchResults: MediaTypeModel[] = undefined; - try { - apiSearchResults = await this.apiManager.query(advancedSearchOptions.query, advancedSearchOptions.apis); - } catch (e) { - console.warn(e); - new Notice(e.toString()); - advancedSearchModal.close(); + const selectResults: MediaTypeModel[] = await this.modalHelper.openSelectModal(apiSearchResults, async (selectedMediaTypeModels) => { + return await this.queryDetails(selectedMediaTypeModels); + }); + + if (!selectResults) { return; } - advancedSearchModal.close(); - - const {selectRes, selectModal} = await this.openMediaDbSelectModal(apiSearchResults, false); - if (!selectRes) { - selectModal.close(); - return; - } - - try { - results = await this.queryDetails(selectRes); - } catch (e) { - console.warn(e); - new Notice(e.toString()); - selectModal.close(); - return; - } - - selectModal.close(); - - if (results) { - await this.createMediaDbNotes(results); - } + await this.createMediaDbNotes(selectResults); } async createEntryWithIdSearchModal() { - let result: MediaTypeModel = undefined; + const idSearchResult: MediaTypeModel = await this.modalHelper.openIdSearchModal(async (idSearchOptions) => { + return await this.apiManager.queryDetailedInfoById(idSearchOptions.query, idSearchOptions.api); + }) - const {idSearchOptions, idSearchModal} = await this.openMediaDbIdSearchModal(); - if (!idSearchOptions) { - idSearchModal.close(); + if (!idSearchResult) { return; } - try { - result = await this.apiManager.queryDetailedInfoById(idSearchOptions.query, idSearchOptions.api); - } catch (e) { - console.warn(e); - new Notice(e.toString()); - idSearchModal.close(); - return; - } - - idSearchModal.close(); - - if (result) { - await this.createMediaDbNoteFromModel(result); - } + await this.createMediaDbNoteFromModel(idSearchResult); } async createMediaDbNotes(models: MediaTypeModel[], attachFile?: TFile): Promise { @@ -517,55 +457,6 @@ export default class MediaDbPlugin extends Plugin { const targetFile = await this.app.vault.create(filePath, fileContent); } - async openMediaDbAdvancedSearchModal(): Promise<{ advancedSearchOptions: { query: string, apis: string[] }, advancedSearchModal: MediaDbAdvancedSearchModal }> { - const modal = new MediaDbAdvancedSearchModal(this); - const res: { query: string, apis: string[] } = await new Promise((resolve, reject) => { - modal.setSubmitCallback(res => resolve(res)); - modal.setCloseCallback(err => { - if (err) { - reject(err); - } - resolve(undefined); - }); - - modal.open(); - }); - return {advancedSearchOptions: res, advancedSearchModal: modal}; - } - - async openMediaDbIdSearchModal(): Promise<{ idSearchOptions: { query: string, api: string }, idSearchModal: MediaDbIdSearchModal }> { - const modal = new MediaDbIdSearchModal(this); - const res: { query: string, api: string } = await new Promise((resolve, reject) => { - modal.setSubmitCallback(res => resolve(res)); - modal.setCloseCallback(err => { - if (err) { - reject(err); - } - resolve(undefined); - }); - - modal.open(); - }); - return {idSearchOptions: res, idSearchModal: modal}; - } - - async openMediaDbSelectModal(resultsToDisplay: MediaTypeModel[], skipButton: boolean = false, allowMultiSelect: boolean = true): Promise<{ selectRes: MediaTypeModel[], selectModal: MediaDbSearchResultModal }> { - const modal = new MediaDbSearchResultModal(this, resultsToDisplay, skipButton, allowMultiSelect); - const res: MediaTypeModel[] = await new Promise((resolve, reject) => { - modal.setSubmitCallback(res => resolve(res)); - modal.setSkipCallback(() => resolve([])); - modal.setCloseCallback(err => { - if (err) { - reject(err); - } - resolve(undefined); - }); - - modal.open(); - }); - return {selectRes: res, selectModal: modal}; - } - async loadSettings() { // console.log(DEFAULT_SETTINGS); const diskSettings: MediaDbPluginSettings = await this.loadData(); diff --git a/src/utils/ModalHelper.ts b/src/utils/ModalHelper.ts new file mode 100644 index 0000000..2529679 --- /dev/null +++ b/src/utils/ModalHelper.ts @@ -0,0 +1,135 @@ +import {MediaDbAdvancedSearchModal} from '../modals/MediaDbAdvancedSearchModal'; +import {MediaDbIdSearchModal} from '../modals/MediaDbIdSearchModal'; +import {MediaTypeModel} from '../models/MediaTypeModel'; +import {MediaDbSearchResultModal} from '../modals/MediaDbSearchResultModal'; +import {Notice} from 'obsidian'; +import MediaDbPlugin from '../main'; + +interface AdvancedSearchOptions { + query: string, + apis: string[], +} + +interface IdSearchOptions { + query: string, + api: string, +} + +export class ModalHelper { + plugin: MediaDbPlugin; + + + constructor(plugin: MediaDbPlugin) { + this.plugin = plugin; + } + + async createAdvancedSearchModal(): Promise<{ advancedSearchOptions: AdvancedSearchOptions, advancedSearchModal: MediaDbAdvancedSearchModal }> { + const modal = new MediaDbAdvancedSearchModal(this.plugin); + const res: { query: string, apis: string[] } = await new Promise((resolve, reject) => { + modal.setSubmitCallback(res => resolve(res)); + modal.setCloseCallback(err => { + if (err) { + reject(err); + } + resolve(undefined); + }); + + modal.open(); + }); + return {advancedSearchOptions: res, advancedSearchModal: modal}; + } + + async openAdvancedSearchModal(submitCallback: (advancedSearchOptions: AdvancedSearchOptions) => Promise): Promise { + const {advancedSearchOptions, advancedSearchModal} = await this.createAdvancedSearchModal(); + if (!advancedSearchOptions) { + advancedSearchModal.close(); + return; + } + + try { + let callbackRes: T; + callbackRes = await submitCallback(advancedSearchOptions); + advancedSearchModal.close(); + return callbackRes; + } catch (e) { + console.warn(e); + new Notice(e.toString()); + advancedSearchModal.close(); + return undefined; + } + } + + async createIdSearchModal(): Promise<{ idSearchOptions: { query: string, api: string }, idSearchModal: MediaDbIdSearchModal }> { + const modal = new MediaDbIdSearchModal(this.plugin); + const res: { query: string, api: string } = await new Promise((resolve, reject) => { + modal.setSubmitCallback(res => resolve(res)); + modal.setCloseCallback(err => { + if (err) { + reject(err); + } + resolve(undefined); + }); + + modal.open(); + }); + return {idSearchOptions: res, idSearchModal: modal}; + } + + async openIdSearchModal(submitCallback: (idSearchOptions: IdSearchOptions) => Promise): Promise { + const {idSearchOptions, idSearchModal} = await this.createIdSearchModal(); + if (!idSearchOptions) { + idSearchModal.close(); + return; + } + + try { + let callbackRes: T; + callbackRes = await submitCallback(idSearchOptions); + idSearchModal.close(); + return callbackRes; + } catch (e) { + console.warn(e); + new Notice(e.toString()); + idSearchModal.close(); + return undefined; + } + } + + async createSelectModal(resultsToDisplay: MediaTypeModel[], skipButton: boolean = false, allowMultiSelect: boolean = true): Promise<{ selectRes: MediaTypeModel[], selectModal: MediaDbSearchResultModal }> { + const modal = new MediaDbSearchResultModal(this.plugin, resultsToDisplay, skipButton, allowMultiSelect); + const res: MediaTypeModel[] = await new Promise((resolve, reject) => { + modal.setSubmitCallback(res => resolve(res)); + modal.setSkipCallback(() => resolve([])); + modal.setCloseCallback(err => { + if (err) { + reject(err); + } + resolve(undefined); + }); + + modal.open(); + }); + return {selectRes: res, selectModal: modal}; + } + + async openSelectModal(mediaModels: MediaTypeModel[], submitCallback: (selectedMediaTypeModels: MediaTypeModel[]) => Promise): Promise { + const {selectRes, selectModal} = await this.createSelectModal(mediaModels, false); + if (!selectRes) { + selectModal.close(); + return; + } + + try { + let callbackRes: MediaTypeModel[]; + callbackRes = await submitCallback(selectRes); + selectModal.close(); + return callbackRes; + } catch (e) { + console.warn(e); + new Notice(e.toString()); + selectModal.close(); + return; + } + } +} + From 805d9d3fb83551630a57ba69e4bb3de9c6f2184d Mon Sep 17 00:00:00 2001 From: mProjectsCode Date: Sun, 2 Oct 2022 22:07:11 +0200 Subject: [PATCH 04/21] Modal keyboard navigation changes --- src/modals/MediaDbAdvancedSearchModal.ts | 19 +++++---- src/modals/MediaDbFolderImportModal.ts | 16 +++++++- src/modals/MediaDbIdSearchModal.ts | 19 +++++---- src/modals/MediaDbSearchResultModal.ts | 1 + src/modals/SelectModal.ts | 51 +++++++++++++++++------- styles.css | 4 ++ 6 files changed, 79 insertions(+), 31 deletions(-) diff --git a/src/modals/MediaDbAdvancedSearchModal.ts b/src/modals/MediaDbAdvancedSearchModal.ts index 3711e55..52c9786 100644 --- a/src/modals/MediaDbAdvancedSearchModal.ts +++ b/src/modals/MediaDbAdvancedSearchModal.ts @@ -96,14 +96,19 @@ export class MediaDbAdvancedSearchModal extends Modal { contentEl.createDiv({cls: 'media-db-plugin-spacer'}); new Setting(contentEl) - .addButton(btn => btn.setButtonText('Cancel').onClick(() => this.close())) .addButton(btn => { - return (this.searchBtn = btn - .setButtonText('Ok') - .setCta() - .onClick(() => { - this.search(); - })); + btn.setButtonText('Cancel'); + btn.onClick(() => this.close()); + btn.buttonEl.addClass('media-db-plugin-button'); + }) + .addButton(btn => { + btn.setButtonText('Ok'); + btn.setCta(); + btn.onClick(() => { + this.search(); + }); + btn.buttonEl.addClass('media-db-plugin-button'); + this.searchBtn = btn; }); } diff --git a/src/modals/MediaDbFolderImportModal.ts b/src/modals/MediaDbFolderImportModal.ts index 415e4df..9c1f64a 100644 --- a/src/modals/MediaDbFolderImportModal.ts +++ b/src/modals/MediaDbFolderImportModal.ts @@ -76,8 +76,20 @@ export class MediaDbFolderImportModal extends Modal { contentEl.createDiv({cls: 'media-db-plugin-spacer'}); new Setting(contentEl) - .addButton(btn => btn.setButtonText('Cancel').onClick(() => this.close())) - .addButton(btn => btn.setButtonText('Ok').setCta().onClick(() => this.submit())); + .addButton(btn => { + btn.setButtonText('Cancel'); + btn.onClick(() => this.close()); + btn.buttonEl.addClass('media-db-plugin-button'); + }) + .addButton(btn => { + btn.setButtonText('Ok'); + btn.setCta(); + btn.onClick(() => { + this.submit(); + }); + btn.buttonEl.addClass('media-db-plugin-button'); + this.searchBtn = btn; + }); } onClose() { diff --git a/src/modals/MediaDbIdSearchModal.ts b/src/modals/MediaDbIdSearchModal.ts index 9437e2a..f53197a 100644 --- a/src/modals/MediaDbIdSearchModal.ts +++ b/src/modals/MediaDbIdSearchModal.ts @@ -84,14 +84,19 @@ export class MediaDbIdSearchModal extends Modal { contentEl.createDiv({cls: 'media-db-plugin-spacer'}); new Setting(contentEl) - .addButton(btn => btn.setButtonText('Cancel').onClick(() => this.close())) .addButton(btn => { - return (this.searchBtn = btn - .setButtonText('Ok') - .setCta() - .onClick(() => { - this.search(); - })); + btn.setButtonText('Cancel'); + btn.onClick(() => this.close()); + btn.buttonEl.addClass('media-db-plugin-button'); + }) + .addButton(btn => { + btn.setButtonText('Ok'); + btn.setCta(); + btn.onClick(() => { + this.search(); + }); + btn.buttonEl.addClass('media-db-plugin-button'); + this.searchBtn = btn; }); } diff --git a/src/modals/MediaDbSearchResultModal.ts b/src/modals/MediaDbSearchResultModal.ts index 4ffe578..546c394 100644 --- a/src/modals/MediaDbSearchResultModal.ts +++ b/src/modals/MediaDbSearchResultModal.ts @@ -59,6 +59,7 @@ export class MediaDbSearchResultModal extends SelectModal { } onClose() { + console.log('close'); this.closeCallback(); } } diff --git a/src/modals/SelectModal.ts b/src/modals/SelectModal.ts index 07f189c..5f726ee 100644 --- a/src/modals/SelectModal.ts +++ b/src/modals/SelectModal.ts @@ -12,6 +12,8 @@ export abstract class SelectModal extends Modal { skipButton?: ButtonComponent; submitButton?: ButtonComponent; + elementWrapper?: HTMLDivElement; + elements: T[]; selectModalElements: SelectModalElement[]; @@ -27,18 +29,28 @@ export abstract class SelectModal extends Modal { this.skipButton = undefined; this.submitButton = undefined; + this.elementWrapper = undefined; + this.elements = elements; this.selectModalElements = []; - this.scope.register([], 'ArrowUp', () => { + this.scope.register([], 'ArrowUp', (evt) => { this.highlightUp(); + evt.preventDefault(); }); - this.scope.register([], 'ArrowDown', () => { + this.scope.register([], 'ArrowDown', (evt) => { this.highlightDown(); + evt.preventDefault(); }); this.scope.register([], 'ArrowRight', () => { this.activateHighlighted(); }); + this.scope.register([], ' ', (evt) => { + if (this.elementWrapper && this.elementWrapper === document.activeElement) { + this.activateHighlighted(); + evt.preventDefault(); + } + }); this.scope.register([], 'Enter', () => this.submit()); } @@ -67,24 +79,17 @@ export abstract class SelectModal extends Modal { async onOpen() { const {contentEl} = this; - /* - contentEl.id = 'media-db-plugin-modal' - - contentEl.on('keydown', '#' + contentEl.id, (ev, delegateTarget) => { - console.log(ev.key); - }); - */ - contentEl.createEl('h2', {text: this.title}); contentEl.createEl('p', {text: this.description}); contentEl.addClass('media-db-plugin-select-modal'); - const elementWrapper = contentEl.createDiv({cls: 'media-db-plugin-select-wrapper'}); + this.elementWrapper = contentEl.createDiv({cls: 'media-db-plugin-select-wrapper'}); + this.elementWrapper.tabIndex = 0; let i = 0; for (const element of this.elements) { - const selectModalElement = new SelectModalElement(element, elementWrapper, i, this, false); + const selectModalElement = new SelectModalElement(element, this.elementWrapper, i, this, false); this.selectModalElements.push(selectModalElement); @@ -96,11 +101,27 @@ export abstract class SelectModal extends Modal { this.selectModalElements.first()?.element.scrollIntoView(); const bottomSettingRow = new Setting(contentEl); - bottomSettingRow.addButton(btn => this.cancelButton = btn.setButtonText('Cancel').onClick(() => this.close())); + bottomSettingRow.addButton(btn => { + btn.setButtonText('Cancel'); + btn.onClick(() => this.close()); + btn.buttonEl.addClass('media-db-plugin-button'); + this.cancelButton = btn; + }); if (this.addSkipButton) { - bottomSettingRow.addButton(btn => this.skipButton = btn.setButtonText('Skip').onClick(() => this.skip())); + bottomSettingRow.addButton(btn => { + btn.setButtonText('Skip'); + btn.onClick(() => this.skip()); + btn.buttonEl.addClass('media-db-plugin-button'); + this.skipButton = btn; + }); } - bottomSettingRow.addButton(btn => this.submitButton = btn.setButtonText('Ok').setCta().onClick(() => this.submit())); + bottomSettingRow.addButton(btn => { + btn.setButtonText('Ok'); + btn.setCta(); + btn.onClick(() => this.submit()); + btn.buttonEl.addClass('media-db-plugin-button'); + this.submitButton = btn; + }); } activateHighlighted() { diff --git a/styles.css b/styles.css index afe1e39..3414a7c 100644 --- a/styles.css +++ b/styles.css @@ -105,4 +105,8 @@ small.media-db-plugin-list-text{ margin-bottom: 5px; } +.media-db-plugin-button:focus { + outline: 1px solid white; +} + /* endregion */ From db4a6c98a3385c82dc4597c2b435d446cdce38b0 Mon Sep 17 00:00:00 2001 From: mProjectsCode Date: Tue, 4 Oct 2022 15:08:48 +0200 Subject: [PATCH 05/21] fix for #56 --- src/main.ts | 47 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/main.ts b/src/main.ts index f3507ba..30fefa0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -78,13 +78,26 @@ export default class MediaDbPlugin extends Plugin { // register command to update the open note this.addCommand({ id: 'update-media-db-note', - name: 'Update the open note, if it is a Media DB entry.', + name: 'Update open note (this will recreate the note)', checkCallback: (checking: boolean) => { if (!this.app.workspace.getActiveFile()) { return false; } if (!checking) { - this.updateActiveNote(); + this.updateActiveNote(false); + } + return true; + }, + }); + this.addCommand({ + id: 'update-media-db-note-metadata', + name: 'Update metadata', + checkCallback: (checking: boolean) => { + if (!this.app.workspace.getActiveFile()) { + return false; + } + if (!checking) { + this.updateActiveNote(true); } return true; }, @@ -175,12 +188,12 @@ export default class MediaDbPlugin extends Plugin { return; } - await this.createMediaDbNoteFromModel(idSearchResult); + await this.createMediaDbNoteFromModel(idSearchResult, {attachTemplate: true, openNote: true}); } async createMediaDbNotes(models: MediaTypeModel[], attachFile?: TFile): Promise { for (const model of models) { - await this.createMediaDbNoteFromModel(model, attachFile); + await this.createMediaDbNoteFromModel(model, {attachTemplate: true, attachFile: attachFile}); } } @@ -197,27 +210,27 @@ export default class MediaDbPlugin extends Plugin { return detailModels; } - async createMediaDbNoteFromModel(mediaTypeModel: MediaTypeModel, attachFile?: TFile): Promise { + async createMediaDbNoteFromModel(mediaTypeModel: MediaTypeModel, options: {attachTemplate?: boolean, attachFile?: TFile, openNote?: boolean}): Promise { try { console.debug('MDB | creating new note'); - let fileContent = await this.generateMediaDbNoteContents(mediaTypeModel, attachFile); + let fileContent = await this.generateMediaDbNoteContents(mediaTypeModel, {attachTemplate: options.attachTemplate, attachFile: options.attachFile}); - await this.createNote(this.mediaTypeManager.getFileName(mediaTypeModel), fileContent); + await this.createNote(this.mediaTypeManager.getFileName(mediaTypeModel), fileContent, options.openNote); } catch (e) { console.warn(e); new Notice(e.toString()); } } - private async generateMediaDbNoteContents(mediaTypeModel: MediaTypeModel, attachFile: TFile) { + private async generateMediaDbNoteContents(mediaTypeModel: MediaTypeModel, options: {attachTemplate?: boolean, attachFile?: TFile}) { let fileMetadata = this.modelPropertyMapper.convertObject(mediaTypeModel.toMetaDataObject()); let fileContent = ''; - ({fileMetadata, fileContent} = await this.attachFile(fileMetadata, fileContent, attachFile)); - ({fileMetadata, fileContent} = await this.attachTemplate(fileMetadata, fileContent, await this.mediaTypeManager.getTemplate(mediaTypeModel, this.app))); + ({fileMetadata, fileContent} = await this.attachFile(fileMetadata, fileContent, options.attachFile)); + ({fileMetadata, fileContent} = await this.attachTemplate(fileMetadata, fileContent, options.attachTemplate ? await this.mediaTypeManager.getTemplate(mediaTypeModel, this.app) : '')); - fileContent = `---\n${this.settings.useCustomYamlStringifier ? YAMLConverter.toYaml(fileMetadata) : stringifyYaml(fileMetadata)}---` + fileContent; + fileContent = `---\n${this.settings.useCustomYamlStringifier ? YAMLConverter.toYaml(fileMetadata) : stringifyYaml(fileMetadata)}---\n` + fileContent; return fileContent; } @@ -230,8 +243,9 @@ export default class MediaDbPlugin extends Plugin { fileMetadata = Object.assign(attachFileMetadata, fileMetadata); let attachFileContent: string = await this.app.vault.read(fileToAttach); - const regExp = new RegExp('^(---)\\n[\\s\\S]*\\n---'); + const regExp = new RegExp(this.frontMatterRexExpPattern); attachFileContent = attachFileContent.replace(regExp, ''); + attachFileContent = attachFileContent.startsWith('\n') ? attachFileContent.substring(1) : attachFileContent; fileContent += attachFileContent; return {fileMetadata: fileMetadata, fileContent: fileContent}; @@ -331,7 +345,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() { + async updateActiveNote(onlyMetadata: boolean = false) { const activeFile: TFile = this.app.workspace.getActiveFile(); if (!activeFile) { throw new Error('MDB | there is no active note'); @@ -357,7 +371,12 @@ export default class MediaDbPlugin extends Plugin { // deletion not happening anymore why is this log statement still here console.debug('MDB | deleting old entry'); - await this.createMediaDbNoteFromModel(newMediaTypeModel, activeFile); + if (onlyMetadata) { + await this.createMediaDbNoteFromModel(newMediaTypeModel, {attachFile: activeFile, openNote: true}); + } else { + await this.createMediaDbNoteFromModel(newMediaTypeModel, {attachTemplate: true, openNote: true}); + } + } async createEntriesFromFolder(folder: TFolder) { From 7c926f00afc48d53b572d46d7d26f3d75aea2c13 Mon Sep 17 00:00:00 2001 From: AB1908 <14124383+AB1908@users.noreply.github.com> Date: Sat, 8 Oct 2022 18:45:04 +0530 Subject: [PATCH 06/21] feat: add previewModal logic to advancedModal --- src/main.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main.ts b/src/main.ts index 30fefa0..9348b9e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -176,6 +176,12 @@ export default class MediaDbPlugin extends Plugin { return; } + const proceed: boolean = await this.modalHelper.openPreviewModal(selectResults, async (result) => { + return true; + }) + if (!proceed) + return; + await this.createMediaDbNotes(selectResults); } From 3ceeb3c51cac0a0c3eda87709bee35124ffbea85 Mon Sep 17 00:00:00 2001 From: AB1908 <14124383+AB1908@users.noreply.github.com> Date: Sat, 8 Oct 2022 18:46:46 +0530 Subject: [PATCH 07/21] feat: add previewModal handler to ModalHelper --- src/utils/ModalHelper.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/utils/ModalHelper.ts b/src/utils/ModalHelper.ts index 2529679..a573e12 100644 --- a/src/utils/ModalHelper.ts +++ b/src/utils/ModalHelper.ts @@ -131,5 +131,42 @@ export class ModalHelper { return; } } + + async createPreviewModal(mediaTypeModel: MediaTypeModel[]): Promise<{ result: boolean, previewModal: MediaDbPreviewModal }> { + //todo: handle attachFile for existing files + const modal = new MediaDbPreviewModal(this.plugin, mediaTypeModel, { attachTemplate: true, attachFile: false }); + const booleanResult: boolean = await new Promise((resolve, reject) => { + modal.setSubmitCallback(res => resolve(res)); + modal.setCloseCallback(err => { + if (err) { + reject(err); + } + resolve(undefined); + }); + + modal.open(); + }); + return { result: booleanResult, previewModal: modal }; + } + + async openPreviewModal(mediaModels: MediaTypeModel[], submitCallback: (result: boolean) => Promise): Promise { + const { result, previewModal } = await this.createPreviewModal(mediaModels); + if (!result) { + previewModal.close(); + return; + } + + try { + let callbackRes: boolean; + callbackRes = await submitCallback(result); + previewModal.close(); + return callbackRes; + } catch (e) { + console.warn(e); + new Notice(e.toString()); + previewModal.close(); + return; + } + } } From e597da5609adb0299fd44ebd596c8a5abfcb8066 Mon Sep 17 00:00:00 2001 From: AB1908 <14124383+AB1908@users.noreply.github.com> Date: Sat, 8 Oct 2022 18:47:52 +0530 Subject: [PATCH 08/21] fix: spelling --- src/modals/MediaDbAdvancedSearchModal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modals/MediaDbAdvancedSearchModal.ts b/src/modals/MediaDbAdvancedSearchModal.ts index 52c9786..3db3235 100644 --- a/src/modals/MediaDbAdvancedSearchModal.ts +++ b/src/modals/MediaDbAdvancedSearchModal.ts @@ -36,7 +36,7 @@ export class MediaDbAdvancedSearchModal extends Modal { async search(): Promise { if (!this.query || this.query.length < 3) { - new Notice('MDB | Query to short'); + new Notice('MDB | Query too short'); return; } From 7fe9a7791fddea0da4938559980c7a66cb613a34 Mon Sep 17 00:00:00 2001 From: AB1908 <14124383+AB1908@users.noreply.github.com> Date: Sat, 8 Oct 2022 18:49:07 +0530 Subject: [PATCH 09/21] feat: add PreviewModal --- src/modals/MediaDbPreviewModal.ts | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/modals/MediaDbPreviewModal.ts diff --git a/src/modals/MediaDbPreviewModal.ts b/src/modals/MediaDbPreviewModal.ts new file mode 100644 index 0000000..0bc91b4 --- /dev/null +++ b/src/modals/MediaDbPreviewModal.ts @@ -0,0 +1,71 @@ +import { MarkdownRenderer, Modal, Setting, TFile } from "obsidian"; +import MediaDbPlugin from "src/main"; +import { MediaTypeModel } from "src/models/MediaTypeModel"; + +export class MediaDbPreviewModal extends Modal { + selectedSearchResults: MediaTypeModel[]; + options: { attachTemplate: boolean, attachFile: TFile}; + submitCallback: (res: boolean) => void; + closeCallback: (err?: Error) => void; + skipCallback: () => void; + plugin: MediaDbPlugin; + searchBtn: any; + cancelButton: any; + submitButton: any; + busy: any; + + constructor(plugin: MediaDbPlugin, mediaTypeModel: MediaTypeModel[], options: any) { + super(plugin.app); + this.plugin = plugin; + this.selectedSearchResults = mediaTypeModel; + this.options = options; + } + + setSubmitCallback(submitCallback: (res: boolean) => void): void { + this.submitCallback = submitCallback; + } + + setCloseCallback(closeCallback: (err?: Error) => void): void { + this.closeCallback = closeCallback; + } + + async preview(): Promise { + let { contentEl } = this; + for (let result of this.selectedSearchResults) { + let fileContent = await this.plugin.generateMediaDbNoteContents(result, { attachTemplate: this.options.attachTemplate, attachFile: this.options.attachFile }); + this.contentEl.createEl("h3", {text: result.englishTitle}); + const fileDiv = this.contentEl.createDiv(); + fileContent = `\n${fileContent}\n`; + MarkdownRenderer.renderMarkdown(fileContent, fileDiv, null, null); + } + + contentEl.createDiv({ cls: 'media-db-plugin-spacer' }); + + const bottomSettingRow = new Setting(contentEl); + bottomSettingRow.addButton(btn => { + btn.setButtonText('Cancel'); + btn.onClick(() => this.close()); + btn.buttonEl.addClass('media-db-plugin-button'); + this.cancelButton = btn; + }); + bottomSettingRow.addButton(btn => { + btn.setButtonText('Ok'); + btn.setCta(); + btn.onClick(() => this.submit()); + btn.buttonEl.addClass('media-db-plugin-button'); + this.submitButton = btn; + }) + } + + submit() { + if (!this.busy) { + this.busy = true; + this.submitButton.setButtonText('Creating entry...'); + this.submitCallback(true); + } + } + + onOpen(): void { + this.preview() + } +} \ No newline at end of file From 4de23fa9977bb2622f658388cb62f9097d94bffb Mon Sep 17 00:00:00 2001 From: AB1908 <14124383+AB1908@users.noreply.github.com> Date: Sat, 8 Oct 2022 18:51:15 +0530 Subject: [PATCH 10/21] fix: add PreviewModal import --- src/utils/ModalHelper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/ModalHelper.ts b/src/utils/ModalHelper.ts index a573e12..fd44dbd 100644 --- a/src/utils/ModalHelper.ts +++ b/src/utils/ModalHelper.ts @@ -4,6 +4,7 @@ import {MediaTypeModel} from '../models/MediaTypeModel'; import {MediaDbSearchResultModal} from '../modals/MediaDbSearchResultModal'; import {Notice} from 'obsidian'; import MediaDbPlugin from '../main'; +import { MediaDbPreviewModal } from 'src/modals/MediaDbPreviewModal'; interface AdvancedSearchOptions { query: string, From a3905daf5b41b8fd990d1f1c3fd0b3fb238540a8 Mon Sep 17 00:00:00 2001 From: mProjectsCode Date: Sat, 8 Oct 2022 22:04:03 +0200 Subject: [PATCH 11/21] Some more modal changes (look at those doc comments :o) How did this work the first time i tested it??? --- src/api/apis/BoardGameGeekAPI.ts | 1 - src/main.ts | 90 ++--- src/modals/MediaDbAdvancedSearchModal.ts | 26 +- src/modals/MediaDbIdSearchModal.ts | 23 +- src/modals/MediaDbSearchResultModal.ts | 21 +- .../PropertyMappingModelComponent.svelte | 8 +- .../PropertyMappingModelsComponent.svelte | 2 +- src/utils/ModalHelper.ts | 363 ++++++++++++++---- 8 files changed, 374 insertions(+), 160 deletions(-) diff --git a/src/api/apis/BoardGameGeekAPI.ts b/src/api/apis/BoardGameGeekAPI.ts index 57c5a5b..4198720 100644 --- a/src/api/apis/BoardGameGeekAPI.ts +++ b/src/api/apis/BoardGameGeekAPI.ts @@ -4,7 +4,6 @@ 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; diff --git a/src/main.ts b/src/main.ts index 30fefa0..d9cfe58 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,9 +2,8 @@ import {MarkdownView, Notice, parseYaml, Plugin, stringifyYaml, TFile, TFolder} import {getDefaultSettings, MediaDbPluginSettings, MediaDbSettingTab} from './settings/Settings'; import {APIManager} from './api/APIManager'; import {MediaTypeModel} from './models/MediaTypeModel'; -import {dateTimeToString, markdownTable, replaceIllegalFileNameCharactersInString, UserCancelError, UserSkipError} from './utils/Utils'; +import {dateTimeToString, markdownTable, replaceIllegalFileNameCharactersInString} from './utils/Utils'; import {OMDbAPI} from './api/apis/OMDbAPI'; -import {MediaDbSearchResultModal} from './modals/MediaDbSearchResultModal'; import {MALAPI} from './api/apis/MALAPI'; import {WikipediaAPI} from './api/apis/WikipediaAPI'; import {MusicBrainzAPI} from './api/apis/MusicBrainzAPI'; @@ -15,7 +14,7 @@ import {PropertyMapper} from './settings/PropertyMapper'; import {YAMLConverter} from './utils/YAMLConverter'; import {MediaDbFolderImportModal} from './modals/MediaDbFolderImportModal'; import {PropertyMapping, PropertyMappingModel} from './settings/PropertyMapping'; -import {ModalHelper} from './utils/ModalHelper'; +import {ModalHelper, ModalResultCode} from './utils/ModalHelper'; export default class MediaDbPlugin extends Plugin { settings: MediaDbPluginSettings; @@ -126,23 +125,23 @@ export default class MediaDbPlugin extends Plugin { */ async createLinkWithSearchModal() { - let apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal(async (advancedSearchOptions) => { - return await this.apiManager.query(advancedSearchOptions.query, advancedSearchOptions.apis); + let apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal({}, async (advancedSearchModalData) => { + return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis); }); if (!apiSearchResults) { return; } - const selectResults: MediaTypeModel[] = await this.modalHelper.openSelectModal(apiSearchResults, async (selectedMediaTypeModels) => { - return await this.queryDetails(selectedMediaTypeModels); + const selectResults: MediaTypeModel[] = await this.modalHelper.openSelectModal({elements: apiSearchResults, multiSelect: false}, async (selectModalData) => { + return await this.queryDetails(selectModalData.selected); }); if (!selectResults || selectResults.length < 1) { return; } - const link = `[${selectResults[0].title}](${selectResults[0].url})` + const link = `[${selectResults[0].title}](${selectResults[0].url})`; const view = this.app.workspace.getActiveViewOfType(MarkdownView); @@ -160,16 +159,16 @@ export default class MediaDbPlugin extends Plugin { * TODO: further refactor: extract it into own method, pass the action (api query) as lambda as well as an options object */ async createEntryWithAdvancedSearchModal() { - const apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal(async (advancedSearchOptions) => { - return await this.apiManager.query(advancedSearchOptions.query, advancedSearchOptions.apis); + let apiSearchResults: MediaTypeModel[] = await this.modalHelper.openAdvancedSearchModal({}, async (advancedSearchModalData) => { + return await this.apiManager.query(advancedSearchModalData.query, advancedSearchModalData.apis); }); if (!apiSearchResults) { return; } - const selectResults: MediaTypeModel[] = await this.modalHelper.openSelectModal(apiSearchResults, async (selectedMediaTypeModels) => { - return await this.queryDetails(selectedMediaTypeModels); + const selectResults: MediaTypeModel[] = await this.modalHelper.openSelectModal({elements: apiSearchResults}, async (selectModalData) => { + return await this.queryDetails(selectModalData.selected); }); if (!selectResults) { @@ -180,9 +179,9 @@ export default class MediaDbPlugin extends Plugin { } async createEntryWithIdSearchModal() { - const idSearchResult: MediaTypeModel = await this.modalHelper.openIdSearchModal(async (idSearchOptions) => { - return await this.apiManager.queryDetailedInfoById(idSearchOptions.query, idSearchOptions.api); - }) + const idSearchResult: MediaTypeModel = await this.modalHelper.openIdSearchModal({}, async (idSearchModalData) => { + return await this.apiManager.queryDetailedInfoById(idSearchModalData.query, idSearchModalData.api); + }); if (!idSearchResult) { return; @@ -210,7 +209,7 @@ export default class MediaDbPlugin extends Plugin { return detailModels; } - async createMediaDbNoteFromModel(mediaTypeModel: MediaTypeModel, options: {attachTemplate?: boolean, attachFile?: TFile, openNote?: boolean}): Promise { + async createMediaDbNoteFromModel(mediaTypeModel: MediaTypeModel, options: { attachTemplate?: boolean, attachFile?: TFile, openNote?: boolean }): Promise { try { console.debug('MDB | creating new note'); @@ -223,12 +222,15 @@ export default class MediaDbPlugin extends Plugin { } } - private async generateMediaDbNoteContents(mediaTypeModel: MediaTypeModel, options: {attachTemplate?: boolean, attachFile?: TFile}) { + private async generateMediaDbNoteContents(mediaTypeModel: MediaTypeModel, options: { attachTemplate?: boolean, attachFile?: TFile }) { let fileMetadata = this.modelPropertyMapper.convertObject(mediaTypeModel.toMetaDataObject()); let fileContent = ''; ({fileMetadata, fileContent} = await this.attachFile(fileMetadata, fileContent, options.attachFile)); - ({fileMetadata, fileContent} = await this.attachTemplate(fileMetadata, fileContent, options.attachTemplate ? await this.mediaTypeManager.getTemplate(mediaTypeModel, this.app) : '')); + ({ + fileMetadata, + fileContent, + } = await this.attachTemplate(fileMetadata, fileContent, options.attachTemplate ? await this.mediaTypeManager.getTemplate(mediaTypeModel, this.app) : '')); fileContent = `---\n${this.settings.useCustomYamlStringifier ? YAMLConverter.toYaml(fileMetadata) : stringifyYaml(fileMetadata)}---\n` + fileContent; return fileContent; @@ -417,46 +419,36 @@ export default class MediaDbPlugin extends Plugin { continue; } - let selectedResults: MediaTypeModel[] = []; - const modal = new MediaDbSearchResultModal(this, results, true); - try { - selectedResults = await new Promise((resolve, reject) => { - modal.title = `Results for \'${title}\'`; - modal.setSubmitCallback(res => resolve(res)); - modal.setSkipCallback(() => reject(new UserCancelError('user skipped'))); - modal.setCloseCallback(err => { - if (err) { - reject(err); - } - reject(new UserCancelError('user canceled')); - }); + let {selectModalResult, selectModal} = await this.modalHelper.createSelectModal({elements: results, skipButton: true, modalTitle: `Results for \'${title}\'`}); - modal.open(); - }); - } catch (e) { - modal.close(); - if (e instanceof UserCancelError) { - erroredFiles.push({filePath: file.path, error: e.message}); - canceled = true; - continue; - } else if (e instanceof UserSkipError) { - erroredFiles.push({filePath: file.path, error: e.message}); - continue; - } else { - erroredFiles.push({filePath: file.path, error: e.message}); - continue; - } + if (selectModalResult.code === ModalResultCode.ERROR) { + erroredFiles.push({filePath: file.path, error: selectModalResult.error.message}); + selectModal.close(); + continue; } - if (selectedResults.length === 0) { + if (selectModalResult.code === ModalResultCode.CLOSE) { + erroredFiles.push({filePath: file.path, error: 'user canceled'}); + selectModal.close(); + canceled = true; + continue; + } + + if (selectModalResult.code === ModalResultCode.SKIP) { + erroredFiles.push({filePath: file.path, error: 'user skipped'}); + selectModal.close(); + continue; + } + + if (selectModalResult.data.selected.length === 0) { erroredFiles.push({filePath: file.path, error: `no search results selected`}); continue; } - const detailedResults = await this.queryDetails(selectedResults); + const detailedResults = await this.queryDetails(selectModalResult.data.selected); await this.createMediaDbNotes(detailedResults, appendContent ? file : null); - modal.close(); + selectModal.close(); } } diff --git a/src/modals/MediaDbAdvancedSearchModal.ts b/src/modals/MediaDbAdvancedSearchModal.ts index 52c9786..e25f0a2 100644 --- a/src/modals/MediaDbAdvancedSearchModal.ts +++ b/src/modals/MediaDbAdvancedSearchModal.ts @@ -1,26 +1,37 @@ 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} from '../utils/ModalHelper'; export class MediaDbAdvancedSearchModal extends Modal { + plugin: MediaDbPlugin; + query: string; isBusy: boolean; - plugin: MediaDbPlugin; - searchBtn: ButtonComponent; + title: string; selectedApis: { name: string, selected: boolean }[]; - submitCallback?: (res: { query: string, apis: string[] }) => void; + + searchBtn: ButtonComponent; + + submitCallback?: (res: AdvancedSearchModalData) => void; closeCallback?: (err?: Error) => void; - constructor(plugin: MediaDbPlugin) { + + constructor(plugin: MediaDbPlugin, advancedSearchModalOptions: AdvancedSearchModalOptions) { + advancedSearchModalOptions = Object.assign({}, ADVANCED_SEARCH_MODAL_DEFAULT_OPTIONS, advancedSearchModalOptions); super(plugin.app); + this.plugin = plugin; this.selectedApis = []; + this.title = advancedSearchModalOptions.modalTitle; + this.query = advancedSearchModalOptions.prefilledSearchString; + for (const api of this.plugin.apiManager.apis) { - this.selectedApis.push({name: api.apiName, selected: false}); + this.selectedApis.push({name: api.apiName, selected: advancedSearchModalOptions.preselectedAPIs.contains(api.apiName)}); } } - setSubmitCallback(submitCallback: (res: { query: string, apis: string[] }) => void): void { + setSubmitCallback(submitCallback: (res: AdvancedSearchModalData) => void): void { this.submitCallback = submitCallback; } @@ -59,12 +70,13 @@ export class MediaDbAdvancedSearchModal extends Modal { onOpen() { const {contentEl} = this; - contentEl.createEl('h2', {text: 'Search media db'}); + contentEl.createEl('h2', {text: this.title}); const placeholder = 'Search by title'; const searchComponent = new TextComponent(contentEl); searchComponent.inputEl.style.width = '100%'; searchComponent.setPlaceholder(placeholder); + searchComponent.setValue(this.query); searchComponent.onChange(value => (this.query = value)); searchComponent.inputEl.addEventListener('keydown', this.keyPressCallback.bind(this)); diff --git a/src/modals/MediaDbIdSearchModal.ts b/src/modals/MediaDbIdSearchModal.ts index f53197a..ec29cac 100644 --- a/src/modals/MediaDbIdSearchModal.ts +++ b/src/modals/MediaDbIdSearchModal.ts @@ -1,23 +1,32 @@ import {ButtonComponent, DropdownComponent, Modal, Notice, Setting, TextComponent} from 'obsidian'; import {MediaTypeModel} from '../models/MediaTypeModel'; import MediaDbPlugin from '../main'; +import {ID_SEARCH_MODAL_DEFAULT_OPTIONS, IdSearchModalData, IdSearchModalOptions} from '../utils/ModalHelper'; export class MediaDbIdSearchModal extends Modal { + plugin: MediaDbPlugin; + query: string; isBusy: boolean; - plugin: MediaDbPlugin; - searchBtn: ButtonComponent; + title: string; selectedApi: string; - submitCallback?: (res: { query: string, api: string }, err?: Error) => void; + + searchBtn: ButtonComponent; + + submitCallback?: (res: IdSearchModalData, err?: Error) => void; closeCallback?: (err?: Error) => void; - constructor(plugin: MediaDbPlugin) { + + constructor(plugin: MediaDbPlugin, idSearchModalOptions: IdSearchModalOptions) { + idSearchModalOptions = Object.assign({}, ID_SEARCH_MODAL_DEFAULT_OPTIONS, idSearchModalOptions); super(plugin.app); + this.plugin = plugin; - this.selectedApi = plugin.apiManager.apis[0].apiName; + this.title = idSearchModalOptions.modalTitle; + this.selectedApi = idSearchModalOptions.preselectedAPI || plugin.apiManager.apis[0].apiName; } - setSubmitCallback(submitCallback: (res: { query: string, api: string }, err?: Error) => void): void { + setSubmitCallback(submitCallback: (res: IdSearchModalData, err?: Error) => void): void { this.submitCallback = submitCallback; } @@ -54,7 +63,7 @@ export class MediaDbIdSearchModal extends Modal { onOpen() { const {contentEl} = this; - contentEl.createEl('h2', {text: 'Search media db by id'}); + contentEl.createEl('h2', {text: this.title}); const placeholder = 'Search by id'; const searchComponent = new TextComponent(contentEl); diff --git a/src/modals/MediaDbSearchResultModal.ts b/src/modals/MediaDbSearchResultModal.ts index 546c394..c44a91f 100644 --- a/src/modals/MediaDbSearchResultModal.ts +++ b/src/modals/MediaDbSearchResultModal.ts @@ -1,31 +1,34 @@ import {MediaTypeModel} from '../models/MediaTypeModel'; import MediaDbPlugin from '../main'; import {SelectModal} from './SelectModal'; +import {SELECT_MODAL_OPTIONS_DEFAULT, SelectModalData, SelectModalOptions} from '../utils/ModalHelper'; export class MediaDbSearchResultModal extends SelectModal { plugin: MediaDbPlugin; - heading: string; + busy: boolean; - submitCallback: (res: MediaTypeModel[]) => void; + sendCallback: boolean; + + submitCallback: (res: SelectModalData) => void; closeCallback: (err?: Error) => void; skipCallback: () => void; - sendCallback: boolean; - constructor(plugin: MediaDbPlugin, elements: MediaTypeModel[], skipButton: boolean, allowMultiSelect: boolean = true) { - super(plugin.app, elements, allowMultiSelect); + constructor(plugin: MediaDbPlugin, selectModalOptions: SelectModalOptions) { + selectModalOptions = Object.assign({}, SELECT_MODAL_OPTIONS_DEFAULT, selectModalOptions); + super(plugin.app, selectModalOptions.elements, selectModalOptions.multiSelect); this.plugin = plugin; - this.title = 'Search Results'; + this.title = selectModalOptions.modalTitle; this.description = 'Select one or multiple search results.'; - this.addSkipButton = skipButton; + this.addSkipButton = selectModalOptions.skipButton; this.busy = false; this.sendCallback = false; } - setSubmitCallback(submitCallback: (res: MediaTypeModel[]) => void): void { + setSubmitCallback(submitCallback: (res: SelectModalData) => void): void { this.submitCallback = submitCallback; } @@ -49,7 +52,7 @@ export class MediaDbSearchResultModal extends SelectModal { if (!this.busy) { this.busy = true; this.submitButton.setButtonText('Creating entry...'); - this.submitCallback(this.selectModalElements.filter(x => x.isActive()).map(x => x.value)); + this.submitCallback({selected: this.selectModalElements.filter(x => x.isActive()).map(x => x.value)}); } } diff --git a/src/settings/PropertyMappingModelComponent.svelte b/src/settings/PropertyMappingModelComponent.svelte index da8797b..a0a3b68 100644 --- a/src/settings/PropertyMappingModelComponent.svelte +++ b/src/settings/PropertyMappingModelComponent.svelte @@ -46,16 +46,16 @@
- { /if } - { /if } + { /if } + { /if } - { /each } + { /each } { #if !validationResult?.res }
{validationResult?.err?.message}
- { /if } + { /if }