obsidian#FuzzySuggestModal TypeScript Examples

The following examples show how to use obsidian#FuzzySuggestModal. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: review-deck.ts    From obsidian-spaced-repetition with MIT License 6 votes vote down vote up
export class ReviewDeckSelectionModal extends FuzzySuggestModal<string> {
    public deckKeys: string[] = [];
    public submitCallback: (deckKey: string) => void;

    constructor(app: App, deckKeys: string[]) {
        super(app);
        this.deckKeys = deckKeys;
    }

    getItems(): string[] {
        return this.deckKeys;
    }

    getItemText(item: string): string {
        return item;
    }

    onChooseItem(deckKey: string, _: MouseEvent | KeyboardEvent): void {
        this.close();
        this.submitCallback(deckKey);
    }
}
Example #2
Source File: Modal.ts    From obsidian-chartsview-plugin with MIT License 6 votes vote down vote up
export class ChartTemplateSuggestModal extends FuzzySuggestModal<ChartTemplateType> {

    constructor(app: App, private editor: Editor) {
        super(app);
    }

    getItems(): ChartTemplateType[] {
        return Object.entries(ChartTemplateType);
    }

    getItemText(item: ChartTemplateType) {
        return item[0];
    }

    renderSuggestion(item: FuzzyMatch<ChartTemplateType>, el: HTMLElement): void {
        const div = createDiv({ cls: "chartsview-thumbnail" });
        const type = ChartTemplateType[item.item[0] as keyof typeof ChartTemplateType];
        const img = createEl("img", {
            attr: {
                src: ChartThumbnailMapping[type]
            }
        });
        div.appendChild(img);
        el.appendChild(div);
        el.addClass("chartsview-thumbnail-container");
        super.renderSuggestion(item, el);
    }

    onChooseItem(item: ChartTemplateType) {
        insertEditor(
            this.editor,
            Buffer.from(item[1], "base64").toString("utf8")
        );
    }
}
Example #3
Source File: synonymProviderChooser.ts    From obsidian-dictionary with GNU Affero General Public License v3.0 6 votes vote down vote up
export default class SynonymProviderChooser extends FuzzySuggestModal<string>{
    plugin: DictionaryPlugin;
    available: string[] = [];

    constructor(app: App, plugin: DictionaryPlugin) {
        super(app);
        this.plugin = plugin;
        for(let i = 0; i < this.plugin.manager.synonymProvider.length; i++){
            const api = this.plugin.manager.synonymProvider[i];
            if (api.supportedLanguages.contains(this.plugin.settings.defaultLanguage)) {
                this.available.push(api.name);
            }
        }
        this.setPlaceholder(t("Choose a Synonym Provider Service"));
    }

    onOpen(): void {
        if (this.available.length <= 1) {
            this.onChooseItem(this.available.first() ?? null);
        }
        super.onOpen();
    }

    getItems(): string[] {
        return this.available;
    }

    getItemText(item: string): string {
        return item;
    }

    async onChooseItem(item: string): Promise<void> {
        const lang = this.plugin.settings.defaultLanguage;
        this.plugin.settings.apiSettings[lang].synonymApiName = item;
        await this.plugin.saveSettings();
        this.close();
    }
}
Example #4
Source File: choiceSuggester.ts    From quickadd with MIT License 6 votes vote down vote up
export default class ChoiceSuggester extends FuzzySuggestModal<IChoice> {
    private choiceExecutor: IChoiceExecutor = new ChoiceExecutor(this.app, this.plugin);

    public static Open(plugin: QuickAdd, choices: IChoice[], choiceExecutor?: IChoiceExecutor) {
        new ChoiceSuggester(plugin, choices, choiceExecutor).open();
    }

    constructor(private plugin: QuickAdd, private choices: IChoice[], choiceExecutor?: IChoiceExecutor) {
        super(plugin.app);
        if (choiceExecutor) this.choiceExecutor = choiceExecutor;
    }

    getItemText(item: IChoice): string {
        return item.name;
    }

    getItems(): IChoice[] {
        return this.choices;
    }

    async onChooseItem(item: IChoice, evt: MouseEvent | KeyboardEvent): Promise<void> {
        if (item.type === ChoiceType.Multi)
            this.onChooseMultiType(<IMultiChoice>item);
        else
            await this.choiceExecutor.execute(item);
    }

    private onChooseMultiType(multi: IMultiChoice) {
        const choices = [...multi.choices];

        if (multi.name != "← Back")
            choices.push(new MultiChoice("← Back").addChoices(this.choices))

        ChoiceSuggester.Open(this.plugin, choices);
    }


}
Example #5
Source File: languageChooser.ts    From obsidian-dictionary with GNU Affero General Public License v3.0 6 votes vote down vote up
export default class LanguageChooser extends FuzzySuggestModal<string>{
    plugin: DictionaryPlugin;

    constructor(app: App, plugin: DictionaryPlugin) {
        super(app);
        this.plugin = plugin;
        this.setPlaceholder(t("Choose a Language"));
    }

    getItems(): string[] {
        const items: string[] = [];
        for (const lang in LANGUAGES) {
            items.push(lang);
        }
        return items;
    }

    getItemText(item: string): string {
        if(item == this.plugin.settings.defaultLanguage) {
            return LANGUAGES[item] + ' ?';
        } else {
            return LANGUAGES[item];
        }
    }

    async onChooseItem(item: string): Promise<void> {
        this.plugin.settings.defaultLanguage = item as keyof typeof LANGUAGES;
        this.plugin.settings.normalLang = item as keyof typeof LANGUAGES;
        await this.plugin.saveSettings();
        this.close();
    }

}
Example #6
Source File: watcher.ts    From obsidian-fantasy-calendar with MIT License 6 votes vote down vote up
class CalendarPickerModal extends FuzzySuggestModal<Calendar> {
    chosen: Calendar;
    constructor(public plugin: FantasyCalendar) {
        super(plugin.app);
    }
    getItems() {
        return this.plugin.data.calendars;
    }
    getItemText(item: Calendar) {
        return item.name;
    }
    onChooseItem(item: Calendar, evt: MouseEvent | KeyboardEvent): void {
        this.chosen = item;
        this.close();
    }
}
Example #7
Source File: commandSuggester.ts    From obsidian-customizable-sidebar with MIT License 6 votes vote down vote up
export default class CommandSuggester extends FuzzySuggestModal<Command> {

	constructor(private plugin: CustomSidebarPlugin) {
		super(plugin.app);
	}

	getItems(): Command[] {
		//@ts-ignore
		return this.app.commands.listCommands();
	}

	getItemText(item: Command): string {
		return item.name;
	}

	async onChooseItem(item: Command, evt: MouseEvent | KeyboardEvent): Promise<void> {
		if (item.icon) {
			this.plugin.addRibbonIcon(item.icon ?? "", item.name, () => {
				//@ts-ignore
				this.app.commands.executeCommandById(item.id);
			})
			this.plugin.settings.sidebarCommands.push(item);
			await this.plugin.saveSettings();
			setTimeout(() => {
				dispatchEvent(new Event("CS-addedCommand"));
			}, 100);
		} else {
			new IconPicker(this.plugin, item).open()
		}
	}

}
Example #8
Source File: SuggesterModal.ts    From Templater with GNU Affero General Public License v3.0 5 votes vote down vote up
export class SuggesterModal<T> extends FuzzySuggestModal<T> {
    private resolve: (value: T) => void;
    private reject: (reason?: TemplaterError) => void;
    private submitted = false;

    constructor(
        app: App,
        private text_items: string[] | ((item: T) => string),
        private items: T[],
        placeholder: string,
        limit?: number
    ) {
        super(app);
        this.setPlaceholder(placeholder);
        this.limit = limit;
    }

    getItems(): T[] {
        return this.items;
    }

    onClose(): void {
        if (!this.submitted) {
            this.reject(new TemplaterError("Cancelled prompt"));
        }
    }

    selectSuggestion(
        value: FuzzyMatch<T>,
        evt: MouseEvent | KeyboardEvent
    ): void {
        this.submitted = true;
        this.close();
        this.onChooseSuggestion(value, evt);
    }

    getItemText(item: T): string {
        if (this.text_items instanceof Function) {
            return this.text_items(item);
        }
        return (
            this.text_items[this.items.indexOf(item)] || "Undefined Text Item"
        );
    }

    onChooseItem(item: T): void {
        this.resolve(item);
    }

    async openAndGetValue(
        resolve: (value: T) => void,
        reject: (reason?: TemplaterError) => void
    ): Promise<void> {
        this.resolve = resolve;
        this.reject = reject;
        this.open();
    }
}
Example #9
Source File: suggester.ts    From obsidian-initiative-tracker with GNU General Public License v3.0 5 votes vote down vote up
abstract class ElementSuggestionModal<T> extends FuzzySuggestModal<T> {
    items: T[] = [];
    suggestions: HTMLDivElement[];
    scope: Scope = new Scope();
    suggester: Suggester<FuzzyMatch<T>>;
    suggestEl: HTMLDivElement;
    promptEl: HTMLDivElement;
    emptyStateText: string = "No match found";
    limit: number = Infinity;
    filteredItems: FuzzyMatch<T>[] = [];
    constructor(
        app: App,
        inputEl: HTMLInputElement,
        suggestEl: HTMLDivElement
    ) {
        super(app);
        this.inputEl = inputEl;

        this.suggestEl = suggestEl.createDiv(/* "suggestion-container" */);

        this.contentEl = this.suggestEl.createDiv(/* "suggestion" */);

        this.suggester = new Suggester(this, this.contentEl, this.scope);

        this.scope.register([], "Escape", this.close.bind(this));

        this.inputEl.addEventListener("input", this._onInputChanged.bind(this));
        this.inputEl.addEventListener("focus", this._onInputChanged.bind(this));
        this.inputEl.addEventListener("blur", this.close.bind(this));
        this.suggestEl.on(
            "mousedown",
            ".suggestion-container",
            (event: MouseEvent) => {
                event.preventDefault();
            }
        );
    }
    empty() {
        this.suggester.empty();
    }
    _onInputChanged(): void {
        const inputStr = this.inputEl.value;
        this.filteredItems = this.getSuggestions(inputStr);
        if (this.filteredItems.length > 0) {
            this.suggester.setSuggestions(
                this.filteredItems.slice(0, this.limit)
            );
        } else {
            this.onNoSuggestion();
        }
        this.onInputChanged();
        this.open();
    }
    onInputChanged(): void {}
    onNoSuggestion() {
        this.empty();
        this.renderSuggestion(
            null,
            this.contentEl.createDiv(/* "suggestion-item" */)
        );
    }
    open(): void {}

    close(): void {}
    createPrompt(prompts: HTMLSpanElement[]) {
        if (!this.promptEl)
            this.promptEl = this.suggestEl.createDiv("prompt-instructions");
        let prompt = this.promptEl.createDiv("prompt-instruction");
        for (let p of prompts) {
            prompt.appendChild(p);
        }
    }
    abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
    abstract getItemText(arg: T): string;
    abstract getItems(): T[];
}
Example #10
Source File: definitionProviderChooser.ts    From obsidian-dictionary with GNU Affero General Public License v3.0 5 votes vote down vote up
export default class DefinitionProviderChooser extends FuzzySuggestModal<string>{
    plugin: DictionaryPlugin;
    available: string[] = [];

    constructor(app: App, plugin: DictionaryPlugin) {
        super(app);
        this.plugin = plugin;
        for(let i = 0; i < this.plugin.manager.definitionProvider.length; i++){
            const api = this.plugin.manager.definitionProvider[i];
            if (api.supportedLanguages.contains(this.plugin.settings.defaultLanguage)) {
                this.available.push(api.name);
            }
        }
        this.setPlaceholder(t("Choose a Definition Provider Service"));
    }

    onOpen(): void {
        if (this.available.length <= 1) {
            this.onChooseItem(this.available.first() ?? null);
        }
        super.onOpen();
    }

    getItems(): string[] {
        return this.available;
    }

    getItemText(item: string): string {
        return item;
    }

    async onChooseItem(item: string): Promise<void> {
        const lang = this.plugin.settings.defaultLanguage;
        this.plugin.settings.apiSettings[lang].definitionApiName = item;
        await this.plugin.saveSettings();
        this.close();
        new SynonymProviderChooser(this.app, this.plugin).open();
    }

}
Example #11
Source File: iconPicker.ts    From obsidian-customizable-sidebar with MIT License 5 votes vote down vote up
export default class IconPicker extends FuzzySuggestModal<string>{
	plugin: CustomSidebarPlugin;
	command: Command;
	editMode: boolean;

	constructor(plugin: CustomSidebarPlugin, command: Command, editMode = false) {
		super(plugin.app);
		this.plugin = plugin;
		this.command = command;
		this.editMode = editMode;
		this.setPlaceholder("Pick an icon");
	}

	private cap(string: string): string {
		const words = string.split(" ");

		return words.map((word) => {
			return word[0].toUpperCase() + word.substring(1);
		}).join(" ");
	}

	getItems(): string[] {
		return this.plugin.iconList;
	}

	getItemText(item: string): string {
		return this.cap(item.replace("feather-", "").replace(/-/ig, " "));
	}

	renderSuggestion(item: FuzzyMatch<string>, el: HTMLElement): void {
		el.addClass("CS-icon-container");
		const div = createDiv({ cls: "CS-icon" });
		el.appendChild(div);
		setIcon(div, item.item);
		super.renderSuggestion(item, el);
	}

	async onChooseItem(item: string): Promise<void> {
		this.command.icon = item;

		if (!this.editMode) {
			this.plugin.addRibbonIcon(item, this.command.name, () => {
				//@ts-ignore
				this.app.commands.executeCommandById(this.command.id);
			})
			this.plugin.settings.sidebarCommands.push(this.command);
		} else {
			new Notice("You will need to restart Obsidian for the changes to take effect.")
		}

		await this.plugin.saveSettings();

		setTimeout(() => {
			dispatchEvent(new Event("CS-addedCommand"));
		}, 100);
	}

}
Example #12
Source File: IconModal.ts    From obsidian-banners with MIT License 5 votes vote down vote up
export default class IconModal extends FuzzySuggestModal<IEmojiPair> {
  plugin: BannersPlugin;
  metaManager: MetaManager;
  targetFile: TFile;
  emojis: IEmojiPair[];

  constructor(plugin: BannersPlugin, file: TFile) {
    super(plugin.app);
    this.plugin = plugin;
    this.metaManager = plugin.metaManager;

    this.containerEl.addClass('banner-icon-modal');
    this.targetFile = file;
    this.emojis = Object.entries(allEmojis).map(([code, emoji]) => ({ code, emoji }));
    this.limit = 50;
    this.setPlaceholder('Pick an emoji to use as an icon');
  }

  getItems(): IEmojiPair[] {
    return this.inputEl.value.length ? this.emojis : [];
  }

  getItemText(item: IEmojiPair): string {
    return item.code;
  }

  getSuggestions(query: string): FuzzyMatch<IEmojiPair>[] {
    const emojiText = query.match(EMOJI_REGEX)?.join('');
    return emojiText ? ([{
      item: { code: 'Paste inputted emoji(s)', emoji: emojiText },
      match: { score: 1, matches: [] }
    }]) : super.getSuggestions(query);
  }

  renderSuggestion(match: FuzzyMatch<IEmojiPair>, el: HTMLElement): void {
    super.renderSuggestion(match, el);
    const { useTwemoji } = this.plugin.settings;
    const { emoji } = match.item;
    const html = useTwemoji ? twemoji.parse(emoji) : `<span class="regular-emoji">${emoji} </span>`;
    el.insertAdjacentHTML('afterbegin', html);
  }

  async onChooseItem(item: IEmojiPair) {
    await this.metaManager.upsertBannerData(this.targetFile, { icon: item.emoji });
  }
}
Example #13
Source File: genericSuggester.ts    From quickadd with MIT License 5 votes vote down vote up
export default class GenericSuggester extends FuzzySuggestModal<string>{
    private resolvePromise: (value: string) => void;
    private rejectPromise: (reason?: any) => void;
    public promise: Promise<string>;
    private resolved: boolean;

    public static Suggest(app: App, displayItems: string[], items: string[]) {
        const newSuggester = new GenericSuggester(app, displayItems, items);
        return newSuggester.promise;
    }

    public constructor(app: App, private displayItems: string[], private items: string[]) {
        super(app);

        this.promise = new Promise<string>(
            (resolve, reject) => {(this.resolvePromise = resolve); (this.rejectPromise = reject)}
        );

        this.open();
    }

    getItemText(item: string): string {
        return this.displayItems[this.items.indexOf(item)];
    }

    getItems(): string[] {
        return this.items;
    }

    selectSuggestion(value: FuzzyMatch<string>, evt: MouseEvent | KeyboardEvent) {
        this.resolved = true;
        super.selectSuggestion(value, evt);
    }

    onChooseItem(item: string, evt: MouseEvent | KeyboardEvent): void {
        this.resolved = true;
        this.resolvePromise(item);
    }

    onClose() {
        super.onClose();

        if (!this.resolved)
            this.rejectPromise("no input given.");
    }
}
Example #14
Source File: FuzzySuggester.ts    From Templater with GNU Affero General Public License v3.0 5 votes vote down vote up
export class FuzzySuggester extends FuzzySuggestModal<TFile> {
    public app: App;
    private plugin: TemplaterPlugin;
    private open_mode: OpenMode;
    private creation_folder: TFolder;

    constructor(app: App, plugin: TemplaterPlugin) {
        super(app);
        this.app = app;
        this.plugin = plugin;
        this.setPlaceholder("Type name of a template...");
    }

    getItems(): TFile[] {
        if (!this.plugin.settings.templates_folder) {
            return this.app.vault.getMarkdownFiles();
        }
        const files = errorWrapperSync(
            () =>
                get_tfiles_from_folder(
                    this.app,
                    this.plugin.settings.templates_folder
                ),
            `Couldn't retrieve template files from templates folder ${this.plugin.settings.templates_folder}`
        );
        if (!files) {
            return [];
        }
        return files;
    }

    getItemText(item: TFile): string {
        return item.basename;
    }

    onChooseItem(item: TFile): void {
        switch (this.open_mode) {
            case OpenMode.InsertTemplate:
                this.plugin.templater.append_template_to_active_file(item);
                break;
            case OpenMode.CreateNoteTemplate:
                this.plugin.templater.create_new_note_from_template(
                    item,
                    this.creation_folder
                );
                break;
        }
    }

    start(): void {
        try {
            this.open();
        } catch (e) {
            log_error(e);
        }
    }

    insert_template(): void {
        this.open_mode = OpenMode.InsertTemplate;
        this.start();
    }

    create_new_note_from_template(folder?: TFolder): void {
        this.creation_folder = folder;
        this.open_mode = OpenMode.CreateNoteTemplate;
        this.start();
    }
}
Example #15
Source File: main.ts    From luhman-obsidian-plugin with GNU General Public License v3.0 5 votes vote down vote up
class ZettelSuggester extends FuzzySuggestModal<string> {
  private titles: Map<string, TFile>;
  private completion: (file: TFile) => void;
  private initialQuery: string;

  constructor(
    app: App,
    titles: Map<string, TFile>,
    search: string | undefined,
    completion: (file: TFile) => void
  ) {
    super(app);
    this.initialQuery = search ?? "";
    this.titles = titles;
    this.completion = completion;
    this.emptyStateText = "No zettels found";
    this.setPlaceholder("Search for a zettel...");
    console.log(this.initialQuery);
  }

  onOpen() {
    super.onOpen();
    this.inputEl.value = this.initialQuery;
    var event = new Event("input");
    this.inputEl.dispatchEvent(event);
  }

  getItems(): string[] {
    return Array.from(this.titles.keys()).sort();
  }

  getItemText(item: string): string {
    return item;
  }

  renderSuggestion(value: FuzzyMatch<string>, el: HTMLElement) {
    el.setText(value.item);

    let matches = value.match.matches;
    if (matches == null || matches.length == 0) {
      return;
    }
    let start = matches[0][0];
    let end = matches[0][1];

    let range = new Range();

    let text = el.firstChild;
    if (text == null) {
      return;
    }

    range.setStart(text, start);
    range.setEnd(text, end);
    range.surroundContents(document.createElement("b"));
  }

  onChooseItem(item: string, evt: MouseEvent | KeyboardEvent) {
    this.completion(this.titles.get(item)!);
  }
}
Example #16
Source File: LocalImageModal.ts    From obsidian-banners with MIT License 4 votes vote down vote up
export default class LocalImageModal extends FuzzySuggestModal<TFile> {
  plugin: BannersPlugin;
  vault: Vault;
  metadataCache: MetadataCache;
  metaManager: MetaManager;
  targetFile: TFile;

  constructor(plugin: BannersPlugin, file: TFile) {
    super(plugin.app);
    this.plugin = plugin;
    this.vault = plugin.app.vault;
    this.metadataCache = plugin.app.metadataCache;
    this.metaManager = plugin.metaManager;

    this.containerEl.addClass('banner-local-image-modal');
    this.targetFile = file;
    this.limit = this.plugin.getSettingValue('localSuggestionsLimit');
    this.setPlaceholder('Pick an image to use as a banner');
  }

  getItems(): TFile[] {
    const path = this.plugin.getSettingValue('bannersFolder');

    // Search all files if using the default setting
    if (path === DEFAULT_VALUES.bannersFolder) {
      return this.vault.getFiles().filter(f => IMAGE_FORMATS.includes(f.extension));
    }

    // Only search the designated folder when specified
    const folder = this.vault.getAbstractFileByPath(path);
    if (!folder || !(folder instanceof TFolder)) {
      new Notice(createFragment(frag => {
        frag.appendText('ERROR! Make sure that you set the ');
        frag.createEl('b', { text: 'Banners folder' });
        frag.appendText(' to a valid folder in the settings.');
      }), 7000);
      this.close();
      return [];
    }
    return this.getImagesInFolder(folder);
  }

  getItemText(item: TFile): string {
    return item.path;
  }

  renderSuggestion(match: FuzzyMatch<TFile>, el: HTMLElement) {
    super.renderSuggestion(match, el);

    const { showPreviewInLocalModal } = this.plugin.settings;
    if (showPreviewInLocalModal) {
      const content = el.innerHTML;
      el.addClass('banner-suggestion-item');
      el.innerHTML = html`
        <p class="suggestion-text">${content}</p>
        <div class="suggestion-image-wrapper">
          <img src="${this.vault.getResourcePath(match.item)}" />
        </div>
      `;
    }
  }

  async onChooseItem(image: TFile) {
    const link = this.metadataCache.fileToLinktext(image, this.targetFile.path);
    await this.metaManager.upsertBannerData(this.targetFile, { src: `"![[${link}]]"` });
  }

  private getImagesInFolder(folder: TFolder): TFile[] {
    const files: TFile[] = [];
    folder.children.forEach((abFile) => {
      if (abFile instanceof TFolder) {
        files.push(...this.getImagesInFolder(folder));
      }
      const file = abFile as TFile;
      if (IMAGE_FORMATS.includes(file.extension)) {
        files.push(file);
      }
    });
    return files;
  }
}
Example #17
Source File: modals.ts    From obsidian-citation-plugin with MIT License 4 votes vote down vote up
class SearchModal extends FuzzySuggestModal<Entry> {
  plugin: CitationPlugin;
  limit = 50;

  loadingEl: HTMLElement;
  loadingCheckerHandle: NodeJS.Timeout;
  // How frequently should we check whether the library is still loading?
  loadingCheckInterval = 250;

  constructor(app: App, plugin: CitationPlugin) {
    super(app);
    this.plugin = plugin;

    this.resultContainerEl.addClass('zoteroModalResults');

    this.inputEl.setAttribute('spellcheck', 'false');

    this.loadingEl = this.resultContainerEl.parentElement.createEl('div', {
      cls: 'zoteroModalLoading',
    });
    this.loadingEl.createEl('div', { cls: 'zoteroModalLoadingAnimation' });
    this.loadingEl.createEl('p', {
      text: 'Loading citation database. Please wait...',
    });
  }

  onOpen() {
    super.onOpen();

    this.checkLoading();
    this.loadingCheckerHandle = setInterval(() => {
      this.checkLoading();
    }, this.loadingCheckInterval);

    // Don't immediately register keyevent listeners. If the modal was triggered
    // by an "Enter" keystroke (e.g. via the Obsidian command dialog), this event
    // will be received here erroneously.
    setTimeout(() => {
      this.inputEl.addEventListener('keydown', (ev) => this.onInputKeydown(ev));
      this.inputEl.addEventListener('keyup', (ev) => this.onInputKeyup(ev));
    }, 200);
  }

  onClose() {
    if (this.loadingCheckerHandle) {
      clearInterval(this.loadingCheckerHandle);
    }
  }

  /**
   * Check if the library is currently being loaded. If so, display animation
   * and disable input. Otherwise hide animation and enable input.
   */
  checkLoading() {
    if (this.plugin.isLibraryLoading) {
      this.loadingEl.removeClass('d-none');
      this.inputEl.disabled = true;
      this.resultContainerEl.empty();
    } else {
      this.loadingEl.addClass('d-none');
      this.inputEl.disabled = false;
      this.inputEl.focus();
    }
  }

  getItems(): Entry[] {
    if (this.plugin.isLibraryLoading) {
      return [];
    }

    return Object.values(this.plugin.library.entries);
  }

  getItemText(item: Entry): string {
    return `${item.title} ${item.authorString} ${item.year}`;
  }

  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  onChooseItem(item: Entry, evt: MouseEvent | KeyboardEvent): void {
    this.plugin.openLiteratureNote(item.id, false).catch(console.error);
  }

  renderSuggestion(match: FuzzyMatch<Entry>, el: HTMLElement): void {
    el.empty();
    const entry = match.item;
    const entryTitle = entry.title || '';

    const container = el.createEl('div', { cls: 'zoteroResult' });
    const titleEl = container.createEl('span', {
      cls: 'zoteroTitle',
    });
    container.createEl('span', { cls: 'zoteroCitekey', text: entry.id });

    const authorsCls = entry.authorString
      ? 'zoteroAuthors'
      : 'zoteroAuthors zoteroAuthorsEmpty';
    const authorsEl = container.createEl('span', {
      cls: authorsCls,
    });

    // Prepare to highlight string matches for each part of the search item.
    // Compute offsets of each rendered element's content within the string
    // returned by `getItemText`.
    const allMatches = match.match.matches;
    const authorStringOffset = 1 + entryTitle.length;

    // Filter a match list to contain only the relevant matches for a given
    // substring, and with match indices shifted relative to the start of that
    // substring
    const shiftMatches = (
      matches: SearchMatches,
      start: number,
      end: number,
    ) => {
      return matches
        .map((match: SearchMatchPart) => {
          const [matchStart, matchEnd] = match;
          return [
            matchStart - start,
            Math.min(matchEnd - start, end),
          ] as SearchMatchPart;
        })
        .filter((match: SearchMatchPart) => {
          const [matchStart, matchEnd] = match;
          return matchStart >= 0;
        });
    };

    // Now highlight matched strings within each element
    renderMatches(
      titleEl,
      entryTitle,
      shiftMatches(allMatches, 0, entryTitle.length),
    );
    if (entry.authorString) {
      renderMatches(
        authorsEl,
        entry.authorString,
        shiftMatches(
          allMatches,
          authorStringOffset,
          authorStringOffset + entry.authorString.length,
        ),
      );
    }
  }

  onInputKeydown(ev: KeyboardEvent) {
    if (ev.key == 'Tab') {
      ev.preventDefault();
    }
  }

  onInputKeyup(ev: KeyboardEvent) {
    if (ev.key == 'Enter' || ev.key == 'Tab') {
      ((this as unknown) as FuzzySuggestModalExt<Entry>).chooser.useSelectedItem(
        ev,
      );
    }
  }
}
Example #18
Source File: suggester.ts    From obsidian-fantasy-calendar with MIT License 4 votes vote down vote up
export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
    items: T[] = [];
    suggestions: HTMLDivElement[];
    popper: PopperInstance;
    scope: Scope = new Scope();
    suggester: Suggester<FuzzyMatch<T>>;
    suggestEl: HTMLDivElement;
    promptEl: HTMLDivElement;
    emptyStateText: string = "No match found";
    limit: number = 100;
    shouldNotOpen: boolean;
    constructor(app: App, inputEl: HTMLInputElement, items: T[]) {
        super(app);
        this.inputEl = inputEl;
        this.items = items;

        this.suggestEl = createDiv("suggestion-container");

        this.contentEl = this.suggestEl.createDiv("suggestion");

        this.suggester = new Suggester(this, this.contentEl, this.scope);

        this.scope.register([], "Escape", this.onEscape.bind(this));

        this.inputEl.addEventListener("input", this.onInputChanged.bind(this));
        this.inputEl.addEventListener("focus", this.onFocus.bind(this));
        this.inputEl.addEventListener("blur", this.close.bind(this));
        this.suggestEl.on(
            "mousedown",
            ".suggestion-container",
            (event: MouseEvent) => {
                event.preventDefault();
            }
        );
    }
    empty() {
        this.suggester.empty();
    }
    onInputChanged(): void {
        if (this.shouldNotOpen) return;
        const inputStr = this.modifyInput(this.inputEl.value);
        const suggestions = this.getSuggestions(inputStr);
        if (suggestions.length > 0) {
            this.suggester.setSuggestions(suggestions.slice(0, this.limit));
        } else {
            this.onNoSuggestion();
        }
        this.open();
    }
    onFocus(): void {
        this.shouldNotOpen = false;
        this.onInputChanged();
    }
    modifyInput(input: string): string {
        return input;
    }
    onNoSuggestion() {
        this.empty();
        this.renderSuggestion(
            null,
            this.contentEl.createDiv("suggestion-item")
        );
    }
    open(): void {
        // TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
        this.app.keymap.pushScope(this.scope);

        document.body.appendChild(this.suggestEl);
        this.popper = createPopper(this.inputEl, this.suggestEl, {
            placement: "bottom-start",
            modifiers: [
                {
                    name: "offset",
                    options: {
                        offset: [0, 10]
                    }
                },
                {
                    name: "flip",
                    options: {
                        fallbackPlacements: ["top"]
                    }
                }
            ]
        });
    }

    onEscape(): void {
        this.close();
        this.shouldNotOpen = true;
    }
    close(): void {
        // TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
        this.app.keymap.popScope(this.scope);

        this.suggester.setSuggestions([]);
        if (this.popper) {
            this.popper.destroy();
        }

        this.suggestEl.detach();
    }
    createPrompt(prompts: HTMLSpanElement[]) {
        if (!this.promptEl)
            this.promptEl = this.suggestEl.createDiv("prompt-instructions");
        let prompt = this.promptEl.createDiv("prompt-instruction");
        for (let p of prompts) {
            prompt.appendChild(p);
        }
    }
    abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
    abstract getItemText(arg: T): string;
    abstract getItems(): T[];
}
Example #19
Source File: index.ts    From obsidian-admonition with MIT License 4 votes vote down vote up
export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
    items: T[] = [];
    suggestions: HTMLDivElement[];
    popper: PopperInstance;
    scope: Scope = new Scope();
    suggester: Suggester<FuzzyMatch<T>>;
    suggestEl: HTMLDivElement;
    promptEl: HTMLDivElement;
    emptyStateText: string = "No match found";
    limit: number = 100;
    constructor(app: App, inputEl: HTMLInputElement, items: T[]) {
        super(app);
        this.inputEl = inputEl;
        this.items = items;

        this.suggestEl = createDiv("suggestion-container");

        this.suggestEl.style.width = `${inputEl.clientWidth}px`;

        this.contentEl = this.suggestEl.createDiv("suggestion");

        this.suggester = new Suggester(this, this.contentEl, this.scope);

        this.scope.register([], "Escape", this.close.bind(this));

        this.inputEl.addEventListener("input", this.onInputChanged.bind(this));
        this.inputEl.addEventListener("focus", this.onInputChanged.bind(this));
        this.inputEl.addEventListener("blur", this.close.bind(this));
        this.suggestEl.on(
            "mousedown",
            ".suggestion-container",
            (event: MouseEvent) => {
                event.preventDefault();
            }
        );
    }
    empty() {
        this.suggester.empty();
    }
    onInputChanged(): void {
        const inputStr = this.modifyInput(this.inputEl.value);
        const suggestions = this.getSuggestions(inputStr);
        if (suggestions.length > 0) {
            this.suggester.setSuggestions(suggestions.slice(0, this.limit));
        } else {
            this.onNoSuggestion();
        }
        this.open();
    }

    modifyInput(input: string): string {
        return input;
    }
    onNoSuggestion() {
        this.empty();
        this.renderSuggestion(
            null,
            this.contentEl.createDiv("suggestion-item")
        );
    }
    open(): void {
        // TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (<any>this.app).keymap.pushScope(this.scope);

        document.body.appendChild(this.suggestEl);
        this.popper = createPopper(this.inputEl, this.suggestEl, {
            placement: "bottom-start",
            modifiers: [
                {
                    name: "offset",
                    options: {
                        offset: [0, 10]
                    }
                },
                {
                    name: "flip",
                    options: {
                        fallbackPlacements: ["top"]
                    }
                }
            ]
        });
    }

    close(): void {
        // TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (<any>this.app).keymap.popScope(this.scope);

        this.suggester.setSuggestions([]);
        if (this.popper) {
            this.popper.destroy();
        }

        this.suggestEl.detach();
    }
    createPrompt(prompts: HTMLSpanElement[]) {
        if (!this.promptEl)
            this.promptEl = this.suggestEl.createDiv("prompt-instructions");
        let prompt = this.promptEl.createDiv("prompt-instruction");
        for (let p of prompts) {
            prompt.appendChild(p);
        }
    }
    abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
    abstract getItemText(arg: T): string;
    abstract getItems(): T[];
}
Example #20
Source File: suggester.ts    From obsidian-initiative-tracker with GNU General Public License v3.0 4 votes vote down vote up
abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
    items: T[] = [];
    suggestions: HTMLDivElement[];
    popper: PopperInstance;
    scope: Scope = new Scope();
    suggester: Suggester<FuzzyMatch<T>>;
    suggestEl: HTMLDivElement;
    promptEl: HTMLDivElement;
    emptyStateText: string = "No match found";
    limit: number = 25;
    constructor(app: App, inputEl: HTMLInputElement) {
        super(app);
        this.inputEl = inputEl;

        this.suggestEl = createDiv({
            attr: { style: "min-width: 475px;" },
            cls: "suggestion-container"
        });

        this.contentEl = this.suggestEl.createDiv("suggestion");

        this.suggester = new Suggester(this, this.contentEl, this.scope);

        this.scope.register([], "Escape", this.close.bind(this));

        this.inputEl.addEventListener("input", this.onInputChanged.bind(this));
        /* this.inputEl.addEventListener("focus", this.onInputChanged.bind(this)); */
        this.inputEl.addEventListener("blur", this.close.bind(this));
        this.suggestEl.on(
            "mousedown",
            ".suggestion-container",
            (event: MouseEvent) => {
                event.preventDefault();
            }
        );
    }
    empty() {
        this.suggester.empty();
    }
    onInputChanged(): void {
        const inputStr = this.modifyInput(this.inputEl.value);
        const suggestions = this.getSuggestions(inputStr);

        if (suggestions.length > 0) {
            this.suggester.setSuggestions(suggestions.slice(0, this.limit));
        } else {
            this.onNoSuggestion();
        }
        this.open();
    }

    modifyInput(input: string): string {
        return input;
    }
    onNoSuggestion() {
        this.empty();
        this.renderSuggestion(
            null,
            this.contentEl.createDiv("suggestion-item")
        );
    }
    open(): void {
        // TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (<any>this.app).keymap.pushScope(this.scope);

        document.body.appendChild(this.suggestEl);
        this.popper = createPopper(this.inputEl, this.suggestEl, {
            placement: "auto-start",
            modifiers: [
                {
                    name: "offset",
                    options: {
                        offset: [0, 10]
                    }
                },
                {
                    name: "flip",
                    options: {
                        allowedAutoPlacements: ["top-start", "bottom-start"]
                    }
                }
            ]
        });
    }

    close(): void {
        // TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (<any>this.app).keymap.popScope(this.scope);

        this.suggester.setSuggestions([]);
        if (this.popper) {
            this.popper.destroy();
        }

        this.suggestEl.detach();
    }
    createPrompt(prompts: HTMLSpanElement[]) {
        if (!this.promptEl)
            this.promptEl = this.suggestEl.createDiv("prompt-instructions");
        let prompt = this.promptEl.createDiv("prompt-instruction");
        for (let p of prompts) {
            prompt.appendChild(p);
        }
    }
    abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
    abstract getItemText(arg: T): string;
    abstract getItems(): T[];
}