refactor(react/dialogs): integrate self-triggering modal in more dialogs

This commit is contained in:
Elian Doran 2025-08-10 00:32:26 +03:00
parent cd5467bf5c
commit fa97ec6c72
No known key found for this signature in database
14 changed files with 241 additions and 244 deletions

View File

@ -1,4 +1,3 @@
import { openDialog } from "../../services/dialog.js";
import ReactBasicWidget from "../react/ReactBasicWidget.js"; import ReactBasicWidget from "../react/ReactBasicWidget.js";
import Modal from "../react/Modal.js"; import Modal from "../react/Modal.js";
import { t } from "../../services/i18n.js"; import { t } from "../../services/i18n.js";
@ -9,19 +8,26 @@ import openService from "../../services/open.js";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import type { CSSProperties } from "preact/compat"; import type { CSSProperties } from "preact/compat";
import type { AppInfo } from "@triliumnext/commons"; import type { AppInfo } from "@triliumnext/commons";
import useTriliumEvent from "../react/hooks.jsx";
function AboutDialogComponent() { function AboutDialogComponent() {
let [appInfo, setAppInfo] = useState<AppInfo | null>(null); let [appInfo, setAppInfo] = useState<AppInfo | null>(null);
let [shown, setShown] = useState(false);
async function onShown() {
const appInfo = await server.get<AppInfo>("app-info");
setAppInfo(appInfo);
}
const forceWordBreak: CSSProperties = { wordBreak: "break-all" }; const forceWordBreak: CSSProperties = { wordBreak: "break-all" };
useTriliumEvent("openAboutDialog", () => setShown(true));
return ( return (
<Modal className="about-dialog" size="lg" title={t("about.title")} onShown={onShown}> <Modal className="about-dialog"
size="lg"
title={t("about.title")}
show={shown}
onShown={async () => {
const appInfo = await server.get<AppInfo>("app-info");
setAppInfo(appInfo);
}}
onHidden={() => setShown(false)}
>
<table className="table table-borderless"> <table className="table table-borderless">
<tbody> <tbody>
<tr> <tr>
@ -83,7 +89,4 @@ export default class AboutDialog extends ReactBasicWidget {
return <AboutDialogComponent />; return <AboutDialogComponent />;
} }
async openAboutDialogEvent() {
openDialog(this.$widget);
}
} }

View File

@ -1,5 +1,4 @@
import { EventData } from "../../components/app_context"; import { closeActiveDialog } from "../../services/dialog";
import { closeActiveDialog, openDialog } from "../../services/dialog";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import Modal from "../react/Modal"; import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget"; import ReactBasicWidget from "../react/ReactBasicWidget";
@ -10,24 +9,28 @@ import { useRef, useState } from "preact/hooks";
import tree from "../../services/tree"; import tree from "../../services/tree";
import { useEffect } from "react"; import { useEffect } from "react";
import note_autocomplete, { Suggestion } from "../../services/note_autocomplete"; import note_autocomplete, { Suggestion } from "../../services/note_autocomplete";
import type { default as TextTypeWidget } from "../type_widgets/editable_text.js"; import { default as TextTypeWidget } from "../type_widgets/editable_text.js";
import { logError } from "../../services/ws"; import { logError } from "../../services/ws";
import FormGroup from "../react/FormGroup.js"; import FormGroup from "../react/FormGroup.js";
import { refToJQuerySelector } from "../react/react_utils"; import { refToJQuerySelector } from "../react/react_utils";
import useTriliumEvent from "../react/hooks";
type LinkType = "reference-link" | "external-link" | "hyper-link"; type LinkType = "reference-link" | "external-link" | "hyper-link";
interface AddLinkDialogProps { function AddLinkDialogComponent() {
text?: string; const [ textTypeWidget, setTextTypeWidget ] = useState<TextTypeWidget>();
textTypeWidget?: TextTypeWidget; const [ text, setText ] = useState<string>();
}
function AddLinkDialogComponent({ text: _text, textTypeWidget }: AddLinkDialogProps) {
const [ text, setText ] = useState(_text ?? "");
const [ linkTitle, setLinkTitle ] = useState(""); const [ linkTitle, setLinkTitle ] = useState("");
const hasSelection = textTypeWidget?.hasSelection(); const hasSelection = textTypeWidget?.hasSelection();
const [ linkType, setLinkType ] = useState<LinkType>(hasSelection ? "hyper-link" : "reference-link"); const [ linkType, setLinkType ] = useState<LinkType>(hasSelection ? "hyper-link" : "reference-link");
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null); const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
const [ shown, setShown ] = useState(false);
useTriliumEvent("showAddLinkDialog", ( { textTypeWidget, text }) => {
setTextTypeWidget(textTypeWidget);
setText(text);
setShown(true);
});
async function setDefaultLinkTitle(noteId: string) { async function setDefaultLinkTitle(noteId: string) {
const noteTitle = await tree.getNoteTitle(noteId); const noteTitle = await tree.getNoteTitle(noteId);
@ -100,7 +103,11 @@ function AddLinkDialogComponent({ text: _text, textTypeWidget }: AddLinkDialogPr
footer={<Button text={t("add_link.button_add_link")} keyboardShortcut="Enter" />} footer={<Button text={t("add_link.button_add_link")} keyboardShortcut="Enter" />}
onSubmit={onSubmit} onSubmit={onSubmit}
onShown={onShown} onShown={onShown}
onHidden={() => setSuggestion(null)} onHidden={() => {
setSuggestion(null);
setShown(false);
}}
show={shown}
> >
<FormGroup label={t("add_link.note")}> <FormGroup label={t("add_link.note")}>
<NoteAutocomplete <NoteAutocomplete
@ -150,18 +157,9 @@ function AddLinkDialogComponent({ text: _text, textTypeWidget }: AddLinkDialogPr
} }
export default class AddLinkDialog extends ReactBasicWidget { export default class AddLinkDialog extends ReactBasicWidget {
private props: AddLinkDialogProps = {};
get component() { get component() {
return <AddLinkDialogComponent {...this.props} />; return <AddLinkDialogComponent />;
}
async showAddLinkDialogEvent({ textTypeWidget, text = "" }: EventData<"showAddLinkDialog">) {
this.props.text = text;
this.props.textTypeWidget = textTypeWidget;
this.doRender();
await openDialog(this.$widget);
} }
} }

View File

@ -1,6 +1,5 @@
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { EventData } from "../../components/app_context"; import { closeActiveDialog } from "../../services/dialog";
import { closeActiveDialog, openDialog } from "../../services/dialog";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import Modal from "../react/Modal"; import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget"; import ReactBasicWidget from "../react/ReactBasicWidget";
@ -12,50 +11,52 @@ import Button from "../react/Button";
import bulk_action from "../../services/bulk_action"; import bulk_action from "../../services/bulk_action";
import toast from "../../services/toast"; import toast from "../../services/toast";
import RenameNoteBulkAction from "../bulk_actions/note/rename_note"; import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
import { RawHtmlBlock } from "../react/RawHtml";
import FNote from "../../entities/fnote"; import FNote from "../../entities/fnote";
import froca from "../../services/froca"; import froca from "../../services/froca";
import useTriliumEvent from "../react/hooks"; import useTriliumEvent from "../react/hooks";
interface BulkActionProps { function BulkActionComponent() {
bulkActionNote?: FNote | null; const [ selectedOrActiveNoteIds, setSelectedOrActiveNoteIds ] = useState<string[]>();
selectedOrActiveNoteIds?: string[]; const [ bulkActionNote, setBulkActionNote ] = useState<FNote | null>();
}
function BulkActionComponent({ selectedOrActiveNoteIds, bulkActionNote }: BulkActionProps) {
const [ includeDescendants, setIncludeDescendants ] = useState(false); const [ includeDescendants, setIncludeDescendants ] = useState(false);
const [ affectedNoteCount, setAffectedNoteCount ] = useState(0); const [ affectedNoteCount, setAffectedNoteCount ] = useState(0);
const [ existingActions, setExistingActions ] = useState<RenameNoteBulkAction[]>([]); const [ existingActions, setExistingActions ] = useState<RenameNoteBulkAction[]>([]);
const [ shown, setShown ] = useState(false);
if (!selectedOrActiveNoteIds || !bulkActionNote) { useTriliumEvent("openBulkActionsDialog", async ({ selectedOrActiveNoteIds }) => {
return; setSelectedOrActiveNoteIds(selectedOrActiveNoteIds);
} setBulkActionNote(await froca.getNote("_bulkAction"));
setShown(true);
useEffect(() => {
server.post<BulkActionAffectedNotes>("bulk-action/affected-notes", {
noteIds: selectedOrActiveNoteIds,
includeDescendants
}).then(({ affectedNoteCount }) => setAffectedNoteCount(affectedNoteCount));
}, [ selectedOrActiveNoteIds, includeDescendants ]);
function refreshExistingActions() {
setExistingActions(bulk_action.parseActions(bulkActionNote!));
}
useEffect(() => refreshExistingActions(), []);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().find((row) =>
row.type === "label" && row.name === "action" && row.noteId === "_bulkAction")) {
refreshExistingActions();
}
}); });
return ( selectedOrActiveNoteIds && if (selectedOrActiveNoteIds && bulkActionNote) {
useEffect(() => {
server.post<BulkActionAffectedNotes>("bulk-action/affected-notes", {
noteIds: selectedOrActiveNoteIds,
includeDescendants
}).then(({ affectedNoteCount }) => setAffectedNoteCount(affectedNoteCount));
}, [ selectedOrActiveNoteIds, includeDescendants ]);
function refreshExistingActions() {
setExistingActions(bulk_action.parseActions(bulkActionNote!));
}
useEffect(() => refreshExistingActions(), []);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().find((row) =>
row.type === "label" && row.name === "action" && row.noteId === "_bulkAction")) {
refreshExistingActions();
}
});
}
return (
<Modal <Modal
className="bulk-actions-dialog" className="bulk-actions-dialog"
size="xl" size="xl"
title={t("bulk_actions.bulk_actions")} title={t("bulk_actions.bulk_actions")}
footer={<Button text={t("bulk_actions.execute_bulk_actions")} primary />} footer={<Button text={t("bulk_actions.execute_bulk_actions")} primary />}
show={shown}
onSubmit={async () => { onSubmit={async () => {
await server.post("bulk-action/execute", { await server.post("bulk-action/execute", {
noteIds: selectedOrActiveNoteIds, noteIds: selectedOrActiveNoteIds,
@ -65,6 +66,7 @@ function BulkActionComponent({ selectedOrActiveNoteIds, bulkActionNote }: BulkAc
toast.showMessage(t("bulk_actions.bulk_actions_executed"), 3000); toast.showMessage(t("bulk_actions.bulk_actions_executed"), 3000);
closeActiveDialog(); closeActiveDialog();
}} }}
onHidden={() => setShown(false)}
> >
<h4>{t("bulk_actions.affected_notes")}: <span>{affectedNoteCount}</span></h4> <h4>{t("bulk_actions.affected_notes")}: <span>{affectedNoteCount}</span></h4>
<FormCheckbox <FormCheckbox
@ -114,19 +116,8 @@ function ExistingActionsList({ existingActions }: { existingActions?: RenameNote
export default class BulkActionsDialog extends ReactBasicWidget { export default class BulkActionsDialog extends ReactBasicWidget {
private props: BulkActionProps = {};
get component() { get component() {
return <BulkActionComponent {...this.props} /> return <BulkActionComponent />
}
async openBulkActionsDialogEvent({ selectedOrActiveNoteIds }: EventData<"openBulkActionsDialog">) {
this.props = {
selectedOrActiveNoteIds,
bulkActionNote: await froca.getNote("_bulkAction")
};
this.doRender();
openDialog(this.$widget);
} }
} }

View File

@ -1,6 +1,6 @@
import { useRef, useState } from "preact/compat"; import { useRef, useState } from "preact/compat";
import appContext, { EventData } from "../../components/app_context"; import appContext from "../../components/app_context";
import { closeActiveDialog, openDialog } from "../../services/dialog"; import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import Modal from "../react/Modal"; import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget"; import ReactBasicWidget from "../react/ReactBasicWidget";
@ -15,16 +15,32 @@ import tree from "../../services/tree";
import branches from "../../services/branches"; import branches from "../../services/branches";
import toast from "../../services/toast"; import toast from "../../services/toast";
import NoteList from "../react/NoteList"; import NoteList from "../react/NoteList";
import useTriliumEvent from "../react/hooks";
interface CloneToDialogProps { function CloneToDialogComponent() {
clonedNoteIds?: string[]; const [ clonedNoteIds, setClonedNoteIds ] = useState<string[]>();
}
function CloneToDialogComponent({ clonedNoteIds }: CloneToDialogProps) {
const [ prefix, setPrefix ] = useState(""); const [ prefix, setPrefix ] = useState("");
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null); const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
const [ shown, setShown ] = useState(false);
const autoCompleteRef = useRef<HTMLInputElement>(null); const autoCompleteRef = useRef<HTMLInputElement>(null);
useTriliumEvent("cloneNoteIdsTo", ({ noteIds }) => {
if (!noteIds || noteIds.length === 0) {
noteIds = [appContext.tabManager.getActiveContextNoteId() ?? ""];
}
const clonedNoteIds: string[] = [];
for (const noteId of noteIds) {
if (!clonedNoteIds.includes(noteId)) {
clonedNoteIds.push(noteId);
}
}
setClonedNoteIds(clonedNoteIds);
setShown(true);
});
function onSubmit() { function onSubmit() {
if (!clonedNoteIds) { if (!clonedNoteIds) {
return; return;
@ -49,6 +65,8 @@ function CloneToDialogComponent({ clonedNoteIds }: CloneToDialogProps) {
footer={<Button text={t("clone_to.clone_to_selected_note")} keyboardShortcut="Enter" />} footer={<Button text={t("clone_to.clone_to_selected_note")} keyboardShortcut="Enter" />}
onSubmit={onSubmit} onSubmit={onSubmit}
onShown={() => triggerRecentNotes(autoCompleteRef.current)} onShown={() => triggerRecentNotes(autoCompleteRef.current)}
onHidden={() => setShown(false)}
show={shown}
> >
<h5>{t("clone_to.notes_to_clone")}</h5> <h5>{t("clone_to.notes_to_clone")}</h5>
<NoteList style={{ maxHeight: "200px", overflow: "auto" }} noteIds={clonedNoteIds} /> <NoteList style={{ maxHeight: "200px", overflow: "auto" }} noteIds={clonedNoteIds} />
@ -67,29 +85,9 @@ function CloneToDialogComponent({ clonedNoteIds }: CloneToDialogProps) {
} }
export default class CloneToDialog extends ReactBasicWidget { export default class CloneToDialog extends ReactBasicWidget {
private props: CloneToDialogProps = {};
get component() { get component() {
return <CloneToDialogComponent {...this.props} />; return <CloneToDialogComponent />;
}
async cloneNoteIdsToEvent({ noteIds }: EventData<"cloneNoteIdsTo">) {
if (!noteIds || noteIds.length === 0) {
noteIds = [appContext.tabManager.getActiveContextNoteId() ?? ""];
}
const clonedNoteIds: string[] = [];
for (const noteId of noteIds) {
if (!clonedNoteIds.includes(noteId)) {
clonedNoteIds.push(noteId);
}
}
this.props = { clonedNoteIds };
this.doRender();
openDialog(this.$widget);
} }
} }

View File

@ -1,4 +1,3 @@
import { openDialog } from "../../services/dialog.js";
import ReactBasicWidget from "../react/ReactBasicWidget.js"; import ReactBasicWidget from "../react/ReactBasicWidget.js";
import Modal from "../react/Modal.jsx"; import Modal from "../react/Modal.jsx";
import { t } from "../../services/i18n.js"; import { t } from "../../services/i18n.js";
@ -7,10 +6,18 @@ import { CommandNames } from "../../components/app_context.js";
import RawHtml from "../react/RawHtml.jsx"; import RawHtml from "../react/RawHtml.jsx";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import keyboard_actions from "../../services/keyboard_actions.js"; import keyboard_actions from "../../services/keyboard_actions.js";
import useTriliumEvent from "../react/hooks.jsx";
function HelpDialogComponent() { function HelpDialogComponent() {
const [ shown, setShown ] = useState(false);
useTriliumEvent("showCheatsheet", () => setShown(true));
return ( return (
<Modal title={t("help.title")} className="help-dialog use-tn-links" minWidth="90%" size="lg" scrollable> <Modal
title={t("help.title")} className="help-dialog use-tn-links" minWidth="90%" size="lg" scrollable
onHidden={() => setShown(false)}
show={shown}
>
<div className="help-cards row row-cols-md-3 g-3"> <div className="help-cards row row-cols-md-3 g-3">
<Card title={t("help.noteNavigation")}> <Card title={t("help.noteNavigation")}>
<ul> <ul>
@ -161,7 +168,4 @@ export default class HelpDialog extends ReactBasicWidget {
return <HelpDialogComponent />; return <HelpDialogComponent />;
} }
showCheatsheetEvent() {
openDialog(this.$widget);
}
} }

View File

@ -1,13 +1,17 @@
import { useRef } from "react"; import { useRef } from "react";
import { closeActiveDialog, openDialog } from "../../services/dialog.js"; import { closeActiveDialog } from "../../services/dialog.js";
import { t } from "../../services/i18n.js"; import { t } from "../../services/i18n.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import Button from "../react/Button.js"; import Button from "../react/Button.js";
import Modal from "../react/Modal.js"; import Modal from "../react/Modal.js";
import ReactBasicWidget from "../react/ReactBasicWidget.js"; import ReactBasicWidget from "../react/ReactBasicWidget.js";
import { useState } from "preact/hooks";
import useTriliumEvent from "../react/hooks.jsx";
function IncorrectCpuArchDialogComponent() { function IncorrectCpuArchDialogComponent() {
const [ shown, setShown ] = useState(false);
const downloadButtonRef = useRef<HTMLButtonElement>(null); const downloadButtonRef = useRef<HTMLButtonElement>(null);
useTriliumEvent("showCpuArchWarning", () => setShown(true));
return ( return (
<Modal <Modal
@ -33,6 +37,8 @@ function IncorrectCpuArchDialogComponent() {
<Button text={t("cpu_arch_warning.continue_anyway")} <Button text={t("cpu_arch_warning.continue_anyway")}
onClick={() => closeActiveDialog()} /> onClick={() => closeActiveDialog()} />
</>} </>}
onHidden={() => setShown(false)}
show={shown}
> >
<p>{utils.isMac() ? t("cpu_arch_warning.message_macos") : t("cpu_arch_warning.message_windows")}</p> <p>{utils.isMac() ? t("cpu_arch_warning.message_macos") : t("cpu_arch_warning.message_windows")}</p>
<p>{t("cpu_arch_warning.recommendation")}</p> <p>{t("cpu_arch_warning.recommendation")}</p>
@ -46,8 +52,4 @@ export default class IncorrectCpuArchDialog extends ReactBasicWidget {
return <IncorrectCpuArchDialogComponent /> return <IncorrectCpuArchDialogComponent />
} }
showCpuArchWarningEvent() {
openDialog(this.$widget);
}
} }

View File

@ -1,6 +1,6 @@
import { useRef, useState } from "preact/compat"; import { useCallback, useRef, useState } from "preact/compat";
import appContext from "../../components/app_context"; import appContext from "../../components/app_context";
import { closeActiveDialog, openDialog } from "../../services/dialog"; import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import server from "../../services/server"; import server from "../../services/server";
import toast from "../../services/toast"; import toast from "../../services/toast";
@ -8,6 +8,7 @@ import utils from "../../services/utils";
import Modal from "../react/Modal"; import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget"; import ReactBasicWidget from "../react/ReactBasicWidget";
import Button from "../react/Button"; import Button from "../react/Button";
import useTriliumEvent from "../react/hooks";
interface RenderMarkdownResponse { interface RenderMarkdownResponse {
htmlContent: string; htmlContent: string;
@ -15,7 +16,26 @@ interface RenderMarkdownResponse {
function MarkdownImportDialogComponent() { function MarkdownImportDialogComponent() {
const markdownImportTextArea = useRef<HTMLTextAreaElement>(null); const markdownImportTextArea = useRef<HTMLTextAreaElement>(null);
let [ text, setText ] = useState(""); let [ text, setText ] = useState("");
let [ shown, setShown ] = useState(false);
const triggerImport = useCallback(() => {
if (appContext.tabManager.getActiveContextNoteType() !== "text") {
return;
}
if (utils.isElectron()) {
const { clipboard } = utils.dynamicRequire("electron");
const text = clipboard.readText();
convertMarkdownToHtml(text);
} else {
setShown(true);
}
}, []);
useTriliumEvent("importMarkdownInline", triggerImport);
useTriliumEvent("pasteMarkdownIntoText", triggerImport);
async function sendForm() { async function sendForm() {
await convertMarkdownToHtml(text); await convertMarkdownToHtml(text);
@ -28,6 +48,8 @@ function MarkdownImportDialogComponent() {
className="markdown-import-dialog" title={t("markdown_import.dialog_title")} size="lg" className="markdown-import-dialog" title={t("markdown_import.dialog_title")} size="lg"
footer={<Button className="markdown-import-button" text={t("markdown_import.import_button")} onClick={sendForm} keyboardShortcut="Ctrl+Space" />} footer={<Button className="markdown-import-button" text={t("markdown_import.import_button")} onClick={sendForm} keyboardShortcut="Ctrl+Space" />}
onShown={() => markdownImportTextArea.current?.focus()} onShown={() => markdownImportTextArea.current?.focus()}
onHidden={() => setShown(false) }
show={shown}
> >
<p>{t("markdown_import.modal_body_text")}</p> <p>{t("markdown_import.modal_body_text")}</p>
<textarea ref={markdownImportTextArea} value={text} <textarea ref={markdownImportTextArea} value={text}
@ -49,26 +71,6 @@ export default class MarkdownImportDialog extends ReactBasicWidget {
return <MarkdownImportDialogComponent />; return <MarkdownImportDialogComponent />;
} }
async importMarkdownInlineEvent() {
if (appContext.tabManager.getActiveContextNoteType() !== "text") {
return;
}
if (utils.isElectron()) {
const { clipboard } = utils.dynamicRequire("electron");
const text = clipboard.readText();
convertMarkdownToHtml(text);
} else {
openDialog(this.$widget);
}
}
async pasteMarkdownIntoTextEvent() {
// BC with keyboard shortcuts command
await this.importMarkdownInlineEvent();
}
} }
async function convertMarkdownToHtml(markdownContent: string) { async function convertMarkdownToHtml(markdownContent: string) {

View File

@ -1,8 +1,7 @@
import ReactBasicWidget from "../react/ReactBasicWidget"; import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal"; import Modal from "../react/Modal";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import { closeActiveDialog, openDialog } from "../../services/dialog"; import { closeActiveDialog } from "../../services/dialog";
import { EventData } from "../../components/app_context";
import NoteList from "../react/NoteList"; import NoteList from "../react/NoteList";
import FormGroup from "../react/FormGroup"; import FormGroup from "../react/FormGroup";
import NoteAutocomplete from "../react/NoteAutocomplete"; import NoteAutocomplete from "../react/NoteAutocomplete";
@ -13,15 +12,19 @@ import tree from "../../services/tree";
import froca from "../../services/froca"; import froca from "../../services/froca";
import branches from "../../services/branches"; import branches from "../../services/branches";
import toast from "../../services/toast"; import toast from "../../services/toast";
import useTriliumEvent from "../react/hooks";
interface MoveToDialogProps { function MoveToDialogComponent() {
movedBranchIds?: string[]; const [ movedBranchIds, setMovedBranchIds ] = useState<string[]>();
}
function MoveToDialogComponent({ movedBranchIds }: MoveToDialogProps) {
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null); const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
const [ shown, setShown ] = useState(false);
const autoCompleteRef = useRef<HTMLInputElement>(null); const autoCompleteRef = useRef<HTMLInputElement>(null);
useTriliumEvent("moveBranchIdsTo", ({ branchIds }) => {
setMovedBranchIds(branchIds);
setShown(true);
});
async function onSubmit() { async function onSubmit() {
const notePath = suggestion?.notePath; const notePath = suggestion?.notePath;
if (!notePath) { if (!notePath) {
@ -49,6 +52,8 @@ function MoveToDialogComponent({ movedBranchIds }: MoveToDialogProps) {
footer={<Button text={t("move_to.move_button")} keyboardShortcut="Enter" />} footer={<Button text={t("move_to.move_button")} keyboardShortcut="Enter" />}
onSubmit={onSubmit} onSubmit={onSubmit}
onShown={() => triggerRecentNotes(autoCompleteRef.current)} onShown={() => triggerRecentNotes(autoCompleteRef.current)}
onHidden={() => setShown(false)}
show={shown}
> >
<h5>{t("move_to.notes_to_move")}</h5> <h5>{t("move_to.notes_to_move")}</h5>
<NoteList branchIds={movedBranchIds} /> <NoteList branchIds={movedBranchIds} />
@ -65,17 +70,8 @@ function MoveToDialogComponent({ movedBranchIds }: MoveToDialogProps) {
export default class MoveToDialog extends ReactBasicWidget { export default class MoveToDialog extends ReactBasicWidget {
private props: MoveToDialogProps = {};
get component() { get component() {
return <MoveToDialogComponent {...this.props} />; return <MoveToDialogComponent />;
}
async moveBranchIdsToEvent({ branchIds }: EventData<"moveBranchIdsTo">) {
const movedBranchIds = branchIds;
this.props = { movedBranchIds };
this.doRender();
openDialog(this.$widget);
} }
} }

View File

@ -1,11 +1,16 @@
import { closeActiveDialog, openDialog } from "../../services/dialog"; import { closeActiveDialog } from "../../services/dialog";
import ReactBasicWidget from "../react/ReactBasicWidget"; import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal"; import Modal from "../react/Modal";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import Button from "../react/Button"; import Button from "../react/Button";
import appContext from "../../components/app_context"; import appContext from "../../components/app_context";
import { useState } from "preact/hooks";
import useTriliumEvent from "../react/hooks";
function PasswordNotSetDialogComponent() { function PasswordNotSetDialogComponent() {
const [ shown, setShown ] = useState(false);
useTriliumEvent("showPasswordNotSet", () => setShown(true));
return ( return (
<Modal <Modal
size="md" className="password-not-set-dialog" size="md" className="password-not-set-dialog"
@ -14,6 +19,8 @@ function PasswordNotSetDialogComponent() {
closeActiveDialog(); closeActiveDialog();
appContext.triggerCommand("showOptions", { section: "_optionsPassword" }); appContext.triggerCommand("showOptions", { section: "_optionsPassword" });
}} />} }} />}
onHidden={() => setShown(false)}
show={shown}
> >
<p>{t("password_not_set.body1")}</p> <p>{t("password_not_set.body1")}</p>
<p>{t("password_not_set.body2")}</p> <p>{t("password_not_set.body2")}</p>
@ -27,8 +34,4 @@ export default class PasswordNotSetDialog extends ReactBasicWidget {
return <PasswordNotSetDialogComponent />; return <PasswordNotSetDialogComponent />;
} }
showPasswordNotSetEvent() {
openDialog(this.$widget);
}
} }

View File

@ -1,16 +1,20 @@
import { useRef, useState } from "preact/hooks"; import { useRef, useState } from "preact/hooks";
import { closeActiveDialog, openDialog } from "../../services/dialog"; import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import Button from "../react/Button"; import Button from "../react/Button";
import FormTextBox from "../react/FormTextBox"; import FormTextBox from "../react/FormTextBox";
import Modal from "../react/Modal"; import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget"; import ReactBasicWidget from "../react/ReactBasicWidget";
import protected_session from "../../services/protected_session"; import protected_session from "../../services/protected_session";
import useTriliumEvent from "../react/hooks";
function ProtectedSessionPasswordDialogComponent() { function ProtectedSessionPasswordDialogComponent() {
const [ shown, setShown ] = useState(false);
const [ password, setPassword ] = useState(""); const [ password, setPassword ] = useState("");
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useTriliumEvent("showProtectedSessionPasswordDialog", () => setShown(true));
return ( return (
<Modal <Modal
className="protected-session-password-dialog" className="protected-session-password-dialog"
@ -20,6 +24,8 @@ function ProtectedSessionPasswordDialogComponent() {
footer={<Button text={t("protected_session_password.start_button")} />} footer={<Button text={t("protected_session_password.start_button")} />}
onSubmit={() => protected_session.setupProtectedSession(password)} onSubmit={() => protected_session.setupProtectedSession(password)}
onShown={() => inputRef.current?.focus()} onShown={() => inputRef.current?.focus()}
onHidden={() => setShown(false)}
show={shown}
> >
<label htmlFor="protected-session-password" className="col-form-label">{t("protected_session_password.form_label")}</label> <label htmlFor="protected-session-password" className="col-form-label">{t("protected_session_password.form_label")}</label>
<FormTextBox <FormTextBox
@ -39,10 +45,6 @@ export default class ProtectedSessionPasswordDialog extends ReactBasicWidget {
return <ProtectedSessionPasswordDialogComponent />; return <ProtectedSessionPasswordDialogComponent />;
} }
showProtectedSessionPasswordDialogEvent() {
openDialog(this.$widget);
}
closeProtectedSessionPasswordDialogEvent() { closeProtectedSessionPasswordDialogEvent() {
closeActiveDialog(); closeActiveDialog();
} }

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import appContext, { EventData } from "../../components/app_context"; import appContext from "../../components/app_context";
import dialog, { closeActiveDialog, openDialog } from "../../services/dialog"; import dialog, { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import server from "../../services/server"; import server from "../../services/server";
import toast from "../../services/toast"; import toast from "../../services/toast";
@ -14,14 +14,19 @@ import { formatDateTime } from "../../utils/formatters";
import link from "../../services/link"; import link from "../../services/link";
import RawHtml from "../react/RawHtml"; import RawHtml from "../react/RawHtml";
import ws from "../../services/ws"; import ws from "../../services/ws";
import useTriliumEvent from "../react/hooks";
interface RecentChangesDialogProps { function RecentChangesDialogComponent() {
ancestorNoteId?: string; const [ ancestorNoteId, setAncestorNoteId ] = useState<string>();
}
function RecentChangesDialogComponent({ ancestorNoteId }: RecentChangesDialogProps) {
const [ groupedByDate, setGroupedByDate ] = useState<Map<String, RecentChangesRow[]>>(); const [ groupedByDate, setGroupedByDate ] = useState<Map<String, RecentChangesRow[]>>();
const [ needsRefresh, setNeedsRefresh ] = useState<boolean>(false); const [ needsRefresh, setNeedsRefresh ] = useState(false);
const [ shown, setShown ] = useState(false);
useTriliumEvent("showRecentChanges", ({ ancestorNoteId }) => {
setNeedsRefresh(true);
setAncestorNoteId(ancestorNoteId ?? hoisted_note.getHoistedNoteId());
setShown(true);
});
if (!groupedByDate || needsRefresh) { if (!groupedByDate || needsRefresh) {
useEffect(() => { useEffect(() => {
@ -43,7 +48,7 @@ function RecentChangesDialogComponent({ ancestorNoteId }: RecentChangesDialogPro
}) })
} }
return (ancestorNoteId && return (
<Modal <Modal
title={t("recent_changes.title")} title={t("recent_changes.title")}
className="recent-changes-dialog" className="recent-changes-dialog"
@ -61,6 +66,8 @@ function RecentChangesDialogComponent({ ancestorNoteId }: RecentChangesDialogPro
}} }}
/> />
} }
onHidden={() => setShown(false)}
show={shown}
> >
<div className="recent-changes-content"> <div className="recent-changes-content">
{groupedByDate?.size {groupedByDate?.size
@ -152,18 +159,8 @@ function DeletedNoteLink({ change }: { change: RecentChangesRow }) {
export default class RecentChangesDialog extends ReactBasicWidget { export default class RecentChangesDialog extends ReactBasicWidget {
private props: RecentChangesDialogProps = {};
get component() { get component() {
return <RecentChangesDialogComponent {...this.props} /> return <RecentChangesDialogComponent />
}
async showRecentChangesEvent({ ancestorNoteId }: EventData<"showRecentChanges">) {
this.props = {
ancestorNoteId: ancestorNoteId ?? hoisted_note.getHoistedNoteId()
};
this.doRender();
openDialog(this.$widget);
} }
} }

View File

@ -1,7 +1,7 @@
import type { RevisionPojo, RevisionItem } from "@triliumnext/commons"; import type { RevisionPojo, RevisionItem } from "@triliumnext/commons";
import appContext, { EventData } from "../../components/app_context"; import appContext from "../../components/app_context";
import FNote from "../../entities/fnote"; import FNote from "../../entities/fnote";
import dialog, { closeActiveDialog, openDialog } from "../../services/dialog"; import dialog, { closeActiveDialog } from "../../services/dialog";
import froca from "../../services/froca"; import froca from "../../services/froca";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import server from "../../services/server"; import server from "../../services/server";
@ -18,26 +18,35 @@ import { CSSProperties } from "preact/compat";
import open from "../../services/open"; import open from "../../services/open";
import ActionButton from "../react/ActionButton"; import ActionButton from "../react/ActionButton";
import options from "../../services/options"; import options from "../../services/options";
import useTriliumEvent from "../react/hooks";
interface RevisionsDialogProps { function RevisionsDialogComponent() {
note?: FNote; const [ note, setNote ] = useState<FNote>();
} const [ revisions, setRevisions ] = useState<RevisionItem[]>();
function RevisionsDialogComponent({ note }: RevisionsDialogProps) {
const [ revisions, setRevisions ] = useState<RevisionItem[]>([]);
const [ currentRevision, setCurrentRevision ] = useState<RevisionItem>(); const [ currentRevision, setCurrentRevision ] = useState<RevisionItem>();
const [ shown, setShown ] = useState(false);
if (note) { useTriliumEvent("showRevisions", async ({ noteId }) => {
useEffect(() => { const note = await getNote(noteId);
if (note) {
setNote(note);
setShown(true);
}
});
useEffect(() => {
if (note?.noteId) {
server.get<RevisionItem[]>(`notes/${note.noteId}/revisions`).then(setRevisions); server.get<RevisionItem[]>(`notes/${note.noteId}/revisions`).then(setRevisions);
}, [ note.noteId ]); } else {
} setRevisions(undefined);
}
}, [ note?.noteId ]);
if (revisions?.length && !currentRevision) { if (revisions?.length && !currentRevision) {
setCurrentRevision(revisions[0]); setCurrentRevision(revisions[0]);
} }
return (note && return (
<Modal <Modal
className="revisions-dialog" className="revisions-dialog"
size="xl" size="xl"
@ -49,7 +58,7 @@ function RevisionsDialogComponent({ note }: RevisionsDialogProps) {
onClick={async () => { onClick={async () => {
const text = t("revisions.confirm_delete_all"); const text = t("revisions.confirm_delete_all");
if (await dialog.confirm(text)) { if (note && await dialog.confirm(text)) {
await server.remove(`notes/${note.noteId}/revisions`); await server.remove(`notes/${note.noteId}/revisions`);
closeActiveDialog(); closeActiveDialog();
@ -59,11 +68,16 @@ function RevisionsDialogComponent({ note }: RevisionsDialogProps) {
} }
footer={<RevisionFooter note={note} />} footer={<RevisionFooter note={note} />}
footerStyle={{ paddingTop: 0, paddingBottom: 0 }} footerStyle={{ paddingTop: 0, paddingBottom: 0 }}
onHidden={() => {
setShown(false);
setNote(undefined);
}}
show={shown}
> >
<RevisionsList <RevisionsList
revisions={revisions} revisions={revisions ?? []}
onSelect={(revisionId) => { onSelect={(revisionId) => {
const correspondingRevision = revisions.find((r) => r.revisionId === revisionId); const correspondingRevision = (revisions ?? []).find((r) => r.revisionId === revisionId);
if (correspondingRevision) { if (correspondingRevision) {
setCurrentRevision(correspondingRevision); setCurrentRevision(correspondingRevision);
} }
@ -239,7 +253,7 @@ function RevisionContent({ revisionItem, fullRevision }: { revisionItem?: Revisi
} }
} }
function RevisionFooter({ note }: { note: FNote }) { function RevisionFooter({ note }: { note?: FNote }) {
if (!note) { if (!note) {
return <></>; return <></>;
} }
@ -268,18 +282,8 @@ function RevisionFooter({ note }: { note: FNote }) {
export default class RevisionsDialog extends ReactBasicWidget { export default class RevisionsDialog extends ReactBasicWidget {
private props: RevisionsDialogProps = {};
get component() { get component() {
return <RevisionsDialogComponent {...this.props} /> return <RevisionsDialogComponent />
}
async showRevisionsEvent({ noteId }: EventData<"showRevisions">) {
this.props = {
note: await getNote(noteId) ?? undefined
};
this.doRender();
openDialog(this.$widget);
} }
} }

View File

@ -1,6 +1,5 @@
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { EventData } from "../../components/app_context"; import { closeActiveDialog } from "../../services/dialog";
import { closeActiveDialog, openDialog } from "../../services/dialog";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import Button from "../react/Button"; import Button from "../react/Button";
import FormCheckbox from "../react/FormCheckbox"; import FormCheckbox from "../react/FormCheckbox";
@ -10,13 +9,21 @@ import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget"; import ReactBasicWidget from "../react/ReactBasicWidget";
import server from "../../services/server"; import server from "../../services/server";
import FormGroup from "../react/FormGroup"; import FormGroup from "../react/FormGroup";
import useTriliumEvent from "../react/hooks";
function SortChildNotesDialogComponent({ parentNoteId }: { parentNoteId?: string }) { function SortChildNotesDialogComponent() {
const [ parentNoteId, setParentNoteId ] = useState<string>();
const [ sortBy, setSortBy ] = useState("title"); const [ sortBy, setSortBy ] = useState("title");
const [ sortDirection, setSortDirection ] = useState("asc"); const [ sortDirection, setSortDirection ] = useState("asc");
const [ foldersFirst, setFoldersFirst ] = useState(false); const [ foldersFirst, setFoldersFirst ] = useState(false);
const [ sortNatural, setSortNatural ] = useState(false); const [ sortNatural, setSortNatural ] = useState(false);
const [ sortLocale, setSortLocale ] = useState(""); const [ sortLocale, setSortLocale ] = useState("");
const [ shown, setShown ] = useState(false);
useTriliumEvent("sortChildNotes", ({ node }) => {
setParentNoteId(node.data.noteId);
setShown(true);
});
async function onSubmit() { async function onSubmit() {
await server.put(`notes/${parentNoteId}/sort-children`, { await server.put(`notes/${parentNoteId}/sort-children`, {
@ -31,12 +38,14 @@ function SortChildNotesDialogComponent({ parentNoteId }: { parentNoteId?: string
closeActiveDialog(); closeActiveDialog();
} }
return (parentNoteId && return (
<Modal <Modal
className="sort-child-notes-dialog" className="sort-child-notes-dialog"
title={t("sort_child_notes.sort_children_by")} title={t("sort_child_notes.sort_children_by")}
size="lg" maxWidth={500} size="lg" maxWidth={500}
onSubmit={onSubmit} onSubmit={onSubmit}
onHidden={() => setShown(false)}
show={shown}
footer={<Button text={t("sort_child_notes.sort")} keyboardShortcut="Enter" />} footer={<Button text={t("sort_child_notes.sort")} keyboardShortcut="Enter" />}
> >
<h5>{t("sort_child_notes.sorting_criteria")}</h5> <h5>{t("sort_child_notes.sorting_criteria")}</h5>
@ -88,17 +97,8 @@ function SortChildNotesDialogComponent({ parentNoteId }: { parentNoteId?: string
export default class SortChildNotesDialog extends ReactBasicWidget { export default class SortChildNotesDialog extends ReactBasicWidget {
private parentNoteId?: string;
get component() { get component() {
return <SortChildNotesDialogComponent parentNoteId={this.parentNoteId} />; return <SortChildNotesDialogComponent />;
} }
async sortChildNotesEvent({ node }: EventData<"sortChildNotes">) {
this.parentNoteId = node.data.noteId;
this.doRender();
openDialog(this.$widget);
}
} }

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "preact/compat"; import { useEffect, useState } from "preact/compat";
import { closeActiveDialog, openDialog } from "../../services/dialog"; import { closeActiveDialog } from "../../services/dialog";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import Button from "../react/Button"; import Button from "../react/Button";
import FormCheckbox from "../react/FormCheckbox"; import FormCheckbox from "../react/FormCheckbox";
@ -9,18 +9,21 @@ import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget"; import ReactBasicWidget from "../react/ReactBasicWidget";
import options from "../../services/options"; import options from "../../services/options";
import importService from "../../services/import.js"; import importService from "../../services/import.js";
import { EventData } from "../../components/app_context";
import tree from "../../services/tree"; import tree from "../../services/tree";
import useTriliumEvent from "../react/hooks";
interface UploadAttachmentsDialogProps { function UploadAttachmentsDialogComponent() {
parentNoteId?: string; const [ parentNoteId, setParentNoteId ] = useState<string>();
}
function UploadAttachmentsDialogComponent({ parentNoteId }: UploadAttachmentsDialogProps) {
const [ files, setFiles ] = useState<FileList | null>(null); const [ files, setFiles ] = useState<FileList | null>(null);
const [ shrinkImages, setShrinkImages ] = useState(options.is("compressImages")); const [ shrinkImages, setShrinkImages ] = useState(options.is("compressImages"));
const [ isUploading, setIsUploading ] = useState(false); const [ isUploading, setIsUploading ] = useState(false);
const [ description, setDescription ] = useState<string | undefined>(undefined); const [ description, setDescription ] = useState<string | undefined>(undefined);
const [ shown, setShown ] = useState(false);
useTriliumEvent("showUploadAttachmentsDialog", ({ noteId }) => {
setParentNoteId(noteId);
setShown(true);
});
if (parentNoteId) { if (parentNoteId) {
useEffect(() => { useEffect(() => {
@ -29,23 +32,25 @@ function UploadAttachmentsDialogComponent({ parentNoteId }: UploadAttachmentsDia
}, [parentNoteId]); }, [parentNoteId]);
} }
return (parentNoteId && return (
<Modal <Modal
className="upload-attachments-dialog" className="upload-attachments-dialog"
size="lg" size="lg"
title={t("upload_attachments.upload_attachments_to_note")} title={t("upload_attachments.upload_attachments_to_note")}
footer={<Button text={t("upload_attachments.upload")} primary disabled={!files || isUploading} />} footer={<Button text={t("upload_attachments.upload")} primary disabled={!files || isUploading} />}
onSubmit={async () => { onSubmit={async () => {
if (!files) { if (!files || !parentNoteId) {
return; return;
} }
setIsUploading(true); setIsUploading(true);
const filesCopy = Array.from(files); const filesCopy = Array.from(files);
await importService.uploadFiles("attachments", parentNoteId, filesCopy, { shrinkImages }); await importService.uploadFiles("attachments", parentNoteId, filesCopy, { shrinkImages });
setIsUploading(false); setIsUploading(false);
closeActiveDialog(); closeActiveDialog();
}} }}
onHidden={() => setShown(false)}
show={shown}
> >
<FormGroup label={t("upload_attachments.choose_files")} description={description}> <FormGroup label={t("upload_attachments.choose_files")} description={description}>
<FormFileUpload onChange={setFiles} multiple /> <FormFileUpload onChange={setFiles} multiple />
@ -64,16 +69,8 @@ function UploadAttachmentsDialogComponent({ parentNoteId }: UploadAttachmentsDia
export default class UploadAttachmentsDialog extends ReactBasicWidget { export default class UploadAttachmentsDialog extends ReactBasicWidget {
private props: UploadAttachmentsDialogProps = {};
get component() { get component() {
return <UploadAttachmentsDialogComponent {...this.props} />; return <UploadAttachmentsDialogComponent />;
}
showUploadAttachmentsDialogEvent({ noteId }: EventData<"showUploadAttachmentsDialog">) {
this.props = { parentNoteId: noteId };
this.doRender();
openDialog(this.$widget);
} }
} }