refactor(react/dialogs): integrate proper closing of modal

This commit is contained in:
Elian Doran 2025-08-10 12:22:11 +03:00
parent b7482f2a6a
commit da1f18c60f
No known key found for this signature in database
22 changed files with 46 additions and 60 deletions

View File

@ -1,4 +1,3 @@
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget";
@ -80,11 +79,11 @@ function AddLinkDialogComponent() {
function onSubmit() {
if (suggestion?.notePath) {
// Handle note link
closeActiveDialog();
setShown(false);
textTypeWidget?.addLink(suggestion.notePath, linkType === "reference-link" ? null : linkTitle);
} else if (suggestion?.externalLink) {
// Handle external link
closeActiveDialog();
setShown(false);
textTypeWidget?.addLink(suggestion.externalLink, linkTitle, true);
} else {
logError("No link to add.");

View File

@ -1,6 +1,5 @@
import { useRef, useState } from "preact/hooks";
import appContext from "../../components/app_context.js";
import { closeActiveDialog } from "../../services/dialog.js";
import { t } from "../../services/i18n.js";
import server from "../../services/server.js";
import toast from "../../services/toast.js";
@ -50,7 +49,7 @@ function BranchPrefixDialogComponent() {
}
savePrefix(branch.branchId, prefix);
closeActiveDialog();
setShown(false);
}
return (

View File

@ -1,5 +1,4 @@
import { useEffect, useState } from "preact/hooks";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget";
@ -64,7 +63,7 @@ function BulkActionComponent() {
});
toast.showMessage(t("bulk_actions.bulk_actions_executed"), 3000);
closeActiveDialog();
setShown(false);
}}
onHidden={() => setShown(false)}
>

View File

@ -1,6 +1,5 @@
import { useRef, useState } from "preact/compat";
import appContext from "../../components/app_context";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget";
@ -52,7 +51,7 @@ function CloneToDialogComponent() {
return;
}
closeActiveDialog();
setShown(false);
cloneNotesTo(notePath, clonedNoteIds, prefix);
}

View File

@ -1,7 +1,6 @@
import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal";
import Button from "../react/Button";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import { useState } from "react";
import FormCheckbox from "../react/FormCheckbox";
@ -48,10 +47,10 @@ function ConfirmDialogComponent() {
setShown(false);
}}
footer={<>
<Button text={t("confirm.cancel")} onClick={() => closeActiveDialog()} />
<Button text={t("confirm.cancel")} onClick={() => setShown(false)} />
<Button text={t("confirm.ok")} onClick={() => {
setConfirmed(true);
closeActiveDialog();
setShown(false);
}} />
</>}
show={shown}

View File

@ -1,5 +1,4 @@
import { useRef, useState } from "preact/hooks";
import { closeActiveDialog } from "../../services/dialog.js";
import { t } from "../../services/i18n.js";
import FormCheckbox from "../react/FormCheckbox.js";
import Modal from "../react/Modal.js";
@ -74,12 +73,12 @@ function DeleteNotesDialogComponent() {
}}
footer={<>
<Button text={t("delete_notes.cancel")}
onClick={() => closeActiveDialog()} />
onClick={() => setShown(false)} />
<Button text={t("delete_notes.ok")} primary
buttonRef={okButtonRef}
onClick={() => {
opts.callback?.({ proceed: true, deleteAllClones, eraseNotes });
closeActiveDialog();
setShown(false);
}} />
</>}
show={shown}

View File

@ -1,5 +1,4 @@
import { useState } from "preact/hooks";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import tree from "../../services/tree";
import Button from "../react/Button";
@ -57,7 +56,7 @@ function ExportDialogComponent() {
const format = (exportType === "subtree" ? subtreeFormat : singleFormat);
const version = (format === "opml" ? opmlVersion : "1.0");
exportBranch(opts.branchId, exportType, format, version);
closeActiveDialog();
setShown(false);
}}
onHidden={() => setShown(false)}
footer={<Button className="export-button" text={t("export.export")} primary />}

View File

@ -1,5 +1,4 @@
import { useState } from "preact/hooks";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import tree from "../../services/tree";
import Button from "../react/Button";
@ -49,7 +48,7 @@ function ImportDialogComponent() {
replaceUnderscoresWithSpaces: boolToString(replaceUnderscoresWithSpaces)
};
closeActiveDialog();
setShown(false);
await importService.uploadFiles("notes", parentNoteId, Array.from(files), options);
}}
onHidden={() => setShown(false)}

View File

@ -1,5 +1,4 @@
import { useRef, useState } from "preact/compat";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import FormGroup from "../react/FormGroup";
import FormRadioGroup from "../react/FormRadioGroup";
@ -38,7 +37,7 @@ function IncludeNoteDialogComponent() {
return;
}
closeActiveDialog();
setShown(false);
includeNote(suggestion.notePath, textTypeWidget);
}}
footer={<Button text={t("include_note.button_include")} keyboardShortcut="Enter" />}

View File

@ -1,5 +1,4 @@
import { useRef } from "react";
import { closeActiveDialog } from "../../services/dialog.js";
import { t } from "../../services/i18n.js";
import utils from "../../services/utils.js";
import Button from "../react/Button.js";
@ -35,7 +34,7 @@ function IncorrectCpuArchDialogComponent() {
}
}}/>
<Button text={t("cpu_arch_warning.continue_anyway")}
onClick={() => closeActiveDialog()} />
onClick={() => setShown(false)} />
</>}
onHidden={() => setShown(false)}
show={shown}

View File

@ -1,6 +1,5 @@
import { EventData } from "../../components/app_context";
import ReactBasicWidget from "../react/ReactBasicWidget";
import { closeActiveDialog } from "../../services/dialog";
import Modal from "../react/Modal";
import { t } from "../../services/i18n";
import Button from "../react/Button";
@ -30,7 +29,7 @@ function ShowInfoDialogComponent() {
footer={<Button
buttonRef={okButtonRef}
text={t("info.okButton")}
onClick={() => closeActiveDialog()}
onClick={() => setShown(false)}
/>}
show={shown}
>

View File

@ -1,4 +1,3 @@
import { closeActiveDialog } from "../../services/dialog";
import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal";
import Button from "../react/Button";
@ -54,10 +53,10 @@ function JumpToNoteDialogComponent() {
}, [ text ]);
async function onItemSelected(suggestion: Suggestion) {
setShown(false);
if (suggestion.notePath) {
appContext.tabManager.getActiveContext()?.setNote(suggestion.notePath);
} else if (suggestion.commandId) {
closeActiveDialog();
await commandRegistry.executeCommand(suggestion.commandId);
}
}

View File

@ -1,6 +1,5 @@
import { useCallback, useRef, useState } from "preact/compat";
import appContext from "../../components/app_context";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import server from "../../services/server";
import toast from "../../services/toast";
@ -40,7 +39,7 @@ function MarkdownImportDialogComponent() {
async function sendForm() {
await convertMarkdownToHtml(text);
setText("");
closeActiveDialog();
setShown(false);
}
return (

View File

@ -1,7 +1,6 @@
import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal";
import { t } from "../../services/i18n";
import { closeActiveDialog } from "../../services/dialog";
import NoteList from "../react/NoteList";
import FormGroup from "../react/FormGroup";
import NoteAutocomplete from "../react/NoteAutocomplete";
@ -32,7 +31,7 @@ function MoveToDialogComponent() {
return;
}
closeActiveDialog();
setShown(false);
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
if (!parentNoteId) {
return;

View File

@ -1,6 +1,5 @@
import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import FormGroup from "../react/FormGroup";
import NoteAutocomplete from "../react/NoteAutocomplete";
@ -67,7 +66,7 @@ function NoteTypeChooserDialogComponent() {
templateNoteId,
notePath: parentNote?.notePath
});
closeActiveDialog();
setShown(false);
}
return (

View File

@ -1,4 +1,3 @@
import { closeActiveDialog } from "../../services/dialog";
import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal";
import { t } from "../../services/i18n";
@ -16,7 +15,7 @@ function PasswordNotSetDialogComponent() {
size="md" className="password-not-set-dialog"
title={t("password_not_set.title")}
footer={<Button icon="bx bx-lock" text={t("password_not_set.go_to_password_options")} onClick={() => {
closeActiveDialog();
setShown(false);
appContext.triggerCommand("showOptions", { section: "_optionsPassword" });
}} />}
onHidden={() => setShown(false)}

View File

@ -1,5 +1,4 @@
import { useRef, useState } from "preact/hooks";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import Button from "../react/Button";
import FormTextBox from "../react/FormTextBox";
@ -14,6 +13,7 @@ function ProtectedSessionPasswordDialogComponent() {
const inputRef = useRef<HTMLInputElement>(null);
useTriliumEvent("showProtectedSessionPasswordDialog", () => setShown(true));
useTriliumEvent("closeProtectedSessionPasswordDialog", () => setShown(false));
return (
<Modal
@ -45,8 +45,4 @@ export default class ProtectedSessionPasswordDialog extends ReactBasicWidget {
return <ProtectedSessionPasswordDialogComponent />;
}
closeProtectedSessionPasswordDialogEvent() {
closeActiveDialog();
}
}

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "preact/hooks";
import { Dispatch, StateUpdater, useEffect, useState } from "preact/hooks";
import appContext from "../../components/app_context";
import dialog, { closeActiveDialog } from "../../services/dialog";
import dialog from "../../services/dialog";
import { t } from "../../services/i18n";
import server from "../../services/server";
import toast from "../../services/toast";
@ -71,14 +71,14 @@ function RecentChangesDialogComponent() {
>
<div className="recent-changes-content">
{groupedByDate?.size
? <RecentChangesTimeline groupedByDate={groupedByDate} />
? <RecentChangesTimeline groupedByDate={groupedByDate} setShown={setShown} />
: <>{t("recent_changes.no_changes_message")}</>}
</div>
</Modal>
)
}
function RecentChangesTimeline({ groupedByDate }: { groupedByDate: Map<String, RecentChangesRow[]> }) {
function RecentChangesTimeline({ groupedByDate, setShown }: { groupedByDate: Map<String, RecentChangesRow[]>, setShown: Dispatch<StateUpdater<boolean>> }) {
return (
<>
{ Array.from(groupedByDate.entries()).map(([dateDay, dayChanges]) => {
@ -100,7 +100,7 @@ function RecentChangesTimeline({ groupedByDate }: { groupedByDate: Map<String, R
<span title={change.date}>{formattedTime}</span>
{ !isDeleted
? <NoteLink notePath={notePath} title={change.current_title} />
: <DeletedNoteLink change={change} /> }
: <DeletedNoteLink change={change} setShown={setShown} /> }
</li>
);
})}
@ -129,7 +129,7 @@ function NoteLink({ notePath, title }: { notePath: string, title: string }) {
);
}
function DeletedNoteLink({ change }: { change: RecentChangesRow }) {
function DeletedNoteLink({ change, setShown }: { change: RecentChangesRow, setShown: Dispatch<StateUpdater<boolean>> }) {
return (
<>
<span className="note-title">{change.current_title}</span>
@ -141,7 +141,7 @@ function DeletedNoteLink({ change }: { change: RecentChangesRow }) {
if (await dialog.confirm(text)) {
await server.put(`notes/${change.noteId}/undelete`);
closeActiveDialog();
setShown(false);
await ws.waitForMaxKnownEntityChangeId();
const activeContext = appContext.tabManager.getActiveContext();

View File

@ -1,7 +1,7 @@
import type { RevisionPojo, RevisionItem } from "@triliumnext/commons";
import appContext from "../../components/app_context";
import FNote from "../../entities/fnote";
import dialog, { closeActiveDialog } from "../../services/dialog";
import dialog from "../../services/dialog";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import server from "../../services/server";
@ -11,7 +11,7 @@ import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget";
import FormList, { FormListItem } from "../react/FormList";
import utils from "../../services/utils";
import { useEffect, useRef, useState } from "preact/hooks";
import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks";
import protected_session_holder from "../../services/protected_session_holder";
import { renderMathInElement } from "../../services/math";
import { CSSProperties } from "preact/compat";
@ -61,7 +61,7 @@ function RevisionsDialogComponent() {
if (note && await dialog.confirm(text)) {
await server.remove(`notes/${note.noteId}/revisions`);
closeActiveDialog();
setShown(false);
toast.showMessage(t("revisions.revisions_deleted"));
}
}}/>)
@ -91,7 +91,7 @@ function RevisionsDialogComponent() {
flexDirection: "column",
minWidth: 0
}}>
<RevisionPreview revisionItem={currentRevision} />
<RevisionPreview revisionItem={currentRevision} setShown={setShown} />
</div>
</Modal>
)
@ -111,7 +111,7 @@ function RevisionsList({ revisions, onSelect }: { revisions: RevisionItem[], onS
</FormList>);
}
function RevisionPreview({ revisionItem }: { revisionItem?: RevisionItem}) {
function RevisionPreview({ revisionItem, setShown }: { revisionItem?: RevisionItem, setShown: Dispatch<StateUpdater<boolean>>}) {
const [ fullRevision, setFullRevision ] = useState<RevisionPojo>();
const [ needsRefresh, setNeedsRefresh ] = useState<boolean>();
@ -137,7 +137,7 @@ function RevisionPreview({ revisionItem }: { revisionItem?: RevisionItem}) {
onClick={async () => {
if (await dialog.confirm(t("revisions.confirm_restore"))) {
await server.post(`revisions/${revisionItem.revisionId}/restore`);
closeActiveDialog();
setShown(false);
toast.showMessage(t("revisions.revision_restored"));
}
}}/>

View File

@ -1,5 +1,4 @@
import { useState } from "preact/hooks";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import Button from "../react/Button";
import FormCheckbox from "../react/FormCheckbox";
@ -34,8 +33,7 @@ function SortChildNotesDialogComponent() {
sortLocale
});
// Close the dialog after submission
closeActiveDialog();
setShown(false);
}
return (

View File

@ -1,5 +1,4 @@
import { useEffect, useState } from "preact/compat";
import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import Button from "../react/Button";
import FormCheckbox from "../react/FormCheckbox";
@ -47,7 +46,7 @@ function UploadAttachmentsDialogComponent() {
const filesCopy = Array.from(files);
await importService.uploadFiles("attachments", parentNoteId, filesCopy, { shrinkImages });
setIsUploading(false);
closeActiveDialog();
setShown(false);
}}
onHidden={() => setShown(false)}
show={shown}

View File

@ -4,6 +4,7 @@ import { ComponentChildren } from "preact";
import type { CSSProperties, RefObject } from "preact/compat";
import { openDialog } from "../../services/dialog";
import { ParentComponent } from "./ReactBasicWidget";
import { Modal as BootstrapModal } from "bootstrap";
interface ModalProps {
className: string;
@ -52,6 +53,7 @@ interface ModalProps {
export default function Modal({ children, className, size, title, header, footer, footerStyle, footerAlignment, onShown, onSubmit, helpPageId, minWidth, maxWidth, zIndex, scrollable, onHidden: onHidden, modalRef: _modalRef, formRef: _formRef, bodyStyle, show }: ModalProps) {
const modalRef = _modalRef ?? useRef<HTMLDivElement>(null);
const modalInstanceRef = useRef<BootstrapModal>();
const formRef = _formRef ?? useRef<HTMLFormElement>(null);
const parentWidget = useContext(ParentComponent);
@ -79,8 +81,15 @@ export default function Modal({ children, className, size, title, header, footer
}
useEffect(() => {
if (show && parentWidget) {
openDialog(parentWidget.$widget);
if (!parentWidget) {
return;
}
if (show) {
openDialog(parentWidget.$widget).then(($widget) => {
modalInstanceRef.current = BootstrapModal.getOrCreateInstance($widget[0]);
})
} else {
modalInstanceRef.current?.hide();
}
}, [ show ]);