mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 15:19:01 +02:00
feat(react/dialogs): port prompt
This commit is contained in:
parent
b3c81ce5f2
commit
bde4545afc
@ -244,7 +244,7 @@
|
|||||||
"prompt": {
|
"prompt": {
|
||||||
"title": "提示",
|
"title": "提示",
|
||||||
"close": "关闭",
|
"close": "关闭",
|
||||||
"ok": "确定 <kbd>回车</kbd>",
|
"ok": "确定",
|
||||||
"defaultTitle": "提示"
|
"defaultTitle": "提示"
|
||||||
},
|
},
|
||||||
"protected_session_password": {
|
"protected_session_password": {
|
||||||
|
@ -244,7 +244,7 @@
|
|||||||
"prompt": {
|
"prompt": {
|
||||||
"title": "Prompt",
|
"title": "Prompt",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"ok": "OK <kbd>Eingabe</kbd>",
|
"ok": "OK",
|
||||||
"defaultTitle": "Prompt"
|
"defaultTitle": "Prompt"
|
||||||
},
|
},
|
||||||
"protected_session_password": {
|
"protected_session_password": {
|
||||||
|
@ -250,7 +250,7 @@
|
|||||||
"prompt": {
|
"prompt": {
|
||||||
"title": "Prompt",
|
"title": "Prompt",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"ok": "OK <kbd>enter</kbd>",
|
"ok": "OK",
|
||||||
"defaultTitle": "Prompt"
|
"defaultTitle": "Prompt"
|
||||||
},
|
},
|
||||||
"protected_session_password": {
|
"protected_session_password": {
|
||||||
|
@ -247,7 +247,7 @@
|
|||||||
"prompt": {
|
"prompt": {
|
||||||
"title": "Aviso",
|
"title": "Aviso",
|
||||||
"close": "Cerrar",
|
"close": "Cerrar",
|
||||||
"ok": "Aceptar <kbd>enter</kbd>",
|
"ok": "Aceptar",
|
||||||
"defaultTitle": "Aviso"
|
"defaultTitle": "Aviso"
|
||||||
},
|
},
|
||||||
"protected_session_password": {
|
"protected_session_password": {
|
||||||
|
@ -244,7 +244,7 @@
|
|||||||
"prompt": {
|
"prompt": {
|
||||||
"title": "Prompt",
|
"title": "Prompt",
|
||||||
"close": "Fermer",
|
"close": "Fermer",
|
||||||
"ok": "OK <kbd>entrer</kbd>",
|
"ok": "OK",
|
||||||
"defaultTitle": "Prompt"
|
"defaultTitle": "Prompt"
|
||||||
},
|
},
|
||||||
"protected_session_password": {
|
"protected_session_password": {
|
||||||
|
@ -970,7 +970,7 @@
|
|||||||
},
|
},
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"defaultTitle": "Aviz",
|
"defaultTitle": "Aviz",
|
||||||
"ok": "OK <kbd>enter</kbd>",
|
"ok": "OK",
|
||||||
"title": "Aviz",
|
"title": "Aviz",
|
||||||
"close": "Închide"
|
"close": "Închide"
|
||||||
},
|
},
|
||||||
|
@ -222,7 +222,7 @@
|
|||||||
},
|
},
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"title": "提示",
|
"title": "提示",
|
||||||
"ok": "確定 <kbd>Enter</kbd>",
|
"ok": "確定",
|
||||||
"defaultTitle": "提示"
|
"defaultTitle": "提示"
|
||||||
},
|
},
|
||||||
"protected_session_password": {
|
"protected_session_password": {
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
import { openDialog } from "../../services/dialog.js";
|
|
||||||
import { t } from "../../services/i18n.js";
|
|
||||||
import BasicWidget from "../basic_widget.js";
|
|
||||||
import { Modal } from "bootstrap";
|
|
||||||
|
|
||||||
const TPL = /*html*/`
|
|
||||||
<div class="prompt-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
|
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form class="prompt-dialog-form">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="prompt-title modal-title">${t("prompt.title")}</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("prompt.close")}"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body"></div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="prompt-dialog-ok-button btn btn-primary btn-sm">${t("prompt.ok")}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
interface ShownCallbackData {
|
|
||||||
$dialog: JQuery<HTMLElement>;
|
|
||||||
$question: JQuery<HTMLElement> | null;
|
|
||||||
$answer: JQuery<HTMLElement> | null;
|
|
||||||
$form: JQuery<HTMLElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PromptDialogOptions {
|
|
||||||
title?: string;
|
|
||||||
message?: string;
|
|
||||||
defaultValue?: string;
|
|
||||||
shown?: PromptShownDialogCallback;
|
|
||||||
callback?: (value: string | null) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PromptShownDialogCallback = ((callback: ShownCallbackData) => void) | null;
|
|
||||||
|
|
||||||
export default class PromptDialog extends BasicWidget {
|
|
||||||
private resolve?: ((value: string | null) => void) | undefined | null;
|
|
||||||
private shownCb?: PromptShownDialogCallback | null;
|
|
||||||
|
|
||||||
private modal!: Modal;
|
|
||||||
private $dialogBody!: JQuery<HTMLElement>;
|
|
||||||
private $question!: JQuery<HTMLElement> | null;
|
|
||||||
private $answer!: JQuery<HTMLElement> | null;
|
|
||||||
private $form!: JQuery<HTMLElement>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.resolve = null;
|
|
||||||
this.shownCb = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
doRender() {
|
|
||||||
this.$widget = $(TPL);
|
|
||||||
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
|
|
||||||
this.$dialogBody = this.$widget.find(".modal-body");
|
|
||||||
this.$form = this.$widget.find(".prompt-dialog-form");
|
|
||||||
this.$question = null;
|
|
||||||
this.$answer = null;
|
|
||||||
|
|
||||||
this.$widget.on("shown.bs.modal", () => {
|
|
||||||
if (this.shownCb) {
|
|
||||||
this.shownCb({
|
|
||||||
$dialog: this.$widget,
|
|
||||||
$question: this.$question,
|
|
||||||
$answer: this.$answer,
|
|
||||||
$form: this.$form
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$answer?.trigger("focus").select();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$widget.on("hidden.bs.modal", () => {
|
|
||||||
if (this.resolve) {
|
|
||||||
this.resolve(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$form.on("submit", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (this.resolve) {
|
|
||||||
this.resolve(this.$answer?.val() as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.modal.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showPromptDialogEvent({ title, message, defaultValue, shown, callback }: PromptDialogOptions) {
|
|
||||||
this.shownCb = shown;
|
|
||||||
this.resolve = callback;
|
|
||||||
|
|
||||||
this.$widget.find(".prompt-title").text(title || t("prompt.defaultTitle"));
|
|
||||||
|
|
||||||
this.$question = $("<label>")
|
|
||||||
.prop("for", "prompt-dialog-answer")
|
|
||||||
.text(message || "");
|
|
||||||
|
|
||||||
this.$answer = $("<input>")
|
|
||||||
.prop("type", "text")
|
|
||||||
.prop("id", "prompt-dialog-answer")
|
|
||||||
.addClass("form-control")
|
|
||||||
.val(defaultValue || "");
|
|
||||||
|
|
||||||
this.$dialogBody.empty().append($("<div>").addClass("form-group").append(this.$question).append(this.$answer));
|
|
||||||
|
|
||||||
openDialog(this.$widget, false);
|
|
||||||
}
|
|
||||||
}
|
|
87
apps/client/src/widgets/dialogs/prompt.tsx
Normal file
87
apps/client/src/widgets/dialogs/prompt.tsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
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";
|
||||||
|
import { Modal as BootstrapModal } from "bootstrap";
|
||||||
|
import ReactBasicWidget from "../react/ReactBasicWidget";
|
||||||
|
import FormTextBox from "../react/FormTextBox";
|
||||||
|
import FormGroup from "../react/FormGroup";
|
||||||
|
|
||||||
|
// JQuery here is maintained for compatibility with existing code.
|
||||||
|
interface ShownCallbackData {
|
||||||
|
$dialog: JQuery<HTMLDivElement>;
|
||||||
|
$question: JQuery<HTMLLabelElement> | null;
|
||||||
|
$answer: JQuery<HTMLElement> | null;
|
||||||
|
$form: JQuery<HTMLFormElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PromptShownDialogCallback = ((callback: ShownCallbackData) => void) | null;
|
||||||
|
|
||||||
|
export interface PromptDialogOptions {
|
||||||
|
title?: string;
|
||||||
|
message?: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
shown?: PromptShownDialogCallback;
|
||||||
|
callback?: (value: string | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PromptDialogProps extends PromptDialogOptions { }
|
||||||
|
|
||||||
|
function PromptDialogComponent({ title, message, shown: shownCallback, callback }: PromptDialogProps) {
|
||||||
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
const labelRef = useRef<HTMLLabelElement>(null);
|
||||||
|
const answerRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [ value, setValue ] = useState("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="prompt-dialog"
|
||||||
|
title={title ?? t("prompt.title")}
|
||||||
|
size="lg"
|
||||||
|
zIndex={2000}
|
||||||
|
modalRef={modalRef} formRef={formRef}
|
||||||
|
onShown={() => {
|
||||||
|
shownCallback?.({
|
||||||
|
$dialog: $(modalRef.current),
|
||||||
|
$question: $(labelRef.current),
|
||||||
|
$answer: $(answerRef.current),
|
||||||
|
$form: $(formRef.current) as JQuery<HTMLFormElement>
|
||||||
|
});
|
||||||
|
answerRef.current?.focus();
|
||||||
|
}}
|
||||||
|
onSubmit={() => {
|
||||||
|
const modal = BootstrapModal.getOrCreateInstance(modalRef.current!);
|
||||||
|
modal.hide();
|
||||||
|
|
||||||
|
callback?.(value);
|
||||||
|
}}
|
||||||
|
onHidden={() => callback?.(null)}
|
||||||
|
footer={<Button text={t("prompt.ok")} keyboardShortcut="Enter" primary />}
|
||||||
|
>
|
||||||
|
<FormGroup label={message} labelRef={labelRef}>
|
||||||
|
<FormTextBox
|
||||||
|
name="prompt-dialog-answer"
|
||||||
|
inputRef={answerRef}
|
||||||
|
currentValue={value} onChange={setValue} />
|
||||||
|
</FormGroup>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { ComponentChildren } from "preact";
|
import { ComponentChildren, RefObject } from "preact";
|
||||||
|
|
||||||
interface FormGroupProps {
|
interface FormGroupProps {
|
||||||
|
labelRef?: RefObject<HTMLLabelElement>;
|
||||||
label: string;
|
label: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -8,10 +9,10 @@ interface FormGroupProps {
|
|||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FormGroup({ label, title, className, children, description }: FormGroupProps) {
|
export default function FormGroup({ label, title, className, children, description, labelRef }: FormGroupProps) {
|
||||||
return (
|
return (
|
||||||
<div className={`form-group ${className}`} title={title}>
|
<div className={`form-group ${className}`} title={title}>
|
||||||
<label style={{ width: "100%" }}>
|
<label style={{ width: "100%" }} ref={labelRef}>
|
||||||
{label}
|
{label}
|
||||||
{children}
|
{children}
|
||||||
</label>
|
</label>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HTMLInputTypeAttribute } from "preact/compat";
|
import { HTMLInputTypeAttribute, RefObject } from "preact/compat";
|
||||||
|
|
||||||
interface FormTextBoxProps {
|
interface FormTextBoxProps {
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -8,13 +8,15 @@ interface FormTextBoxProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
autoComplete?: string;
|
autoComplete?: string;
|
||||||
onChange?(newValue: string): void;
|
onChange?(newValue: string): void;
|
||||||
|
inputRef?: RefObject<HTMLInputElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FormTextBox({ id, type, name, className, currentValue, onChange, autoComplete }: FormTextBoxProps) {
|
export default function FormTextBox({ id, type, name, className, currentValue, onChange, autoComplete, inputRef }: FormTextBoxProps) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
|
ref={inputRef}
|
||||||
type={type ?? "text"}
|
type={type ?? "text"}
|
||||||
className={`form-control ${className}`}
|
className={`form-control ${className ?? ""}`}
|
||||||
id={id}
|
id={id}
|
||||||
name={name}
|
name={name}
|
||||||
value={currentValue}
|
value={currentValue}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef } from "preact/hooks";
|
import { useEffect, useRef } from "preact/hooks";
|
||||||
import { t } from "../../services/i18n";
|
import { t } from "../../services/i18n";
|
||||||
import { ComponentChildren } from "preact";
|
import { ComponentChildren } from "preact";
|
||||||
import type { CSSProperties } from "preact/compat";
|
import type { CSSProperties, RefObject } from "preact/compat";
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
className: string;
|
className: string;
|
||||||
@ -29,10 +29,20 @@ interface ModalProps {
|
|||||||
/** Called when the modal is hidden, either via close button, backdrop click or submit. */
|
/** Called when the modal is hidden, either via close button, backdrop click or submit. */
|
||||||
onHidden?: () => void;
|
onHidden?: () => void;
|
||||||
helpPageId?: string;
|
helpPageId?: string;
|
||||||
|
/**
|
||||||
|
* Gives access to the underlying modal element. This is useful for manipulating the modal directly
|
||||||
|
* or for attaching event listeners.
|
||||||
|
*/
|
||||||
|
modalRef?: RefObject<HTMLDivElement>;
|
||||||
|
/**
|
||||||
|
* Gives access to the underlying form element of the modal. This is only set if `onSubmit` is provided.
|
||||||
|
*/
|
||||||
|
formRef?: RefObject<HTMLFormElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Modal({ children, className, size, title, footer, footerAlignment, onShown, onSubmit, helpPageId, maxWidth, zIndex, scrollable, onHidden: onHidden }: ModalProps) {
|
export default function Modal({ children, className, size, title, footer, footerAlignment, onShown, onSubmit, helpPageId, maxWidth, zIndex, scrollable, onHidden: onHidden, modalRef: _modalRef, formRef: _formRef }: ModalProps) {
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = _modalRef ?? useRef<HTMLDivElement>(null);
|
||||||
|
const formRef = _formRef ?? useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
if (onShown || onHidden) {
|
if (onShown || onHidden) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -84,7 +94,7 @@ export default function Modal({ children, className, size, title, footer, footer
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{onSubmit ? (
|
{onSubmit ? (
|
||||||
<form onSubmit={(e) => {
|
<form ref={formRef} onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onSubmit();
|
onSubmit();
|
||||||
}}>
|
}}>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user