mirror of
https://github.com/zadam/trilium.git
synced 2025-10-30 01:36:24 +01:00
refactor(react/dialogs): use shown everywhere
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -161,20 +172,8 @@ 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,20 +92,8 @@ 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,16 +72,8 @@ 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({ callback }: NoteTypeChooserDialogProps) {
|
||||
const [ parentNote, setParentNote ] = useState<Suggestion>();
|
||||
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);
|
||||
});
|
||||
|
||||
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 />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user