All files / src/views documents-modal.ts

100% Statements 56/56
100% Branches 20/20
100% Functions 11/11
100% Lines 49/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97                  39x 39x 39x 39x 39x         39x 39x 39x 39x 39x 39x       33x 33x 33x   33x   33x         33x 5x 5x     33x       33x 33x   33x 33x   45x 17x   33x   33x       5x 5x       44x 43x 43x 43x       62x 60x       13x 13x 11x 11x 11x 11x 10x 10x 7x 7x 7x   3x 3x        
import { App, Modal, Notice } from "obsidian";
import type LilbeePlugin from "../main";
import { ConfirmModal } from "./confirm-modal";
import { DocumentList } from "../components/document-list";
import { MESSAGES } from "../locales/en";
import { bindEscapeToClose, debounce, DEBOUNCE_MS, extractServerErrorDetail } from "../utils";
 
export class DocumentsModal extends Modal {
    private plugin: LilbeePlugin;
    private list: DocumentList | null = null;
    private unbindScroll: (() => void) | null = null;
    private resultsEl: HTMLElement | null = null;
    private removeBtn: HTMLElement | null = null;
    private searchQuery = "";
    private debouncedSearch: () => void;
    private cancelDebouncedSearch: () => void;
 
    constructor(app: App, plugin: LilbeePlugin) {
        super(app);
        this.plugin = plugin;
        const searchDebounced = debounce(() => this.resetAndFetch(), DEBOUNCE_MS);
        this.debouncedSearch = searchDebounced.run;
        this.cancelDebouncedSearch = searchDebounced.cancel;
        bindEscapeToClose(this);
    }
 
    onOpen(): void {
        const { contentEl } = this;
        contentEl.empty();
        contentEl.addClass("lilbee-documents-modal");
 
        contentEl.createEl("h2", { text: MESSAGES.TITLE_DOCUMENTS });
 
        const searchInput = contentEl.createEl("input", {
            cls: "lilbee-documents-search",
            placeholder: MESSAGES.PLACEHOLDER_SEARCH_DOCUMENTS,
            attr: { type: "text" },
        });
        searchInput.addEventListener("input", () => {
            this.searchQuery = searchInput.value;
            this.debouncedSearch();
        });
 
        this.removeBtn = contentEl.createEl("button", {
            text: MESSAGES.BUTTON_DELETE_SELECTED,
            cls: "lilbee-documents-remove",
        });
        (this.removeBtn as HTMLButtonElement).disabled = true;
        this.removeBtn.addEventListener("click", () => void this.removeSelected());
 
        this.resultsEl = contentEl.createDiv({ cls: "lilbee-documents-results" });
        this.list = new DocumentList(
            this.resultsEl,
            (limit, offset) => this.plugin.api.listDocuments(this.searchQuery || undefined, limit, offset),
            { selectable: true, onSelectionChange: () => this.updateRemoveBtn() },
        );
        this.unbindScroll = this.list.bindScroll(this.resultsEl);
 
        this.resetAndFetch();
    }
 
    onClose(): void {
        this.cancelDebouncedSearch();
        this.unbindScroll?.();
    }
 
    private resetAndFetch(): void {
        if (!this.list) return;
        this.list.reset();
        this.updateRemoveBtn();
        void this.list.loadMore();
    }
 
    private updateRemoveBtn(): void {
        if (!this.removeBtn || !this.list) return;
        (this.removeBtn as HTMLButtonElement).disabled = this.list.selected.length === 0;
    }
 
    private async removeSelected(): Promise<void> {
        const names = this.list?.selected ?? [];
        if (names.length === 0) return;
        const confirm = new ConfirmModal(this.app, MESSAGES.NOTICE_CONFIRM_DELETE_DOCS(names.length));
        confirm.open();
        const confirmed = await confirm.result;
        if (!confirmed) return;
        try {
            const result = await this.plugin.api.removeDocuments(names);
            new Notice(MESSAGES.NOTICE_DELETED(result.removed.length));
            if (result.not_found.length > 0) new Notice(MESSAGES.NOTICE_DELETE_NOT_FOUND(result.not_found.length));
            this.resetAndFetch();
        } catch (err) {
            const reason = err instanceof Error ? extractServerErrorDetail(err.message) : null;
            new Notice(reason ? MESSAGES.ERROR_DELETE_DOCUMENTS_REASON(reason) : MESSAGES.ERROR_DELETE_DOCUMENTS);
        }
    }
}