mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01:00 
			
		
		
		
	refactor(react/dialogs): use shown everywhere
This commit is contained in:
		
							parent
							
								
									fa97ec6c72
								
							
						
					
					
						commit
						b7482f2a6a
					
				@ -30,6 +30,7 @@ import type CodeMirror from "@triliumnext/codemirror";
 | 
			
		||||
import { StartupChecks } from "./startup_checks.js";
 | 
			
		||||
import type { CreateNoteOpts } from "../services/note_create.js";
 | 
			
		||||
import { ColumnComponent } from "tabulator-tables";
 | 
			
		||||
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
 | 
			
		||||
 | 
			
		||||
interface Layout {
 | 
			
		||||
    getRootWidget: (appContext: AppContext) => RootWidget;
 | 
			
		||||
@ -370,6 +371,9 @@ export type CommandMappings = {
 | 
			
		||||
    };
 | 
			
		||||
    refreshTouchBar: CommandData;
 | 
			
		||||
    reloadTextEditor: CommandData;
 | 
			
		||||
    chooseNoteType: CommandData & {
 | 
			
		||||
        callback: ChooseNoteTypeCallback
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type EventMappings = {
 | 
			
		||||
 | 
			
		||||
@ -109,8 +109,6 @@ async function createNote(parentNotePath: string | undefined, options: CreateNot
 | 
			
		||||
 | 
			
		||||
async function chooseNoteType() {
 | 
			
		||||
    return new Promise<ChooseNoteTypeResponse>((res) => {
 | 
			
		||||
        // TODO: Remove ignore after callback for chooseNoteType is defined in app_context.ts
 | 
			
		||||
        //@ts-ignore
 | 
			
		||||
        appContext.triggerCommand("chooseNoteType", { callback: res });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,36 +1,51 @@
 | 
			
		||||
import ReactBasicWidget from "../react/ReactBasicWidget";
 | 
			
		||||
import Modal from "../react/Modal";
 | 
			
		||||
import Button from "../react/Button";
 | 
			
		||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
 | 
			
		||||
import { closeActiveDialog } from "../../services/dialog";
 | 
			
		||||
import { t } from "../../services/i18n";
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import FormCheckbox from "../react/FormCheckbox";
 | 
			
		||||
import useTriliumEvent from "../react/hooks";
 | 
			
		||||
 | 
			
		||||
interface ConfirmDialogProps {
 | 
			
		||||
    title?: string;
 | 
			
		||||
    message?: string | HTMLElement;
 | 
			
		||||
    callback?: ConfirmDialogCallback;
 | 
			
		||||
    lastElementToFocus?: HTMLElement | null;
 | 
			
		||||
    isConfirmDeleteNoteBox?: boolean;   
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ConfirmDialogComponent({ title, message, callback, lastElementToFocus, isConfirmDeleteNoteBox }: ConfirmDialogProps) {
 | 
			
		||||
function ConfirmDialogComponent() {
 | 
			
		||||
    const [ opts, setOpts ] = useState<ConfirmDialogProps>();
 | 
			
		||||
    const [ confirmed, setConfirmed ] = useState<boolean>(false);
 | 
			
		||||
    const [ isDeleteNoteChecked, setIsDeleteNoteChecked ] = useState<boolean>(false);
 | 
			
		||||
    const [ isDeleteNoteChecked, setIsDeleteNoteChecked ] = useState(false);
 | 
			
		||||
    const [ shown, setShown ] = useState(false);
 | 
			
		||||
 | 
			
		||||
    function showDialog(title: string | null, message: MessageType, callback: ConfirmDialogCallback, isConfirmDeleteNoteBox: boolean) {
 | 
			
		||||
        setOpts({
 | 
			
		||||
            title: title ?? undefined,
 | 
			
		||||
            message: (typeof message === "object" && "length" in message ? message[0] : message),
 | 
			
		||||
            callback,
 | 
			
		||||
            isConfirmDeleteNoteBox
 | 
			
		||||
        });
 | 
			
		||||
        setShown(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    useTriliumEvent("showConfirmDialog", ({ message, callback }) => showDialog(null, message, callback, false));
 | 
			
		||||
    useTriliumEvent("showConfirmDeleteNoteBoxWithNoteDialog", ({ title, callback }) => showDialog(title, t("confirm.are_you_sure_remove_note", { title: title }), callback, true));
 | 
			
		||||
 | 
			
		||||
    return ( 
 | 
			
		||||
        <Modal
 | 
			
		||||
            className="confirm-dialog"
 | 
			
		||||
            title={title ?? t("confirm.confirmation")}
 | 
			
		||||
            title={opts?.title ?? t("confirm.confirmation")}
 | 
			
		||||
            size="md"
 | 
			
		||||
            zIndex={2000}
 | 
			
		||||
            scrollable={true}
 | 
			
		||||
            onHidden={() => {
 | 
			
		||||
                callback?.({
 | 
			
		||||
                opts?.callback?.({
 | 
			
		||||
                    confirmed,
 | 
			
		||||
                    isDeleteNoteChecked
 | 
			
		||||
                });
 | 
			
		||||
                lastElementToFocus?.focus();
 | 
			
		||||
                setShown(false);
 | 
			
		||||
            }}
 | 
			
		||||
            footer={<>
 | 
			
		||||
                <Button text={t("confirm.cancel")} onClick={() => closeActiveDialog()} />
 | 
			
		||||
@ -39,12 +54,13 @@ function ConfirmDialogComponent({ title, message, callback, lastElementToFocus,
 | 
			
		||||
                    closeActiveDialog();
 | 
			
		||||
                }} />
 | 
			
		||||
            </>}
 | 
			
		||||
            show={shown}
 | 
			
		||||
        >
 | 
			
		||||
            {!message || typeof message === "string"
 | 
			
		||||
                ? <div>{(message as string) ?? ""}</div>
 | 
			
		||||
                : <div dangerouslySetInnerHTML={{ __html: message.outerHTML ?? "" }} />}
 | 
			
		||||
            {!opts?.message || typeof opts?.message === "string"
 | 
			
		||||
                ? <div>{(opts?.message as string) ?? ""}</div>
 | 
			
		||||
                : <div dangerouslySetInnerHTML={{ __html: opts?.message.outerHTML ?? "" }} />}
 | 
			
		||||
 | 
			
		||||
            {isConfirmDeleteNoteBox && (
 | 
			
		||||
            {opts?.isConfirmDeleteNoteBox && (
 | 
			
		||||
                <FormCheckbox
 | 
			
		||||
                    name="confirm-dialog-delete-note"
 | 
			
		||||
                    label={t("confirm.also_delete_note")}
 | 
			
		||||
@ -77,31 +93,8 @@ export interface ConfirmWithTitleOptions {
 | 
			
		||||
 | 
			
		||||
export default class ConfirmDialog extends ReactBasicWidget {
 | 
			
		||||
 | 
			
		||||
    private props: ConfirmDialogProps = {};
 | 
			
		||||
 | 
			
		||||
    get component() {
 | 
			
		||||
        return <ConfirmDialogComponent {...this.props} />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    showConfirmDialogEvent({ message, callback }: ConfirmWithMessageOptions) {
 | 
			
		||||
        this.showDialog(null, message, callback, false);        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    showConfirmDeleteNoteBoxWithNoteDialogEvent({ title, callback }: ConfirmWithTitleOptions) {
 | 
			
		||||
        const message = t("confirm.are_you_sure_remove_note", { title: title });
 | 
			
		||||
        this.showDialog(title, message, callback, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private showDialog(title: string | null, message: MessageType, callback: ConfirmDialogCallback, isConfirmDeleteNoteBox: boolean) {
 | 
			
		||||
        this.props = {
 | 
			
		||||
            title: title ?? undefined,
 | 
			
		||||
            message: (typeof message === "object" && "length" in message ? message[0] : message),
 | 
			
		||||
            lastElementToFocus: (document.activeElement as HTMLElement),
 | 
			
		||||
            callback,
 | 
			
		||||
            isConfirmDeleteNoteBox
 | 
			
		||||
        };
 | 
			
		||||
        this.doRender();
 | 
			
		||||
        openDialog(this.$widget);
 | 
			
		||||
        return <ConfirmDialogComponent />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { useRef, useState } from "preact/hooks";
 | 
			
		||||
import { closeActiveDialog, openDialog } from "../../services/dialog.js";
 | 
			
		||||
import { closeActiveDialog } from "../../services/dialog.js";
 | 
			
		||||
import { t } from "../../services/i18n.js";
 | 
			
		||||
import FormCheckbox from "../react/FormCheckbox.js";
 | 
			
		||||
import Modal from "../react/Modal.js";
 | 
			
		||||
@ -12,6 +12,7 @@ import FNote from "../../entities/fnote.js";
 | 
			
		||||
import link from "../../services/link.js";
 | 
			
		||||
import Button from "../react/Button.jsx";
 | 
			
		||||
import Alert from "../react/Alert.jsx";
 | 
			
		||||
import useTriliumEvent from "../react/hooks.jsx";
 | 
			
		||||
 | 
			
		||||
export interface ResolveOptions {
 | 
			
		||||
    proceed: boolean;
 | 
			
		||||
@ -25,22 +26,28 @@ interface ShowDeleteNotesDialogOpts {
 | 
			
		||||
    forceDeleteAllClones?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ShowDeleteNotesDialogProps extends ShowDeleteNotesDialogOpts { }
 | 
			
		||||
 | 
			
		||||
interface BrokenRelationData {
 | 
			
		||||
    note: string;
 | 
			
		||||
    relation: string;
 | 
			
		||||
    source: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function DeleteNotesDialogComponent({ forceDeleteAllClones, branchIdsToDelete, callback }: ShowDeleteNotesDialogProps) {
 | 
			
		||||
function DeleteNotesDialogComponent() {
 | 
			
		||||
    const [ opts, setOpts ] = useState<ShowDeleteNotesDialogOpts>({});
 | 
			
		||||
    const [ deleteAllClones, setDeleteAllClones ] = useState(false);
 | 
			
		||||
    const [ eraseNotes, setEraseNotes ] = useState(!!forceDeleteAllClones);
 | 
			
		||||
    const [ eraseNotes, setEraseNotes ] = useState(!!opts.forceDeleteAllClones);
 | 
			
		||||
    const [ brokenRelations, setBrokenRelations ] = useState<DeleteNotesPreview["brokenRelations"]>([]);
 | 
			
		||||
    const [ noteIdsToBeDeleted, setNoteIdsToBeDeleted ] = useState<DeleteNotesPreview["noteIdsToBeDeleted"]>([]);    
 | 
			
		||||
    const [ shown, setShown ] = useState(false);
 | 
			
		||||
    const okButtonRef = useRef<HTMLButtonElement>(null);
 | 
			
		||||
 | 
			
		||||
    useTriliumEvent("showDeleteNotesDialog", (opts) => {
 | 
			
		||||
        setOpts(opts);
 | 
			
		||||
        setShown(true);
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const { branchIdsToDelete, forceDeleteAllClones } = opts;
 | 
			
		||||
        if (!branchIdsToDelete || branchIdsToDelete.length === 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }        
 | 
			
		||||
@ -52,7 +59,7 @@ function DeleteNotesDialogComponent({ forceDeleteAllClones, branchIdsToDelete, c
 | 
			
		||||
            setBrokenRelations(response.brokenRelations);
 | 
			
		||||
            setNoteIdsToBeDeleted(response.noteIdsToBeDeleted);
 | 
			
		||||
        });
 | 
			
		||||
    }, [ branchIdsToDelete, deleteAllClones, forceDeleteAllClones ]);
 | 
			
		||||
    }, [ opts ]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Modal
 | 
			
		||||
@ -61,24 +68,28 @@ function DeleteNotesDialogComponent({ forceDeleteAllClones, branchIdsToDelete, c
 | 
			
		||||
            scrollable
 | 
			
		||||
            title={t("delete_notes.delete_notes_preview")}
 | 
			
		||||
            onShown={() => okButtonRef.current?.focus()}
 | 
			
		||||
            onHidden={() => callback?.({ proceed: false })}
 | 
			
		||||
            onHidden={() => {
 | 
			
		||||
                opts.callback?.({ proceed: false })
 | 
			
		||||
                setShown(false);
 | 
			
		||||
            }}
 | 
			
		||||
            footer={<>
 | 
			
		||||
                <Button text={t("delete_notes.cancel")}
 | 
			
		||||
                    onClick={() => closeActiveDialog()} />
 | 
			
		||||
                <Button text={t("delete_notes.ok")} primary
 | 
			
		||||
                    buttonRef={okButtonRef}
 | 
			
		||||
                    onClick={() => {
 | 
			
		||||
                        callback?.({ proceed: true, deleteAllClones, eraseNotes });
 | 
			
		||||
                        opts.callback?.({ proceed: true, deleteAllClones, eraseNotes });
 | 
			
		||||
                        closeActiveDialog();
 | 
			
		||||
                    }} />
 | 
			
		||||
            </>}
 | 
			
		||||
            show={shown}
 | 
			
		||||
        >
 | 
			
		||||
            <FormCheckbox name="delete-all-clones" label={t("delete_notes.delete_all_clones_description")}
 | 
			
		||||
                currentValue={deleteAllClones} onChange={setDeleteAllClones}
 | 
			
		||||
            />
 | 
			
		||||
            <FormCheckbox
 | 
			
		||||
                name="erase-notes" label={t("delete_notes.erase_notes_warning")}
 | 
			
		||||
                disabled={forceDeleteAllClones}
 | 
			
		||||
                disabled={opts.forceDeleteAllClones}
 | 
			
		||||
                currentValue={eraseNotes} onChange={setEraseNotes}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
@ -160,21 +171,9 @@ function BrokenRelations({ brokenRelations }: { brokenRelations: DeleteNotesPrev
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class DeleteNotesDialog extends ReactBasicWidget {
 | 
			
		||||
    
 | 
			
		||||
    private props: ShowDeleteNotesDialogProps = {};
 | 
			
		||||
 | 
			
		||||
    get component() {
 | 
			
		||||
        return <DeleteNotesDialogComponent {...this.props} />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async showDeleteNotesDialogEvent({ branchIdsToDelete, callback, forceDeleteAllClones }: ShowDeleteNotesDialogOpts) {
 | 
			
		||||
        this.props = {
 | 
			
		||||
            branchIdsToDelete,
 | 
			
		||||
            callback,
 | 
			
		||||
            forceDeleteAllClones
 | 
			
		||||
        };
 | 
			
		||||
        this.doRender();
 | 
			
		||||
        openDialog(this.$widget);
 | 
			
		||||
    }
 | 
			
		||||
        return <DeleteNotesDialogComponent />;
 | 
			
		||||
    }    
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import { useState } from "preact/hooks";
 | 
			
		||||
import { EventData } from "../../components/app_context";
 | 
			
		||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
 | 
			
		||||
import { closeActiveDialog } from "../../services/dialog";
 | 
			
		||||
import { t } from "../../services/i18n";
 | 
			
		||||
import tree from "../../services/tree";
 | 
			
		||||
import Button from "../react/Button";
 | 
			
		||||
@ -13,6 +12,7 @@ import toastService, { ToastOptions } from "../../services/toast";
 | 
			
		||||
import utils from "../../services/utils";
 | 
			
		||||
import open from "../../services/open";
 | 
			
		||||
import froca from "../../services/froca";
 | 
			
		||||
import useTriliumEvent from "../react/hooks";
 | 
			
		||||
 | 
			
		||||
interface ExportDialogProps {
 | 
			
		||||
    branchId?: string | null;
 | 
			
		||||
@ -20,24 +20,48 @@ interface ExportDialogProps {
 | 
			
		||||
    defaultType?: "subtree" | "single";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ExportDialogComponent({ branchId, noteTitle, defaultType }: ExportDialogProps) {
 | 
			
		||||
    const [ exportType, setExportType ] = useState<string>(defaultType ?? "subtree");
 | 
			
		||||
    const [ subtreeFormat, setSubtreeFormat ] = useState<string>("html");
 | 
			
		||||
    const [ singleFormat, setSingleFormat ] = useState<string>("html");
 | 
			
		||||
    const [ opmlVersion, setOpmlVersion ] = useState<string>("2.0");
 | 
			
		||||
function ExportDialogComponent() {
 | 
			
		||||
    const [ opts, setOpts ] = useState<ExportDialogProps>();
 | 
			
		||||
    const [ exportType, setExportType ] = useState(opts?.defaultType ?? "subtree");
 | 
			
		||||
    const [ subtreeFormat, setSubtreeFormat ] = useState("html");
 | 
			
		||||
    const [ singleFormat, setSingleFormat ] = useState("html");
 | 
			
		||||
    const [ opmlVersion, setOpmlVersion ] = useState("2.0");
 | 
			
		||||
    const [ shown, setShown ] = useState(false);
 | 
			
		||||
 | 
			
		||||
    return (branchId &&
 | 
			
		||||
    useTriliumEvent("showExportDialog", async ({ notePath, defaultType }) => {
 | 
			
		||||
        const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
 | 
			
		||||
        if (!parentNoteId) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const branchId = await froca.getBranchId(parentNoteId, noteId);
 | 
			
		||||
 | 
			
		||||
        setOpts({
 | 
			
		||||
            noteTitle: noteId && await tree.getNoteTitle(noteId),
 | 
			
		||||
            defaultType,
 | 
			
		||||
            branchId
 | 
			
		||||
        });
 | 
			
		||||
        setShown(true);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Modal
 | 
			
		||||
            className="export-dialog"
 | 
			
		||||
            title={`${t("export.export_note_title")} ${noteTitle ?? ""}`}
 | 
			
		||||
            title={`${t("export.export_note_title")} ${opts?.noteTitle ?? ""}`}
 | 
			
		||||
            size="lg"
 | 
			
		||||
            onSubmit={() => {
 | 
			
		||||
                if (!opts || !opts.branchId) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const format = (exportType === "subtree" ? subtreeFormat : singleFormat);
 | 
			
		||||
                const version = (format === "opml" ? opmlVersion : "1.0");
 | 
			
		||||
                exportBranch(branchId, exportType, format, version);
 | 
			
		||||
                exportBranch(opts.branchId, exportType, format, version);
 | 
			
		||||
                closeActiveDialog();
 | 
			
		||||
            }}
 | 
			
		||||
            onHidden={() => setShown(false)}
 | 
			
		||||
            footer={<Button className="export-button" text={t("export.export")} primary />}
 | 
			
		||||
            show={shown}
 | 
			
		||||
        >
 | 
			
		||||
 | 
			
		||||
            <FormRadioGroup
 | 
			
		||||
@ -104,29 +128,10 @@ function ExportDialogComponent({ branchId, noteTitle, defaultType }: ExportDialo
 | 
			
		||||
 | 
			
		||||
export default class ExportDialog extends ReactBasicWidget {
 | 
			
		||||
 | 
			
		||||
    private props: ExportDialogProps = {};
 | 
			
		||||
 | 
			
		||||
    get component() {
 | 
			
		||||
        return <ExportDialogComponent {...this.props} />
 | 
			
		||||
    }
 | 
			
		||||
        return <ExportDialogComponent />
 | 
			
		||||
    }    
 | 
			
		||||
 | 
			
		||||
    async showExportDialogEvent({ notePath, defaultType }: EventData<"showExportDialog">) {
 | 
			
		||||
        const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
 | 
			
		||||
        if (!parentNoteId) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const branchId = await froca.getBranchId(parentNoteId, noteId);
 | 
			
		||||
 | 
			
		||||
        this.props = {
 | 
			
		||||
            noteTitle: noteId && await tree.getNoteTitle(noteId),
 | 
			
		||||
            defaultType,
 | 
			
		||||
            branchId
 | 
			
		||||
        };
 | 
			
		||||
        this.doRender();
 | 
			
		||||
        
 | 
			
		||||
        openDialog(this.$widget);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function exportBranch(branchId: string, type: string, format: string, version: string) {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import { useState } from "preact/hooks";
 | 
			
		||||
import { EventData } from "../../components/app_context";
 | 
			
		||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
 | 
			
		||||
import { closeActiveDialog } from "../../services/dialog";
 | 
			
		||||
import { t } from "../../services/i18n";
 | 
			
		||||
import tree from "../../services/tree";
 | 
			
		||||
import Button from "../react/Button";
 | 
			
		||||
@ -11,13 +10,11 @@ import Modal from "../react/Modal";
 | 
			
		||||
import RawHtml from "../react/RawHtml";
 | 
			
		||||
import ReactBasicWidget from "../react/ReactBasicWidget";
 | 
			
		||||
import importService, { UploadFilesOptions } from "../../services/import";
 | 
			
		||||
import useTriliumEvent from "../react/hooks";
 | 
			
		||||
 | 
			
		||||
interface ImportDialogComponentProps {
 | 
			
		||||
    parentNoteId?: string;
 | 
			
		||||
    noteTitle?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ImportDialogComponent({  parentNoteId, noteTitle }: ImportDialogComponentProps) {
 | 
			
		||||
function ImportDialogComponent() {
 | 
			
		||||
    const [ parentNoteId, setParentNoteId ] = useState<string>();
 | 
			
		||||
    const [ noteTitle, setNoteTitle ] = useState<string>();
 | 
			
		||||
    const [ files, setFiles ] = useState<FileList | null>(null);
 | 
			
		||||
    const [ safeImport, setSafeImport ] = useState(true);
 | 
			
		||||
    const [ explodeArchives, setExplodeArchives ] = useState(true);
 | 
			
		||||
@ -25,14 +22,21 @@ function ImportDialogComponent({  parentNoteId, noteTitle }: ImportDialogCompone
 | 
			
		||||
    const [ textImportedAsText, setTextImportedAsText ] = useState(true);
 | 
			
		||||
    const [ codeImportedAsCode, setCodeImportedAsCode ] = useState(true);
 | 
			
		||||
    const [ replaceUnderscoresWithSpaces, setReplaceUnderscoresWithSpaces ] = useState(true);
 | 
			
		||||
    const [ shown, setShown ] = useState(false);
 | 
			
		||||
 | 
			
		||||
    return (parentNoteId &&
 | 
			
		||||
    useTriliumEvent("showImportDialog", ({ noteId }) => {
 | 
			
		||||
        setParentNoteId(noteId);
 | 
			
		||||
        tree.getNoteTitle(noteId).then(setNoteTitle);
 | 
			
		||||
        setShown(true);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Modal
 | 
			
		||||
            className="import-dialog"
 | 
			
		||||
            size="lg"
 | 
			
		||||
            title={t("import.importIntoNote")}
 | 
			
		||||
            onSubmit={async () => {
 | 
			
		||||
                if (!files) {
 | 
			
		||||
                if (!files || !parentNoteId) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }                
 | 
			
		||||
 | 
			
		||||
@ -48,7 +52,9 @@ function ImportDialogComponent({  parentNoteId, noteTitle }: ImportDialogCompone
 | 
			
		||||
                closeActiveDialog();
 | 
			
		||||
                await importService.uploadFiles("notes", parentNoteId, Array.from(files), options);
 | 
			
		||||
            }}
 | 
			
		||||
            onHidden={() => setShown(false)}
 | 
			
		||||
            footer={<Button text={t("import.import")} primary disabled={!files} />}
 | 
			
		||||
            show={shown}
 | 
			
		||||
        >
 | 
			
		||||
            <FormGroup label={t("import.chooseImportFile")} description={<>{t("import.importDescription")} <strong>{ noteTitle }</strong></>}>
 | 
			
		||||
                <FormFileUpload multiple onChange={setFiles} />
 | 
			
		||||
@ -86,21 +92,9 @@ function ImportDialogComponent({  parentNoteId, noteTitle }: ImportDialogCompone
 | 
			
		||||
 | 
			
		||||
export default class ImportDialog extends ReactBasicWidget {
 | 
			
		||||
 | 
			
		||||
    private props?: ImportDialogComponentProps = {};
 | 
			
		||||
 | 
			
		||||
    get component() {
 | 
			
		||||
        return <ImportDialogComponent {...this.props} />
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async showImportDialogEvent({ noteId }: EventData<"showImportDialog">) {
 | 
			
		||||
        this.props = {
 | 
			
		||||
            parentNoteId: noteId,
 | 
			
		||||
            noteTitle: await tree.getNoteTitle(noteId)
 | 
			
		||||
        }
 | 
			
		||||
        this.doRender();
 | 
			
		||||
 | 
			
		||||
        openDialog(this.$widget);
 | 
			
		||||
    }
 | 
			
		||||
        return <ImportDialogComponent />
 | 
			
		||||
    }    
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import { useRef, useState } from "preact/compat";
 | 
			
		||||
import type { EventData } from "../../components/app_context";
 | 
			
		||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
 | 
			
		||||
import { closeActiveDialog } from "../../services/dialog";
 | 
			
		||||
import { t } from "../../services/i18n";
 | 
			
		||||
import FormGroup from "../react/FormGroup";
 | 
			
		||||
import FormRadioGroup from "../react/FormRadioGroup";
 | 
			
		||||
@ -12,24 +11,30 @@ import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete
 | 
			
		||||
import tree from "../../services/tree";
 | 
			
		||||
import froca from "../../services/froca";
 | 
			
		||||
import EditableTextTypeWidget from "../type_widgets/editable_text";
 | 
			
		||||
import useTriliumEvent from "../react/hooks";
 | 
			
		||||
 | 
			
		||||
interface IncludeNoteDialogProps {
 | 
			
		||||
    textTypeWidget?: EditableTextTypeWidget;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function IncludeNoteDialogComponent({ textTypeWidget }: IncludeNoteDialogProps) {
 | 
			
		||||
function IncludeNoteDialogComponent() {
 | 
			
		||||
    const [textTypeWidget, setTextTypeWidget] = useState<EditableTextTypeWidget>();
 | 
			
		||||
    const [suggestion, setSuggestion] = useState<Suggestion | null>(null);
 | 
			
		||||
    const [boxSize, setBoxSize] = useState("medium");
 | 
			
		||||
    const [shown, setShown] = useState(false);
 | 
			
		||||
 | 
			
		||||
    useTriliumEvent("showIncludeNoteDialog", ({ textTypeWidget }) => {
 | 
			
		||||
        setTextTypeWidget(textTypeWidget);
 | 
			
		||||
        setShown(true);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const autoCompleteRef = useRef<HTMLInputElement>(null);
 | 
			
		||||
 | 
			
		||||
    return (textTypeWidget &&
 | 
			
		||||
    return (
 | 
			
		||||
        <Modal
 | 
			
		||||
            className="include-note-dialog"
 | 
			
		||||
            title={t("include_note.dialog_title")}
 | 
			
		||||
            size="lg"
 | 
			
		||||
            onShown={() => triggerRecentNotes(autoCompleteRef.current)}
 | 
			
		||||
            onHidden={() => setShown(false)}
 | 
			
		||||
            onSubmit={() => {
 | 
			
		||||
                if (!suggestion?.notePath) {
 | 
			
		||||
                if (!suggestion?.notePath || !textTypeWidget) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -37,6 +42,7 @@ function IncludeNoteDialogComponent({ textTypeWidget }: IncludeNoteDialogProps)
 | 
			
		||||
                includeNote(suggestion.notePath, textTypeWidget);
 | 
			
		||||
            }}
 | 
			
		||||
            footer={<Button text={t("include_note.button_include")} keyboardShortcut="Enter" />}
 | 
			
		||||
            show={shown}
 | 
			
		||||
        >
 | 
			
		||||
            <FormGroup label={t("include_note.label_note")}>
 | 
			
		||||
                <NoteAutocomplete
 | 
			
		||||
@ -66,17 +72,9 @@ function IncludeNoteDialogComponent({ textTypeWidget }: IncludeNoteDialogProps)
 | 
			
		||||
 | 
			
		||||
export default class IncludeNoteDialog extends ReactBasicWidget {
 | 
			
		||||
 | 
			
		||||
    private props: IncludeNoteDialogProps = {};
 | 
			
		||||
 | 
			
		||||
    get component() {
 | 
			
		||||
        return <IncludeNoteDialogComponent {...this.props} />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async showIncludeNoteDialogEvent({ textTypeWidget }: EventData<"showIncludeDialog">) {
 | 
			
		||||
        this.props = { textTypeWidget };
 | 
			
		||||
        this.doRender();
 | 
			
		||||
        openDialog(this.$widget);
 | 
			
		||||
    }
 | 
			
		||||
        return <IncludeNoteDialogComponent />;
 | 
			
		||||
    }    
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,29 +1,30 @@
 | 
			
		||||
import { EventData } from "../../components/app_context";
 | 
			
		||||
import ReactBasicWidget from "../react/ReactBasicWidget";
 | 
			
		||||
import { ConfirmDialogCallback } from "./confirm";
 | 
			
		||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
 | 
			
		||||
import { closeActiveDialog } from "../../services/dialog";
 | 
			
		||||
import Modal from "../react/Modal";
 | 
			
		||||
import { t } from "../../services/i18n";
 | 
			
		||||
import Button from "../react/Button";
 | 
			
		||||
import { useRef } from "preact/compat";
 | 
			
		||||
import { useRef, useState } from "preact/hooks";
 | 
			
		||||
import { RawHtmlBlock } from "../react/RawHtml";
 | 
			
		||||
import useTriliumEvent from "../react/hooks";
 | 
			
		||||
 | 
			
		||||
interface ShowInfoDialogProps {
 | 
			
		||||
    message?: string | HTMLElement;
 | 
			
		||||
    callback?: ConfirmDialogCallback;
 | 
			
		||||
    lastElementToFocus?: HTMLElement | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ShowInfoDialogComponent({ message, callback, lastElementToFocus }: ShowInfoDialogProps) {
 | 
			
		||||
function ShowInfoDialogComponent() {
 | 
			
		||||
    const [ opts, setOpts ] = useState<EventData<"showInfoDialog">>();
 | 
			
		||||
    const [ shown, setShown ] = useState(false);
 | 
			
		||||
    const okButtonRef = useRef<HTMLButtonElement>(null);
 | 
			
		||||
 | 
			
		||||
    return (message && <Modal
 | 
			
		||||
    useTriliumEvent("showInfoDialog", (opts) => {
 | 
			
		||||
        setOpts(opts);
 | 
			
		||||
        setShown(true);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return (<Modal
 | 
			
		||||
        className="info-dialog"
 | 
			
		||||
        size="sm"
 | 
			
		||||
        title={t("info.modalTitle")}
 | 
			
		||||
        onHidden={() => {
 | 
			
		||||
            callback?.();
 | 
			
		||||
            lastElementToFocus?.focus();
 | 
			
		||||
            opts?.callback?.();
 | 
			
		||||
            setShown(false);
 | 
			
		||||
        }}
 | 
			
		||||
        onShown={() => okButtonRef.current?.focus?.()}
 | 
			
		||||
        footer={<Button
 | 
			
		||||
@ -31,27 +32,16 @@ function ShowInfoDialogComponent({ message, callback, lastElementToFocus }: Show
 | 
			
		||||
            text={t("info.okButton")}
 | 
			
		||||
            onClick={() => closeActiveDialog()}
 | 
			
		||||
        />}
 | 
			
		||||
        show={shown}
 | 
			
		||||
    >
 | 
			
		||||
        <RawHtmlBlock className="info-dialog-content" html={message} />
 | 
			
		||||
        <RawHtmlBlock className="info-dialog-content" html={opts?.message ?? ""} />
 | 
			
		||||
    </Modal>);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class InfoDialog extends ReactBasicWidget {
 | 
			
		||||
 | 
			
		||||
    private props: ShowInfoDialogProps = {};
 | 
			
		||||
 | 
			
		||||
    get component() {
 | 
			
		||||
        return <ShowInfoDialogComponent {...this.props} />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    showInfoDialogEvent({ message, callback }: EventData<"showInfoDialog">) {
 | 
			
		||||
        this.props = { 
 | 
			
		||||
            message: Array.isArray(message) ? message[0] : message,
 | 
			
		||||
            callback,
 | 
			
		||||
            lastElementToFocus: (document.activeElement as HTMLElement)
 | 
			
		||||
        };
 | 
			
		||||
        this.doRender();
 | 
			
		||||
        openDialog(this.$widget);
 | 
			
		||||
        return <ShowInfoDialogComponent />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
 | 
			
		||||
import { closeActiveDialog } from "../../services/dialog";
 | 
			
		||||
import ReactBasicWidget from "../react/ReactBasicWidget";
 | 
			
		||||
import Modal from "../react/Modal";
 | 
			
		||||
import Button from "../react/Button";
 | 
			
		||||
@ -9,20 +9,45 @@ import note_autocomplete, { Suggestion } from "../../services/note_autocomplete"
 | 
			
		||||
import appContext from "../../components/app_context";
 | 
			
		||||
import commandRegistry from "../../services/command_registry";
 | 
			
		||||
import { refToJQuerySelector } from "../react/react_utils";
 | 
			
		||||
import useTriliumEvent from "../react/hooks";
 | 
			
		||||
 | 
			
		||||
const KEEP_LAST_SEARCH_FOR_X_SECONDS = 120;
 | 
			
		||||
 | 
			
		||||
type Mode = "last-search" | "recent-notes" | "commands";
 | 
			
		||||
 | 
			
		||||
interface JumpToNoteDialogProps {
 | 
			
		||||
    mode: Mode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function JumpToNoteDialogComponent({ mode }: JumpToNoteDialogProps) {
 | 
			
		||||
function JumpToNoteDialogComponent() {
 | 
			
		||||
    const [ mode, setMode ] = useState<Mode>("last-search");
 | 
			
		||||
    const [ lastOpenedTs, setLastOpenedTs ] = useState<number>(0);
 | 
			
		||||
    const containerRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
    const autocompleteRef = useRef<HTMLInputElement>(null);
 | 
			
		||||
    const [ isCommandMode, setIsCommandMode ] = useState(mode === "commands");
 | 
			
		||||
    const [ text, setText ] = useState(isCommandMode ? "> " : "");
 | 
			
		||||
    const [ shown, setShown ] = useState(false);
 | 
			
		||||
 | 
			
		||||
    async function openDialog(commandMode: boolean) {        
 | 
			
		||||
        let newMode: Mode;
 | 
			
		||||
        if (commandMode) {
 | 
			
		||||
            newMode = "commands";            
 | 
			
		||||
        } else if (Date.now() - lastOpenedTs > KEEP_LAST_SEARCH_FOR_X_SECONDS * 1000) {
 | 
			
		||||
            // if you open the Jump To dialog soon after using it previously, it can often mean that you
 | 
			
		||||
            // actually want to search for the same thing (e.g., you opened the wrong note at first try)
 | 
			
		||||
            // so we'll keep the content.
 | 
			
		||||
            // if it's outside of this time limit, then we assume it's a completely new search and show recent notes instead.
 | 
			
		||||
            newMode = "recent-notes";
 | 
			
		||||
        } else {
 | 
			
		||||
            newMode = "last-search";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (mode !== newMode) {
 | 
			
		||||
            setMode(newMode);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setShown(true);
 | 
			
		||||
        setLastOpenedTs(Date.now());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    useTriliumEvent("jumpToNote", () => openDialog(false));
 | 
			
		||||
    useTriliumEvent("commandPalette", () => openDialog(true));
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setIsCommandMode(text.startsWith(">"));
 | 
			
		||||
@ -77,7 +102,9 @@ function JumpToNoteDialogComponent({ mode }: JumpToNoteDialogProps) {
 | 
			
		||||
                onChange={onItemSelected}
 | 
			
		||||
                />}
 | 
			
		||||
            onShown={onShown}
 | 
			
		||||
            onHidden={() => setShown(false)}
 | 
			
		||||
            footer={!isCommandMode && <Button className="show-in-full-text-button" text={t("jump_to_note.search_button")} keyboardShortcut="Ctrl+Enter" />}
 | 
			
		||||
            show={shown}
 | 
			
		||||
        >
 | 
			
		||||
            <div className="algolia-autocomplete-container jump-to-note-results" ref={containerRef}></div>
 | 
			
		||||
        </Modal>
 | 
			
		||||
@ -86,45 +113,8 @@ function JumpToNoteDialogComponent({ mode }: JumpToNoteDialogProps) {
 | 
			
		||||
 | 
			
		||||
export default class JumpToNoteDialog extends ReactBasicWidget {
 | 
			
		||||
 | 
			
		||||
    private lastOpenedTs?: number;
 | 
			
		||||
    private props: JumpToNoteDialogProps = {
 | 
			
		||||
        mode: "last-search"
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    get component() {
 | 
			
		||||
        return <JumpToNoteDialogComponent {...this.props} />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async openDialog(commandMode = false) {        
 | 
			
		||||
        this.lastOpenedTs = Date.now();
 | 
			
		||||
        
 | 
			
		||||
        let newMode: Mode;
 | 
			
		||||
        if (commandMode) {
 | 
			
		||||
            newMode = "commands";            
 | 
			
		||||
        } else if (Date.now() - this.lastOpenedTs > KEEP_LAST_SEARCH_FOR_X_SECONDS * 1000) {
 | 
			
		||||
            // if you open the Jump To dialog soon after using it previously, it can often mean that you
 | 
			
		||||
            // actually want to search for the same thing (e.g., you opened the wrong note at first try)
 | 
			
		||||
            // so we'll keep the content.
 | 
			
		||||
            // if it's outside of this time limit, then we assume it's a completely new search and show recent notes instead.
 | 
			
		||||
            newMode = "recent-notes";
 | 
			
		||||
        } else {
 | 
			
		||||
            newMode = "last-search";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.props.mode !== newMode) {
 | 
			
		||||
            this.props.mode = newMode;
 | 
			
		||||
            this.doRender();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        openDialog(this.$widget);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async jumpToNoteEvent() {
 | 
			
		||||
        await this.openDialog();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    async commandPaletteEvent() {
 | 
			
		||||
        await this.openDialog(true);
 | 
			
		||||
        return <JumpToNoteDialogComponent />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import ReactBasicWidget from "../react/ReactBasicWidget";
 | 
			
		||||
import Modal from "../react/Modal";
 | 
			
		||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
 | 
			
		||||
import { closeActiveDialog } from "../../services/dialog";
 | 
			
		||||
import { t } from "../../services/i18n";
 | 
			
		||||
import FormGroup from "../react/FormGroup";
 | 
			
		||||
import NoteAutocomplete from "../react/NoteAutocomplete";
 | 
			
		||||
@ -11,6 +11,7 @@ import { MenuCommandItem, MenuItem } from "../../menus/context_menu";
 | 
			
		||||
import { TreeCommandNames } from "../../menus/tree_context_menu";
 | 
			
		||||
import { Suggestion } from "../../services/note_autocomplete";
 | 
			
		||||
import Badge from "../react/Badge";
 | 
			
		||||
import useTriliumEvent from "../react/hooks";
 | 
			
		||||
 | 
			
		||||
export interface ChooseNoteTypeResponse {
 | 
			
		||||
    success: boolean;
 | 
			
		||||
@ -19,20 +20,24 @@ export interface ChooseNoteTypeResponse {
 | 
			
		||||
    notePath?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Callback = (data: ChooseNoteTypeResponse) => void;
 | 
			
		||||
export type ChooseNoteTypeCallback = (data: ChooseNoteTypeResponse) => void;
 | 
			
		||||
 | 
			
		||||
const SEPARATOR_TITLE_REPLACEMENTS = [
 | 
			
		||||
    t("note_type_chooser.builtin_templates"),
 | 
			
		||||
    t("note_type_chooser.templates")
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
interface NoteTypeChooserDialogProps {
 | 
			
		||||
    callback?: Callback;
 | 
			
		||||
}
 | 
			
		||||
function NoteTypeChooserDialogComponent() {
 | 
			
		||||
    const [ callback, setCallback ] = useState<ChooseNoteTypeCallback>();
 | 
			
		||||
    const [ shown, setShown ] = useState(false);
 | 
			
		||||
    const [ parentNote, setParentNote ] = useState<Suggestion | null>(); 
 | 
			
		||||
    const [ noteTypes, setNoteTypes ] = useState<MenuItem<TreeCommandNames>[]>([]);    
 | 
			
		||||
 | 
			
		||||
    useTriliumEvent("chooseNoteType", ({ callback }) => {
 | 
			
		||||
        setCallback(() => callback);
 | 
			
		||||
        setShown(true);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
function NoteTypeChooserDialogComponent({ callback }: NoteTypeChooserDialogProps) {
 | 
			
		||||
    const [ parentNote, setParentNote ] = useState<Suggestion>(); 
 | 
			
		||||
    const [ noteTypes, setNoteTypes ] = useState<MenuItem<TreeCommandNames>[]>([]);
 | 
			
		||||
    if (!noteTypes.length) {
 | 
			
		||||
        useEffect(() => {
 | 
			
		||||
            note_types.getNoteTypeItems().then(noteTypes => {
 | 
			
		||||
@ -72,7 +77,11 @@ function NoteTypeChooserDialogComponent({ callback }: NoteTypeChooserDialogProps
 | 
			
		||||
            size="md"
 | 
			
		||||
            zIndex={1100} // note type chooser needs to be higher than other dialogs from which it is triggered, e.g. "add link"
 | 
			
		||||
            scrollable
 | 
			
		||||
            onHidden={() => callback?.({ success: false })}
 | 
			
		||||
            onHidden={() => {
 | 
			
		||||
                callback?.({ success: false });
 | 
			
		||||
                setShown(false);
 | 
			
		||||
            }}
 | 
			
		||||
            show={shown}
 | 
			
		||||
        >
 | 
			
		||||
            <FormGroup label={t("note_type_chooser.change_path_prompt")}>
 | 
			
		||||
                <NoteAutocomplete
 | 
			
		||||
@ -114,16 +123,8 @@ function NoteTypeChooserDialogComponent({ callback }: NoteTypeChooserDialogProps
 | 
			
		||||
 | 
			
		||||
export default class NoteTypeChooserDialog extends ReactBasicWidget {
 | 
			
		||||
 | 
			
		||||
    private props: NoteTypeChooserDialogProps = {};
 | 
			
		||||
 | 
			
		||||
    get component() {
 | 
			
		||||
        return <NoteTypeChooserDialogComponent {...this.props} />
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async chooseNoteTypeEvent({ callback }: { callback: Callback }) {
 | 
			
		||||
        this.props = { callback };
 | 
			
		||||
        this.doRender();
 | 
			
		||||
        openDialog(this.$widget);
 | 
			
		||||
    }
 | 
			
		||||
        return <NoteTypeChooserDialogComponent />
 | 
			
		||||
    }    
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
import { useRef, useState } from "preact/hooks";
 | 
			
		||||
import { closeActiveDialog, openDialog } from "../../services/dialog";
 | 
			
		||||
import { t } from "../../services/i18n";
 | 
			
		||||
import Button from "../react/Button";
 | 
			
		||||
import Modal from "../react/Modal";
 | 
			
		||||
@ -8,6 +7,7 @@ import ReactBasicWidget from "../react/ReactBasicWidget";
 | 
			
		||||
import FormTextBox from "../react/FormTextBox";
 | 
			
		||||
import FormGroup from "../react/FormGroup";
 | 
			
		||||
import { refToJQuerySelector } from "../react/react_utils";
 | 
			
		||||
import useTriliumEvent from "../react/hooks";
 | 
			
		||||
 | 
			
		||||
// JQuery here is maintained for compatibility with existing code.
 | 
			
		||||
interface ShownCallbackData {
 | 
			
		||||
@ -27,24 +27,29 @@ export interface PromptDialogOptions {
 | 
			
		||||
    callback?: (value: string | null) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface PromptDialogProps extends PromptDialogOptions { }
 | 
			
		||||
 | 
			
		||||
function PromptDialogComponent({ title, message, shown: shownCallback, callback }: PromptDialogProps) {
 | 
			
		||||
function PromptDialogComponent() {    
 | 
			
		||||
    const modalRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
    const formRef = useRef<HTMLFormElement>(null);
 | 
			
		||||
    const labelRef = useRef<HTMLLabelElement>(null);
 | 
			
		||||
    const answerRef = useRef<HTMLInputElement>(null);
 | 
			
		||||
    const [ opts, setOpts ] = useState<PromptDialogOptions>();
 | 
			
		||||
    const [ value, setValue ] = useState("");    
 | 
			
		||||
    const [ shown, setShown ] = useState(false);
 | 
			
		||||
    
 | 
			
		||||
    useTriliumEvent("showPromptDialog", (opts) => {
 | 
			
		||||
        setOpts(opts);
 | 
			
		||||
        setShown(true);
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Modal
 | 
			
		||||
            className="prompt-dialog"
 | 
			
		||||
            title={title ?? t("prompt.title")}
 | 
			
		||||
            title={opts?.title ?? t("prompt.title")}
 | 
			
		||||
            size="lg"
 | 
			
		||||
            zIndex={2000}
 | 
			
		||||
            modalRef={modalRef} formRef={formRef}            
 | 
			
		||||
            onShown={() => {
 | 
			
		||||
                shownCallback?.({
 | 
			
		||||
                opts?.shown?.({
 | 
			
		||||
                    $dialog: refToJQuerySelector(modalRef),
 | 
			
		||||
                    $question: refToJQuerySelector(labelRef),
 | 
			
		||||
                    $answer: refToJQuerySelector(answerRef),
 | 
			
		||||
@ -56,12 +61,16 @@ function PromptDialogComponent({ title, message, shown: shownCallback, callback
 | 
			
		||||
                const modal = BootstrapModal.getOrCreateInstance(modalRef.current!);
 | 
			
		||||
                modal.hide();
 | 
			
		||||
 | 
			
		||||
                callback?.(value);
 | 
			
		||||
                opts?.callback?.(value);
 | 
			
		||||
            }}
 | 
			
		||||
            onHidden={() => {
 | 
			
		||||
                opts?.callback?.(null);
 | 
			
		||||
                setShown(false);
 | 
			
		||||
            }}
 | 
			
		||||
            onHidden={() => callback?.(null)}
 | 
			
		||||
            footer={<Button text={t("prompt.ok")} keyboardShortcut="Enter" primary />}
 | 
			
		||||
            show={shown}
 | 
			
		||||
        >
 | 
			
		||||
            <FormGroup label={message} labelRef={labelRef}>
 | 
			
		||||
            <FormGroup label={opts?.message} labelRef={labelRef}>
 | 
			
		||||
                <FormTextBox
 | 
			
		||||
                    name="prompt-dialog-answer"
 | 
			
		||||
                    inputRef={answerRef}
 | 
			
		||||
@ -73,16 +82,8 @@ function PromptDialogComponent({ title, message, shown: shownCallback, callback
 | 
			
		||||
 | 
			
		||||
export default class PromptDialog extends ReactBasicWidget {
 | 
			
		||||
 | 
			
		||||
    private props: PromptDialogProps = {};
 | 
			
		||||
 | 
			
		||||
    get component() {
 | 
			
		||||
        return <PromptDialogComponent {...this.props} />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    showPromptDialogEvent(props: PromptDialogOptions) {
 | 
			
		||||
        this.props = props;
 | 
			
		||||
        this.doRender();
 | 
			
		||||
        openDialog(this.$widget, false);
 | 
			
		||||
        return <PromptDialogComponent />;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user