Merge pull request #207 from Spydarlee/batch-import-by-id

Allow batch / folder import by id as well as title
This commit is contained in:
Moritz Jung 2025-09-03 22:08:47 +02:00 committed by GitHub
commit 9941f14008
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 133 additions and 70 deletions

View file

@ -26,6 +26,7 @@ import type { SearchModalOptions } from './utils/ModalHelper';
import { ModalHelper, ModalResultCode } from './utils/ModalHelper'; import { ModalHelper, ModalResultCode } from './utils/ModalHelper';
import type { CreateNoteOptions } from './utils/Utils'; import type { CreateNoteOptions } from './utils/Utils';
import { dateTimeToString, markdownTable, replaceIllegalFileNameCharactersInString, unCamelCase, hasTemplaterPlugin, useTemplaterPluginInFile } from './utils/Utils'; import { dateTimeToString, markdownTable, replaceIllegalFileNameCharactersInString, unCamelCase, hasTemplaterPlugin, useTemplaterPluginInFile } from './utils/Utils';
import { BulkImportLookupMethod } from 'src/utils/BulkImportLookupMethod';
export type Metadata = Record<string, unknown>; export type Metadata = Record<string, unknown>;
@ -561,9 +562,14 @@ export default class MediaDbPlugin extends Plugin {
const erroredFiles: { filePath: string; error: string }[] = []; const erroredFiles: { filePath: string; error: string }[] = [];
let canceled: boolean = false; let canceled: boolean = false;
const { selectedAPI, titleFieldName, appendContent } = await new Promise<{ selectedAPI: string; titleFieldName: string; appendContent: boolean }>(resolve => { const { selectedAPI, lookupMethod, fieldName, appendContent } = await new Promise<{
new MediaDbFolderImportModal(this.app, this, (selectedAPI: string, titleFieldName: string, appendContent: boolean) => { selectedAPI: string;
resolve({ selectedAPI, titleFieldName, appendContent }); lookupMethod: string;
fieldName: string;
appendContent: boolean;
}>(resolve => {
new MediaDbFolderImportModal(this.app, this, (selectedAPI: string, lookupMethod: string, fieldName: string, appendContent: boolean) => {
resolve({ selectedAPI, lookupMethod, fieldName, appendContent });
}).open(); }).open();
}); });
@ -576,16 +582,27 @@ export default class MediaDbPlugin extends Plugin {
} }
const metadata = this.getMetadataFromFileCache(file); const metadata = this.getMetadataFromFileCache(file);
const lookupValue = metadata[fieldName];
const title = metadata[titleFieldName]; if (!lookupValue || typeof lookupValue !== 'string') {
if (!title || typeof title !== 'string') { erroredFiles.push({ filePath: file.path, error: `metadata field '${fieldName}' not found, empty, or not a string` });
erroredFiles.push({ filePath: file.path, error: `metadata field '${titleFieldName}' not found, empty, or not a string` }); continue;
} else if (lookupMethod === BulkImportLookupMethod.ID) {
try {
const model = await this.apiManager.queryDetailedInfoById(lookupValue, selectedAPI);
if (model) {
await this.createMediaDbNotes([model], appendContent ? file : undefined);
} else {
erroredFiles.push({ filePath: file.path, error: `Failed to query API with id: ${lookupValue}` });
}
} catch (e) {
erroredFiles.push({ filePath: file.path, error: `${e}` });
continue; continue;
} }
} else if (lookupMethod === BulkImportLookupMethod.TITLE) {
let results: MediaTypeModel[] = []; let results: MediaTypeModel[] = [];
try { try {
results = await this.apiManager.query(title, [selectedAPI]); results = await this.apiManager.query(lookupValue, [selectedAPI]);
} catch (e) { } catch (e) {
erroredFiles.push({ filePath: file.path, error: `${e}` }); erroredFiles.push({ filePath: file.path, error: `${e}` });
continue; continue;
@ -595,7 +612,11 @@ export default class MediaDbPlugin extends Plugin {
continue; continue;
} }
const { 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 '${lookupValue}'`,
});
if (selectModalResult.code === ModalResultCode.ERROR) { if (selectModalResult.code === ModalResultCode.ERROR) {
erroredFiles.push({ filePath: file.path, error: selectModalResult.error.message }); erroredFiles.push({ filePath: file.path, error: selectModalResult.error.message });
@ -625,6 +646,10 @@ export default class MediaDbPlugin extends Plugin {
await this.createMediaDbNotes(detailedResults, appendContent ? file : undefined); await this.createMediaDbNotes(detailedResults, appendContent ? file : undefined);
selectModal.close(); selectModal.close();
} else {
erroredFiles.push({ filePath: file.path, error: `invalid lookup type` });
continue;
}
} }
} }

View file

@ -1,26 +1,30 @@
import type { App, ButtonComponent } from 'obsidian'; import type { App, ButtonComponent } from 'obsidian';
import { DropdownComponent, Modal, Setting, TextComponent, ToggleComponent } from 'obsidian'; import { DropdownComponent, Modal, Setting, TextComponent, ToggleComponent } from 'obsidian';
import type MediaDbPlugin from '../main'; import type MediaDbPlugin from '../main';
import type { APIModel } from 'src/api/APIModel';
import { BulkImportLookupMethod } from 'src/utils/BulkImportLookupMethod';
export class MediaDbFolderImportModal extends Modal { export class MediaDbFolderImportModal extends Modal {
plugin: MediaDbPlugin; plugin: MediaDbPlugin;
onSubmit: (selectedAPI: string, titleFieldName: string, appendContent: boolean) => void; onSubmit: (selectedAPI: string, lookupMethod: string, fieldName: string, appendContent: boolean) => void;
selectedApi: string; selectedApi: string;
searchBtn?: ButtonComponent; searchBtn?: ButtonComponent;
titleFieldName: string; lookupMethod: string;
fieldName: string;
appendContent: boolean; appendContent: boolean;
constructor(app: App, plugin: MediaDbPlugin, onSubmit: (selectedAPI: string, titleFieldName: string, appendContent: boolean) => void) { constructor(app: App, plugin: MediaDbPlugin, onSubmit: (selectedAPI: string, lookupMethod: string, fieldName: string, appendContent: boolean) => void) {
super(app); super(app);
this.plugin = plugin; this.plugin = plugin;
this.onSubmit = onSubmit; this.onSubmit = onSubmit;
this.selectedApi = plugin.apiManager.apis[0].apiName; this.selectedApi = plugin.apiManager.apis[0].apiName;
this.titleFieldName = ''; this.lookupMethod = BulkImportLookupMethod.TITLE;
this.fieldName = '';
this.appendContent = false; this.appendContent = false;
} }
submit(): void { submit(): void {
this.onSubmit(this.selectedApi, this.titleFieldName, this.appendContent); this.onSubmit(this.selectedApi, this.lookupMethod, this.fieldName, this.appendContent);
this.close(); this.close();
} }
@ -29,21 +33,19 @@ export class MediaDbFolderImportModal extends Modal {
contentEl.createEl('h2', { text: 'Import folder as Media DB entries' }); contentEl.createEl('h2', { text: 'Import folder as Media DB entries' });
const apiSelectorWrapper = contentEl.createEl('div', { cls: 'media-db-plugin-list-wrapper' }); this.createDropdownEl(
const apiSelectorTextWrapper = apiSelectorWrapper.createEl('div', { cls: 'media-db-plugin-list-text-wrapper' }); contentEl,
apiSelectorTextWrapper.createEl('span', { text: 'API to search', cls: 'media-db-plugin-list-text' }); 'API to search',
(value: string) => {
const apiSelectorComponent = new DropdownComponent(apiSelectorWrapper);
apiSelectorComponent.onChange((value: string) => {
this.selectedApi = value; this.selectedApi = value;
}); },
for (const api of this.plugin.apiManager.apis) { this.plugin.apiManager.apis.map((api: APIModel) => {
apiSelectorComponent.addOption(api.apiName, api.apiName); return { value: api.apiName, display: api.apiName };
} }),
apiSelectorWrapper.appendChild(apiSelectorComponent.selectEl); );
contentEl.createDiv({ cls: 'media-db-plugin-spacer' }); contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
contentEl.createEl('h3', { text: 'Append note content to Media DB entry.' }); contentEl.createEl('h3', { text: 'Append note content to Media DB entry?' });
const appendContentToggleElementWrapper = contentEl.createEl('div', { cls: 'media-db-plugin-list-wrapper' }); const appendContentToggleElementWrapper = contentEl.createEl('div', { cls: 'media-db-plugin-list-wrapper' });
const appendContentToggleTextWrapper = appendContentToggleElementWrapper.createEl('div', { cls: 'media-db-plugin-list-text-wrapper' }); const appendContentToggleTextWrapper = appendContentToggleElementWrapper.createEl('div', { cls: 'media-db-plugin-list-text-wrapper' });
@ -60,19 +62,38 @@ export class MediaDbFolderImportModal extends Modal {
appendContentToggleComponentWrapper.appendChild(appendContentToggle.toggleEl); appendContentToggleComponentWrapper.appendChild(appendContentToggle.toggleEl);
contentEl.createDiv({ cls: 'media-db-plugin-spacer' }); contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
contentEl.createEl('h3', { text: 'The name of the metadata field that should be used as the title to query.' }); contentEl.createEl('h3', { text: 'Media lookup method' });
contentEl.createEl('p', {
text: 'Choose whether to search the API by title (can return multiple results) or lookup directly using an ID (returns at most one result), and specify the name of the frontmatter property which contains the title or ID of the media.',
});
const placeholder = 'title'; this.createDropdownEl(
const titleFieldNameComponent = new TextComponent(contentEl); contentEl,
titleFieldNameComponent.inputEl.style.width = '100%'; 'Lookup media by',
titleFieldNameComponent.setPlaceholder(placeholder); (value: string) => {
titleFieldNameComponent.onChange(value => (this.titleFieldName = value)); this.lookupMethod = value;
titleFieldNameComponent.inputEl.addEventListener('keydown', ke => { },
[
{ value: BulkImportLookupMethod.TITLE, display: 'Title' },
{ value: BulkImportLookupMethod.ID, display: 'ID' },
],
);
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
const fieldNameWrapperEl = contentEl.createEl('div', { cls: 'media-db-plugin-list-wrapper' });
const fieldNameLabelWrapperEl = fieldNameWrapperEl.createEl('div', { cls: 'media-db-plugin-list-text-wrapper' });
fieldNameLabelWrapperEl.createEl('span', { text: 'Using the property named', cls: 'media-db-plugin-list-text' });
const fieldNameComponent = new TextComponent(fieldNameWrapperEl);
fieldNameComponent.setPlaceholder('title / id');
fieldNameComponent.onChange(value => (this.fieldName = value));
fieldNameComponent.inputEl.addEventListener('keydown', ke => {
if (ke.key === 'Enter') { if (ke.key === 'Enter') {
this.submit(); this.submit();
} }
}); });
contentEl.appendChild(titleFieldNameComponent.inputEl); contentEl.appendChild(fieldNameWrapperEl);
contentEl.createDiv({ cls: 'media-db-plugin-spacer' }); contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
@ -93,6 +114,19 @@ export class MediaDbFolderImportModal extends Modal {
}); });
} }
createDropdownEl(parentEl: HTMLElement, label: string, onChange: (value: string) => void, options: { value: string; display: string }[]): void {
const wrapperEl = parentEl.createEl('div', { cls: 'media-db-plugin-list-wrapper' });
const labelWrapperEl = wrapperEl.createEl('div', { cls: 'media-db-plugin-list-text-wrapper' });
labelWrapperEl.createEl('span', { text: label, cls: 'media-db-plugin-list-text' });
const dropDownComponent = new DropdownComponent(wrapperEl);
dropDownComponent.onChange(onChange);
for (const option of options) {
dropDownComponent.addOption(option.value, option.display);
}
wrapperEl.appendChild(dropDownComponent.selectEl);
}
onClose(): void { onClose(): void {
const { contentEl } = this; const { contentEl } = this;
contentEl.empty(); contentEl.empty();

View file

@ -0,0 +1,4 @@
export enum BulkImportLookupMethod {
ID = 'id',
TITLE = 'title',
}