add obsidian eslint plugin, solve major issues
This commit is contained in:
parent
84214a1c3c
commit
498e2611ae
105 changed files with 5702 additions and 5308 deletions
44
packages/obsidian/src/modals/ConfirmOverwriteModal.ts
Normal file
44
packages/obsidian/src/modals/ConfirmOverwriteModal.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import type { App } from 'obsidian';
|
||||
import { Modal, Setting } from 'obsidian';
|
||||
|
||||
export class ConfirmOverwriteModal extends Modal {
|
||||
result: boolean = false;
|
||||
onSubmit: (result: boolean) => void;
|
||||
fileName: string;
|
||||
|
||||
constructor(app: App, fileName: string, onSubmit: (result: boolean) => void) {
|
||||
super(app);
|
||||
this.fileName = fileName;
|
||||
this.onSubmit = onSubmit;
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl('h2', { text: 'File already exists' });
|
||||
contentEl.createEl('p', { text: `The file "${this.fileName}" already exists. Do you want to overwrite it?` });
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
|
||||
const bottomSettingRow = new Setting(contentEl);
|
||||
bottomSettingRow.addButton(btn => {
|
||||
btn.setButtonText('No');
|
||||
btn.onClick(() => this.close());
|
||||
btn.buttonEl.addClass('media-db-plugin-button');
|
||||
});
|
||||
bottomSettingRow.addButton(btn => {
|
||||
btn.setButtonText('Yes');
|
||||
btn.setCta();
|
||||
btn.onClick(() => {
|
||||
this.result = true;
|
||||
this.close();
|
||||
});
|
||||
btn.buttonEl.addClass('media-db-plugin-button');
|
||||
});
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
this.onSubmit(this.result);
|
||||
}
|
||||
}
|
||||
133
packages/obsidian/src/modals/MediaDbAdvancedSearchModal.ts
Normal file
133
packages/obsidian/src/modals/MediaDbAdvancedSearchModal.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import type { ButtonComponent } from 'obsidian';
|
||||
import { Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||
import type MediaDbPlugin from 'packages/obsidian/src/main';
|
||||
import type { AdvancedSearchModalData, AdvancedSearchModalOptions } from 'packages/obsidian/src/utils/ModalHelper';
|
||||
import { ADVANCED_SEARCH_MODAL_DEFAULT_OPTIONS } from 'packages/obsidian/src/utils/ModalHelper';
|
||||
|
||||
export class MediaDbAdvancedSearchModal extends Modal {
|
||||
plugin: MediaDbPlugin;
|
||||
|
||||
query: string;
|
||||
isBusy: boolean;
|
||||
title: string;
|
||||
selectedApis: string[];
|
||||
|
||||
searchBtn?: ButtonComponent;
|
||||
|
||||
submitCallback?: (res: AdvancedSearchModalData) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
|
||||
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 ?? '';
|
||||
this.isBusy = false;
|
||||
}
|
||||
|
||||
setSubmitCb(submitCallback: (res: AdvancedSearchModalData) => void): void {
|
||||
this.submitCallback = submitCallback;
|
||||
}
|
||||
|
||||
setCloseCb(closeCallback: (err?: Error) => void): void {
|
||||
this.closeCallback = closeCallback;
|
||||
}
|
||||
|
||||
keyPressCallback(event: KeyboardEvent): void {
|
||||
if (event.key === 'Enter') {
|
||||
void this.search();
|
||||
}
|
||||
}
|
||||
|
||||
async search(): Promise<void> {
|
||||
if (!this.query || this.query.length < 3) {
|
||||
new Notice('MDB | Query too short');
|
||||
return;
|
||||
}
|
||||
|
||||
const apis: string[] = this.selectedApis;
|
||||
|
||||
if (apis.length === 0) {
|
||||
new Notice('MDB | No API selected');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isBusy) {
|
||||
this.isBusy = true;
|
||||
this.searchBtn?.setDisabled(false);
|
||||
this.searchBtn?.setButtonText('Searching...');
|
||||
|
||||
this.submitCallback?.({ query: this.query, apis: apis });
|
||||
}
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
const { contentEl } = this;
|
||||
|
||||
contentEl.createEl('h2', { text: this.title });
|
||||
|
||||
const placeholder = 'Search by title';
|
||||
const searchComponent = new TextComponent(contentEl);
|
||||
searchComponent.inputEl.addClass('media-db-plugin-search-input');
|
||||
searchComponent.setPlaceholder(placeholder);
|
||||
searchComponent.setValue(this.query);
|
||||
searchComponent.onChange(value => (this.query = value));
|
||||
searchComponent.inputEl.addEventListener('keydown', this.keyPressCallback.bind(this));
|
||||
|
||||
contentEl.appendChild(searchComponent.inputEl);
|
||||
searchComponent.inputEl.focus();
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
contentEl.createEl('h3', { text: 'APIs to search' });
|
||||
|
||||
// const apiToggleComponents: Component[] = [];
|
||||
for (const api of this.plugin.apiManager.apis) {
|
||||
const apiToggleListElementWrapper = contentEl.createDiv({ cls: 'media-db-plugin-list-wrapper' });
|
||||
|
||||
const apiToggleTextWrapper = apiToggleListElementWrapper.createDiv({ cls: 'media-db-plugin-list-text-wrapper' });
|
||||
apiToggleTextWrapper.createSpan({ text: api.apiName, cls: 'media-db-plugin-list-text' });
|
||||
apiToggleTextWrapper.createEl('small', { text: api.apiDescription, cls: 'media-db-plugin-list-text' });
|
||||
|
||||
const apiToggleComponentWrapper = apiToggleListElementWrapper.createDiv({ cls: 'media-db-plugin-list-toggle' });
|
||||
|
||||
const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper);
|
||||
apiToggleComponent.setTooltip(api.apiName);
|
||||
apiToggleComponent.setValue(this.selectedApis.some(x => x === api.apiName));
|
||||
apiToggleComponent.onChange(value => {
|
||||
if (value) {
|
||||
this.selectedApis.push(api.apiName);
|
||||
} else {
|
||||
this.selectedApis = this.selectedApis.filter(x => x !== api.apiName);
|
||||
}
|
||||
});
|
||||
apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl);
|
||||
}
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
|
||||
new Setting(contentEl)
|
||||
.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(() => {
|
||||
void this.search();
|
||||
});
|
||||
btn.buttonEl.addClass('media-db-plugin-button');
|
||||
this.searchBtn = btn;
|
||||
});
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.closeCallback?.();
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
134
packages/obsidian/src/modals/MediaDbBulkImportModal.ts
Normal file
134
packages/obsidian/src/modals/MediaDbBulkImportModal.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import type { ButtonComponent } from 'obsidian';
|
||||
import { DropdownComponent, Modal, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||
import type { APIModel } from 'packages/obsidian/src/api/APIModel';
|
||||
import type MediaDbPlugin from 'packages/obsidian/src/main';
|
||||
import { BulkImportLookupMethod } from 'packages/obsidian/src/utils/BulkImportHelper';
|
||||
|
||||
export class MediaDbBulkImportModal extends Modal {
|
||||
plugin: MediaDbPlugin;
|
||||
onSubmit: (selectedAPI: string, lookupMethod: BulkImportLookupMethod, fieldName: string, appendContent: boolean) => void;
|
||||
selectedApi: string;
|
||||
searchBtn?: ButtonComponent;
|
||||
lookupMethod: BulkImportLookupMethod;
|
||||
fieldName: string;
|
||||
appendContent: boolean;
|
||||
|
||||
constructor(plugin: MediaDbPlugin, onSubmit: (selectedAPI: string, lookupMethod: BulkImportLookupMethod, fieldName: string, appendContent: boolean) => void) {
|
||||
super(plugin.app);
|
||||
this.plugin = plugin;
|
||||
this.onSubmit = onSubmit;
|
||||
this.selectedApi = plugin.apiManager.apis[0].apiName;
|
||||
this.lookupMethod = BulkImportLookupMethod.TITLE;
|
||||
this.fieldName = '';
|
||||
this.appendContent = false;
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
this.onSubmit(this.selectedApi, this.lookupMethod, this.fieldName, this.appendContent);
|
||||
this.close();
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
const { contentEl } = this;
|
||||
|
||||
contentEl.createEl('h2', { text: 'Import folder as Media DB entries' });
|
||||
|
||||
this.createDropdownEl(
|
||||
contentEl,
|
||||
'API to search',
|
||||
(value: string) => {
|
||||
this.selectedApi = value;
|
||||
},
|
||||
this.plugin.apiManager.apis.map((api: APIModel) => {
|
||||
return { value: api.apiName, display: api.apiName };
|
||||
}),
|
||||
);
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
contentEl.createEl('h3', { text: 'Append note content to Media DB entry?' });
|
||||
|
||||
const appendContentToggleElementWrapper = contentEl.createDiv({ cls: 'media-db-plugin-list-wrapper' });
|
||||
const appendContentToggleTextWrapper = appendContentToggleElementWrapper.createDiv({ cls: 'media-db-plugin-list-text-wrapper' });
|
||||
appendContentToggleTextWrapper.createSpan({
|
||||
text: 'If this is enabled, the plugin will override metadata fields with the same name.',
|
||||
cls: 'media-db-plugin-list-text',
|
||||
});
|
||||
|
||||
const appendContentToggleComponentWrapper = appendContentToggleElementWrapper.createDiv({ cls: 'media-db-plugin-list-toggle' });
|
||||
|
||||
const appendContentToggle = new ToggleComponent(appendContentToggleElementWrapper);
|
||||
appendContentToggle.setValue(false);
|
||||
appendContentToggle.onChange(value => (this.appendContent = value));
|
||||
appendContentToggleComponentWrapper.appendChild(appendContentToggle.toggleEl);
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
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.',
|
||||
});
|
||||
|
||||
this.createDropdownEl(
|
||||
contentEl,
|
||||
'Lookup media by',
|
||||
(value: string) => {
|
||||
this.lookupMethod = value as BulkImportLookupMethod;
|
||||
},
|
||||
[
|
||||
{ value: BulkImportLookupMethod.TITLE, display: 'Title' },
|
||||
{ value: BulkImportLookupMethod.ID, display: 'ID' },
|
||||
],
|
||||
);
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
|
||||
const fieldNameWrapperEl = contentEl.createDiv({ cls: 'media-db-plugin-list-wrapper' });
|
||||
const fieldNameLabelWrapperEl = fieldNameWrapperEl.createDiv({ cls: 'media-db-plugin-list-text-wrapper' });
|
||||
fieldNameLabelWrapperEl.createSpan({ 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') {
|
||||
this.submit();
|
||||
}
|
||||
});
|
||||
contentEl.appendChild(fieldNameWrapperEl);
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
|
||||
new Setting(contentEl)
|
||||
.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;
|
||||
});
|
||||
}
|
||||
|
||||
createDropdownEl(parentEl: HTMLElement, label: string, onChange: (value: string) => void, options: { value: string; display: string }[]): void {
|
||||
const wrapperEl = parentEl.createDiv({ cls: 'media-db-plugin-list-wrapper' });
|
||||
const labelWrapperEl = wrapperEl.createDiv({ cls: 'media-db-plugin-list-text-wrapper' });
|
||||
labelWrapperEl.createSpan({ 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 {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
119
packages/obsidian/src/modals/MediaDbIdSearchModal.ts
Normal file
119
packages/obsidian/src/modals/MediaDbIdSearchModal.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import type { ButtonComponent } from 'obsidian';
|
||||
import { DropdownComponent, Modal, Notice, Setting, TextComponent } from 'obsidian';
|
||||
import type MediaDbPlugin from 'packages/obsidian/src/main';
|
||||
import type { IdSearchModalData, IdSearchModalOptions } from 'packages/obsidian/src/utils/ModalHelper';
|
||||
import { ID_SEARCH_MODAL_DEFAULT_OPTIONS } from 'packages/obsidian/src/utils/ModalHelper';
|
||||
|
||||
export class MediaDbIdSearchModal extends Modal {
|
||||
plugin: MediaDbPlugin;
|
||||
|
||||
query: string;
|
||||
isBusy: boolean;
|
||||
title: string;
|
||||
selectedApi: string;
|
||||
|
||||
searchBtn?: ButtonComponent;
|
||||
|
||||
submitCallback?: (res: IdSearchModalData, err?: Error) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
|
||||
constructor(plugin: MediaDbPlugin, idSearchModalOptions: IdSearchModalOptions) {
|
||||
idSearchModalOptions = Object.assign({}, ID_SEARCH_MODAL_DEFAULT_OPTIONS, idSearchModalOptions);
|
||||
super(plugin.app);
|
||||
|
||||
this.plugin = plugin;
|
||||
this.title = idSearchModalOptions.modalTitle ?? '';
|
||||
this.selectedApi = idSearchModalOptions.preselectedAPI ?? plugin.apiManager.apis[0].apiName;
|
||||
this.query = '';
|
||||
this.isBusy = false;
|
||||
}
|
||||
|
||||
setSubmitCb(submitCallback: (res: IdSearchModalData, err?: Error) => void): void {
|
||||
this.submitCallback = submitCallback;
|
||||
}
|
||||
|
||||
setCloseCb(closeCallback: (err?: Error) => void): void {
|
||||
this.closeCallback = closeCallback;
|
||||
}
|
||||
|
||||
keyPressCallback(event: KeyboardEvent): void {
|
||||
if (event.key === 'Enter') {
|
||||
void this.search();
|
||||
}
|
||||
}
|
||||
|
||||
async search(): Promise<void> {
|
||||
if (!this.query) {
|
||||
new Notice('MDB | no Id entered');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selectedApi) {
|
||||
new Notice('MDB | No API selected');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isBusy) {
|
||||
this.isBusy = true;
|
||||
this.searchBtn?.setDisabled(false);
|
||||
this.searchBtn?.setButtonText('Searching...');
|
||||
|
||||
this.submitCallback?.({ query: this.query, api: this.selectedApi });
|
||||
}
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
const { contentEl } = this;
|
||||
|
||||
contentEl.createEl('h2', { text: this.title });
|
||||
|
||||
const placeholder = 'Search by id';
|
||||
const searchComponent = new TextComponent(contentEl);
|
||||
searchComponent.inputEl.addClass('media-db-plugin-search-input');
|
||||
searchComponent.setPlaceholder(placeholder);
|
||||
searchComponent.onChange(value => (this.query = value));
|
||||
searchComponent.inputEl.addEventListener('keydown', this.keyPressCallback.bind(this));
|
||||
|
||||
contentEl.appendChild(searchComponent.inputEl);
|
||||
searchComponent.inputEl.focus();
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
|
||||
const apiSelectorWrapper = contentEl.createDiv({ cls: 'media-db-plugin-list-wrapper' });
|
||||
const apiSelectorTExtWrapper = apiSelectorWrapper.createDiv({ cls: 'media-db-plugin-list-text-wrapper' });
|
||||
apiSelectorTExtWrapper.createSpan({ text: 'API to search', cls: 'media-db-plugin-list-text' });
|
||||
|
||||
const apiSelectorComponent = new DropdownComponent(apiSelectorWrapper);
|
||||
apiSelectorComponent.onChange((value: string) => {
|
||||
this.selectedApi = value;
|
||||
});
|
||||
for (const api of this.plugin.apiManager.apis) {
|
||||
apiSelectorComponent.addOption(api.apiName, api.apiName);
|
||||
}
|
||||
apiSelectorWrapper.appendChild(apiSelectorComponent.selectEl);
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
|
||||
new Setting(contentEl)
|
||||
.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(() => {
|
||||
void this.search();
|
||||
});
|
||||
btn.buttonEl.addClass('media-db-plugin-button');
|
||||
this.searchBtn = btn;
|
||||
});
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.closeCallback?.();
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
87
packages/obsidian/src/modals/MediaDbPreviewModal.ts
Normal file
87
packages/obsidian/src/modals/MediaDbPreviewModal.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import { Component, MarkdownRenderer, Modal, Setting } from 'obsidian';
|
||||
import type MediaDbPlugin from 'packages/obsidian/src/main';
|
||||
import type { MediaTypeModel } from 'packages/obsidian/src/models/MediaTypeModel';
|
||||
import { Logger } from 'packages/obsidian/src/utils/Logger';
|
||||
import type { PreviewModalData, PreviewModalOptions } from 'packages/obsidian/src/utils/ModalHelper';
|
||||
import { PREVIEW_MODAL_DEFAULT_OPTIONS } from 'packages/obsidian/src/utils/ModalHelper';
|
||||
|
||||
export class MediaDbPreviewModal extends Modal {
|
||||
plugin: MediaDbPlugin;
|
||||
|
||||
elements: MediaTypeModel[];
|
||||
title: string;
|
||||
markdownComponent: Component;
|
||||
|
||||
submitCallback?: (previewModalData: PreviewModalData) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
|
||||
constructor(plugin: MediaDbPlugin, previewModalOptions: PreviewModalOptions) {
|
||||
previewModalOptions = Object.assign({}, PREVIEW_MODAL_DEFAULT_OPTIONS, previewModalOptions);
|
||||
|
||||
super(plugin.app);
|
||||
|
||||
this.plugin = plugin;
|
||||
this.title = previewModalOptions.modalTitle ?? '';
|
||||
this.elements = previewModalOptions.elements ?? [];
|
||||
|
||||
this.markdownComponent = new Component();
|
||||
}
|
||||
|
||||
setSubmitCb(submitCallback: (previewModalData: PreviewModalData) => void): void {
|
||||
this.submitCallback = submitCallback;
|
||||
}
|
||||
|
||||
setCloseCb(closeCallback: (err?: Error) => void): void {
|
||||
this.closeCallback = closeCallback;
|
||||
}
|
||||
|
||||
async preview(): Promise<void> {
|
||||
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' });
|
||||
|
||||
this.markdownComponent.load();
|
||||
|
||||
for (const result of this.elements) {
|
||||
previewWrapper.createEl('h3', { text: result.englishTitle });
|
||||
const fileDiv = previewWrapper.createDiv({ cls: 'media-db-plugin-preview' });
|
||||
|
||||
let fileContent = this.plugin.fileHelper.generateMediaDbNoteFrontmatterPreview(result);
|
||||
fileContent = `\`\`\`yaml\n${fileContent}\`\`\``;
|
||||
|
||||
try {
|
||||
// TODO: fix this not rendering the frontmatter any more
|
||||
await MarkdownRenderer.render(this.app, fileContent, fileDiv, '', this.markdownComponent);
|
||||
} catch (e) {
|
||||
Logger.warn(`mdb | error during rendering of preview`, e);
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
});
|
||||
bottomSettingRow.addButton(btn => {
|
||||
btn.setButtonText('Ok');
|
||||
btn.setCta();
|
||||
btn.onClick(() => this.submitCallback?.({ confirmed: true }));
|
||||
btn.buttonEl.addClass('media-db-plugin-button');
|
||||
});
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
void this.preview();
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.markdownComponent.unload();
|
||||
this.closeCallback?.();
|
||||
}
|
||||
}
|
||||
145
packages/obsidian/src/modals/MediaDbSearchModal.ts
Normal file
145
packages/obsidian/src/modals/MediaDbSearchModal.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import type { ButtonComponent } from 'obsidian';
|
||||
import { Modal, Notice, Setting, TextComponent, ToggleComponent } from 'obsidian';
|
||||
import type MediaDbPlugin from 'packages/obsidian/src/main';
|
||||
import type { MediaType } from 'packages/obsidian/src/utils/MediaType';
|
||||
import { MEDIA_TYPES } from 'packages/obsidian/src/utils/MediaTypeManager';
|
||||
import type { SearchModalData, SearchModalOptions } from 'packages/obsidian/src/utils/ModalHelper';
|
||||
import { SEARCH_MODAL_DEFAULT_OPTIONS } from 'packages/obsidian/src/utils/ModalHelper';
|
||||
import { unCamelCase } from 'packages/obsidian/src/utils/Utils';
|
||||
|
||||
export class MediaDbSearchModal extends Modal {
|
||||
plugin: MediaDbPlugin;
|
||||
|
||||
query: string;
|
||||
isBusy: boolean;
|
||||
title: string;
|
||||
selectedTypes: MediaType[];
|
||||
|
||||
searchBtn?: ButtonComponent;
|
||||
|
||||
submitCallback?: (res: SearchModalData) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
|
||||
constructor(plugin: MediaDbPlugin, searchModalOptions: SearchModalOptions) {
|
||||
searchModalOptions = Object.assign({}, SEARCH_MODAL_DEFAULT_OPTIONS, searchModalOptions);
|
||||
super(plugin.app);
|
||||
|
||||
this.plugin = plugin;
|
||||
this.selectedTypes = [...(searchModalOptions.preselectedTypes ?? [])];
|
||||
this.title = searchModalOptions.modalTitle ?? '';
|
||||
this.query = searchModalOptions.prefilledSearchString ?? '';
|
||||
this.isBusy = false;
|
||||
}
|
||||
|
||||
setSubmitCb(submitCallback: (res: SearchModalData) => void): void {
|
||||
this.submitCallback = submitCallback;
|
||||
}
|
||||
|
||||
setCloseCb(closeCallback: (err?: Error) => void): void {
|
||||
this.closeCallback = closeCallback;
|
||||
}
|
||||
|
||||
keyPressCallback(event: KeyboardEvent): void {
|
||||
if (event.key === 'Enter') {
|
||||
void this.search();
|
||||
}
|
||||
}
|
||||
|
||||
async search(): Promise<void> {
|
||||
if (!this.query || this.query.length < 3) {
|
||||
new Notice('MDB | Query too short');
|
||||
return;
|
||||
}
|
||||
|
||||
const types: MediaType[] = this.selectedTypes;
|
||||
|
||||
if (types.length === 0) {
|
||||
new Notice('MDB | No Type selected');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isBusy) {
|
||||
this.isBusy = true;
|
||||
this.searchBtn?.setDisabled(false);
|
||||
this.searchBtn?.setButtonText('Searching...');
|
||||
|
||||
this.submitCallback?.({ query: this.query, types: types });
|
||||
}
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
const { contentEl } = this;
|
||||
|
||||
contentEl.createEl('h2', { text: this.title });
|
||||
|
||||
const placeholder = 'Search by title';
|
||||
const searchComponent = new TextComponent(contentEl);
|
||||
let currentToggle: ToggleComponent | undefined = undefined;
|
||||
|
||||
searchComponent.inputEl.addClass('media-db-plugin-search-input');
|
||||
searchComponent.setPlaceholder(placeholder);
|
||||
searchComponent.setValue(this.query);
|
||||
searchComponent.onChange(value => (this.query = value));
|
||||
searchComponent.inputEl.addEventListener('keydown', this.keyPressCallback.bind(this));
|
||||
|
||||
contentEl.appendChild(searchComponent.inputEl);
|
||||
searchComponent.inputEl.focus();
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
contentEl.createEl('h3', { text: 'APIs to search' });
|
||||
|
||||
for (const mediaType of MEDIA_TYPES) {
|
||||
const apiToggleListElementWrapper = contentEl.createDiv({ cls: 'media-db-plugin-list-wrapper' });
|
||||
|
||||
const apiToggleTextWrapper = apiToggleListElementWrapper.createDiv({ cls: 'media-db-plugin-list-text-wrapper' });
|
||||
apiToggleTextWrapper.createSpan({ text: unCamelCase(mediaType), cls: 'media-db-plugin-list-text' });
|
||||
|
||||
const apiToggleComponentWrapper = apiToggleListElementWrapper.createDiv({ cls: 'media-db-plugin-list-toggle' });
|
||||
|
||||
const apiToggleComponent = new ToggleComponent(apiToggleComponentWrapper);
|
||||
apiToggleComponent.setTooltip(unCamelCase(mediaType));
|
||||
apiToggleComponent.setValue(this.selectedTypes.includes(mediaType));
|
||||
if (apiToggleComponent.getValue()) {
|
||||
currentToggle = apiToggleComponent;
|
||||
}
|
||||
apiToggleComponent.onChange(value => {
|
||||
if (value) {
|
||||
if (currentToggle && currentToggle !== apiToggleComponent) {
|
||||
currentToggle.setValue(false);
|
||||
this.selectedTypes = this.selectedTypes.filter(x => x !== mediaType);
|
||||
}
|
||||
currentToggle = apiToggleComponent;
|
||||
this.selectedTypes.push(mediaType);
|
||||
} else {
|
||||
currentToggle = undefined;
|
||||
this.selectedTypes = this.selectedTypes.filter(x => x !== mediaType);
|
||||
}
|
||||
});
|
||||
apiToggleComponentWrapper.appendChild(apiToggleComponent.toggleEl);
|
||||
}
|
||||
|
||||
contentEl.createDiv({ cls: 'media-db-plugin-spacer' });
|
||||
|
||||
new Setting(contentEl)
|
||||
.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(() => {
|
||||
void this.search();
|
||||
});
|
||||
btn.buttonEl.addClass('media-db-plugin-button');
|
||||
this.searchBtn = btn;
|
||||
});
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.closeCallback?.();
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
66
packages/obsidian/src/modals/MediaDbSearchResultModal.ts
Normal file
66
packages/obsidian/src/modals/MediaDbSearchResultModal.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import type MediaDbPlugin from 'packages/obsidian/src/main';
|
||||
import { SelectModal } from 'packages/obsidian/src/modals/SelectModal';
|
||||
import type { MediaTypeModel } from 'packages/obsidian/src/models/MediaTypeModel';
|
||||
import type { SelectModalData, SelectModalOptions } from 'packages/obsidian/src/utils/ModalHelper';
|
||||
import { SELECTMODALOPTIONSDEFAULT } from 'packages/obsidian/src/utils/ModalHelper';
|
||||
|
||||
export class MediaDbSearchResultModal extends SelectModal<MediaTypeModel> {
|
||||
plugin: MediaDbPlugin;
|
||||
|
||||
busy: boolean;
|
||||
sendCallback: boolean;
|
||||
|
||||
submitCallback?: (res: SelectModalData) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
skipCallback?: () => void;
|
||||
submitButtonText: string;
|
||||
|
||||
constructor(plugin: MediaDbPlugin, selectModalOptions: SelectModalOptions) {
|
||||
selectModalOptions = Object.assign({}, SELECTMODALOPTIONSDEFAULT, selectModalOptions);
|
||||
super(plugin.app, selectModalOptions.elements ?? [], selectModalOptions.multiSelect);
|
||||
this.plugin = plugin;
|
||||
this.title = selectModalOptions.modalTitle ?? '';
|
||||
this.description = selectModalOptions.description ?? 'Select one or multiple search results.';
|
||||
this.addSkipButton = selectModalOptions.skipButton ?? false;
|
||||
this.submitButtonText = selectModalOptions.submitButtonText ?? 'Ok';
|
||||
this.busy = false;
|
||||
this.sendCallback = false;
|
||||
}
|
||||
|
||||
setSubmitCb(submitCallback: (res: SelectModalData) => void): void {
|
||||
this.submitCallback = submitCallback;
|
||||
}
|
||||
|
||||
setCloseCb(closeCallback: (err?: Error) => void): void {
|
||||
this.closeCallback = closeCallback;
|
||||
}
|
||||
|
||||
setSkipCallback(skipCallback: () => void): void {
|
||||
this.skipCallback = skipCallback;
|
||||
}
|
||||
|
||||
// Renders each suggestion item.
|
||||
renderElement(item: MediaTypeModel, el: HTMLElement): void {
|
||||
el.createDiv({ 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(): void {
|
||||
if (!this.busy) {
|
||||
this.busy = true;
|
||||
this.submitButton?.setButtonText('Creating entry...');
|
||||
this.submitCallback?.({ selected: this.selectModalElements.filter(x => x.isActive()).map(x => x.value) });
|
||||
}
|
||||
}
|
||||
|
||||
skip(): void {
|
||||
this.skipButton?.setButtonText('Skipping...');
|
||||
this.skipCallback?.();
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.closeCallback?.();
|
||||
}
|
||||
}
|
||||
50
packages/obsidian/src/modals/MediaDbSeasonSelectModal.ts
Normal file
50
packages/obsidian/src/modals/MediaDbSeasonSelectModal.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import type MediaDbPlugin from 'packages/obsidian/src/main';
|
||||
import { SelectModal } from 'packages/obsidian/src/modals/SelectModal';
|
||||
|
||||
export interface SeasonSelectModalElement {
|
||||
season_number: number;
|
||||
name: string;
|
||||
air_date?: string;
|
||||
poster_path?: string;
|
||||
}
|
||||
|
||||
export class MediaDbSeasonSelectModal extends SelectModal<SeasonSelectModalElement> {
|
||||
plugin: MediaDbPlugin;
|
||||
submitCallback?: (selectedSeasons: SeasonSelectModalElement[]) => void;
|
||||
closeCallback?: (err?: Error) => void;
|
||||
seriesName?: string;
|
||||
|
||||
constructor(plugin: MediaDbPlugin, seasons: SeasonSelectModalElement[], multiSelect = true, seriesName?: string) {
|
||||
super(plugin.app, seasons, multiSelect);
|
||||
this.plugin = plugin;
|
||||
this.seriesName = seriesName;
|
||||
this.title = `Select seasons for${seriesName ? ` ${seriesName}` : ''}`;
|
||||
this.description = 'Select one or more seasons to create notes for.';
|
||||
this.submitButtonText = 'Create Entry';
|
||||
}
|
||||
|
||||
renderElement(season: SeasonSelectModalElement, el: HTMLElement): void {
|
||||
el.createDiv({ text: `${season.name}` });
|
||||
if (season.air_date) {
|
||||
el.createEl('small', { text: `Air date: ${season.air_date}` });
|
||||
}
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
const selected = this.selectModalElements.filter(x => x.isActive()).map(x => x.value);
|
||||
this.submitCallback?.(selected);
|
||||
this.close();
|
||||
}
|
||||
|
||||
skip(): void {
|
||||
this.close();
|
||||
}
|
||||
|
||||
setSubmitCb(cb: (selectedSeasons: SeasonSelectModalElement[]) => void): void {
|
||||
this.submitCallback = cb;
|
||||
}
|
||||
|
||||
setCloseCb(cb: (err?: Error) => void): void {
|
||||
this.closeCallback = cb;
|
||||
}
|
||||
}
|
||||
176
packages/obsidian/src/modals/SelectModal.ts
Normal file
176
packages/obsidian/src/modals/SelectModal.ts
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
import type { App, ButtonComponent } from 'obsidian';
|
||||
import { Modal, Setting } from 'obsidian';
|
||||
import { SelectModalElement } from 'packages/obsidian/src/modals/SelectModalElement';
|
||||
import { mod } from 'packages/obsidian/src/utils/Utils';
|
||||
|
||||
export abstract class SelectModal<T> extends Modal {
|
||||
allowMultiSelect: boolean;
|
||||
|
||||
title: string;
|
||||
description: string;
|
||||
addSkipButton: boolean;
|
||||
cancelButton?: ButtonComponent;
|
||||
skipButton?: ButtonComponent;
|
||||
submitButton?: ButtonComponent;
|
||||
submitButtonText: string;
|
||||
|
||||
elementWrapper?: HTMLDivElement;
|
||||
|
||||
elements: T[];
|
||||
selectModalElements: SelectModalElement<T>[];
|
||||
|
||||
protected constructor(app: App, elements: T[], allowMultiSelect: boolean = true) {
|
||||
super(app);
|
||||
this.allowMultiSelect = allowMultiSelect;
|
||||
|
||||
this.title = '';
|
||||
this.description = '';
|
||||
this.addSkipButton = false;
|
||||
this.submitButtonText = 'Ok';
|
||||
this.cancelButton = undefined;
|
||||
this.skipButton = undefined;
|
||||
this.submitButton = undefined;
|
||||
|
||||
this.elementWrapper = undefined;
|
||||
|
||||
this.elements = elements;
|
||||
this.selectModalElements = [];
|
||||
|
||||
this.scope.register([], 'ArrowUp', evt => {
|
||||
this.highlightUp();
|
||||
evt.preventDefault();
|
||||
});
|
||||
this.scope.register([], 'ArrowDown', evt => {
|
||||
this.highlightDown();
|
||||
evt.preventDefault();
|
||||
});
|
||||
this.scope.register([], 'ArrowRight', () => {
|
||||
this.activateHighlighted();
|
||||
});
|
||||
this.scope.register([], ' ', evt => {
|
||||
if (this.elementWrapper && this.elementWrapper === activeDocument.activeElement) {
|
||||
this.activateHighlighted();
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
this.scope.register([], 'Enter', () => this.submit());
|
||||
}
|
||||
|
||||
abstract renderElement(value: T, el: HTMLElement): void;
|
||||
|
||||
abstract submit(): void;
|
||||
|
||||
abstract skip(): void;
|
||||
|
||||
disableAllOtherElements(elementId: number): void {
|
||||
for (const selectModalElement of this.selectModalElements) {
|
||||
if (selectModalElement.id !== elementId) {
|
||||
selectModalElement.setActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deHighlightAllOtherElements(elementId: number): void {
|
||||
for (const selectModalElement of this.selectModalElements) {
|
||||
if (selectModalElement.id !== elementId) {
|
||||
selectModalElement.setHighlighted(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
const { contentEl, titleEl } = this;
|
||||
|
||||
titleEl.createEl('h2', { text: this.title });
|
||||
contentEl.addClass('media-db-plugin-select-modal');
|
||||
contentEl.createEl('p', { text: this.description });
|
||||
|
||||
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, this.elementWrapper, i, this, false);
|
||||
|
||||
this.selectModalElements.push(selectModalElement);
|
||||
|
||||
this.renderElement(element, selectModalElement.element);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
this.selectModalElements.first()?.element.scrollIntoView();
|
||||
|
||||
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;
|
||||
});
|
||||
if (this.addSkipButton) {
|
||||
bottomSettingRow.addButton(btn => {
|
||||
btn.setButtonText('Skip');
|
||||
btn.onClick(() => this.skip());
|
||||
btn.buttonEl.addClass('media-db-plugin-button');
|
||||
this.skipButton = btn;
|
||||
});
|
||||
}
|
||||
bottomSettingRow.addButton(btn => {
|
||||
btn.setButtonText(this.submitButtonText);
|
||||
btn.setCta();
|
||||
btn.onClick(() => this.submit());
|
||||
btn.buttonEl.addClass('media-db-plugin-button');
|
||||
this.submitButton = btn;
|
||||
});
|
||||
}
|
||||
|
||||
activateHighlighted(): void {
|
||||
for (const selectModalElement of this.selectModalElements) {
|
||||
if (selectModalElement.isHighlighted()) {
|
||||
selectModalElement.setActive(!selectModalElement.isActive());
|
||||
if (!this.allowMultiSelect) {
|
||||
this.disableAllOtherElements(selectModalElement.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlightUp(): void {
|
||||
for (const selectModalElement of this.selectModalElements) {
|
||||
if (selectModalElement.isHighlighted()) {
|
||||
this.getPreviousSelectModalElement(selectModalElement).setHighlighted(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// nothing is highlighted
|
||||
this.selectModalElements.last()?.setHighlighted(true);
|
||||
}
|
||||
|
||||
highlightDown(): void {
|
||||
for (const selectModalElement of this.selectModalElements) {
|
||||
if (selectModalElement.isHighlighted()) {
|
||||
this.getNextSelectModalElement(selectModalElement).setHighlighted(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// nothing is highlighted
|
||||
this.selectModalElements.first()?.setHighlighted(true);
|
||||
}
|
||||
|
||||
private getNextSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> {
|
||||
let nextId = selectModalElement.id + 1;
|
||||
nextId = mod(nextId, this.selectModalElements.length);
|
||||
|
||||
return this.selectModalElements.find(x => x.id === nextId)!;
|
||||
}
|
||||
|
||||
private getPreviousSelectModalElement(selectModalElement: SelectModalElement<T>): SelectModalElement<T> {
|
||||
let nextId = selectModalElement.id - 1;
|
||||
nextId = mod(nextId, this.selectModalElements.length);
|
||||
|
||||
return this.selectModalElements.find(x => x.id === nextId)!;
|
||||
}
|
||||
}
|
||||
88
packages/obsidian/src/modals/SelectModalElement.ts
Normal file
88
packages/obsidian/src/modals/SelectModalElement.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import type { SelectModal } from 'packages/obsidian/src/modals/SelectModal';
|
||||
|
||||
export class SelectModalElement<T> {
|
||||
selectModal: SelectModal<T>;
|
||||
value: T;
|
||||
readonly id: number;
|
||||
element: HTMLDivElement;
|
||||
cssClass: string;
|
||||
activeClass: string;
|
||||
hoverClass: string;
|
||||
private active: boolean;
|
||||
private highlighted: boolean;
|
||||
|
||||
constructor(value: T, parentElement: HTMLElement, id: number, selectModal: SelectModal<T>, active: boolean = false) {
|
||||
this.value = value;
|
||||
this.id = id;
|
||||
this.active = active;
|
||||
this.selectModal = selectModal;
|
||||
|
||||
this.cssClass = 'media-db-plugin-select-element';
|
||||
this.activeClass = 'media-db-plugin-select-element-selected';
|
||||
this.hoverClass = 'media-db-plugin-select-element-hover';
|
||||
|
||||
this.element = parentElement.createDiv({ cls: this.cssClass });
|
||||
this.element.id = this.getHTMLId();
|
||||
this.element.on('click', '#' + this.getHTMLId(), () => {
|
||||
this.setActive(!this.active);
|
||||
if (!this.selectModal.allowMultiSelect) {
|
||||
this.selectModal.disableAllOtherElements(this.id);
|
||||
}
|
||||
});
|
||||
this.element.on('mouseenter', '#' + this.getHTMLId(), () => {
|
||||
this.setHighlighted(true);
|
||||
});
|
||||
this.element.on('mouseleave', '#' + this.getHTMLId(), () => {
|
||||
this.setHighlighted(false);
|
||||
});
|
||||
|
||||
this.highlighted = false;
|
||||
}
|
||||
|
||||
getHTMLId(): string {
|
||||
return `media-db-plugin-select-element-${this.id}`;
|
||||
}
|
||||
|
||||
isHighlighted(): boolean {
|
||||
return this.highlighted;
|
||||
}
|
||||
|
||||
setHighlighted(value: boolean): void {
|
||||
this.highlighted = value;
|
||||
if (this.highlighted) {
|
||||
this.addClass(this.hoverClass);
|
||||
this.selectModal.deHighlightAllOtherElements(this.id);
|
||||
} else {
|
||||
this.removeClass(this.hoverClass);
|
||||
}
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
setActive(active: boolean): void {
|
||||
this.active = active;
|
||||
this.update();
|
||||
}
|
||||
|
||||
update(): void {
|
||||
if (this.active) {
|
||||
this.addClass(this.activeClass);
|
||||
} else {
|
||||
this.removeClass(this.activeClass);
|
||||
}
|
||||
}
|
||||
|
||||
addClass(cssClass: string): void {
|
||||
if (!this.element.hasClass(cssClass)) {
|
||||
this.element.addClass(cssClass);
|
||||
}
|
||||
}
|
||||
|
||||
removeClass(cssClass: string): void {
|
||||
if (this.element.hasClass(cssClass)) {
|
||||
this.element.removeClass(cssClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue