diff --git a/apps/client/src/widgets/dialogs/branch_prefix.ts b/apps/client/src/widgets/dialogs/branch_prefix.ts
deleted file mode 100644
index 496700a0c..000000000
--- a/apps/client/src/widgets/dialogs/branch_prefix.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import treeService from "../../services/tree.js";
-import server from "../../services/server.js";
-import froca from "../../services/froca.js";
-import toastService from "../../services/toast.js";
-import BasicWidget from "../basic_widget.js";
-import appContext from "../../components/app_context.js";
-import { t } from "../../services/i18n.js";
-import { Modal } from "bootstrap";
-import { openDialog } from "../../services/dialog.js";
-
-const TPL = /*html*/`
`;
-
-export default class BranchPrefixDialog extends BasicWidget {
- private modal!: Modal;
- private $form!: JQuery;
- private $treePrefixInput!: JQuery;
- private $noteTitle!: JQuery;
- private branchId: string | null = null;
-
- doRender() {
- this.$widget = $(TPL);
- this.modal = Modal.getOrCreateInstance(this.$widget[0]);
- this.$form = this.$widget.find(".branch-prefix-form");
- this.$treePrefixInput = this.$widget.find(".branch-prefix-input");
- this.$noteTitle = this.$widget.find(".branch-prefix-note-title");
-
- this.$form.on("submit", () => {
- this.savePrefix();
- return false;
- });
-
- this.$widget.on("shown.bs.modal", () => this.$treePrefixInput.trigger("focus"));
- }
-
- async refresh(notePath: string) {
- const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
-
- if (!noteId || !parentNoteId) {
- return;
- }
-
- const newBranchId = await froca.getBranchId(parentNoteId, noteId);
- if (!newBranchId) {
- return;
- }
- this.branchId = newBranchId;
-
- const branch = froca.getBranch(this.branchId);
- if (!branch || branch.noteId === "root") {
- return;
- }
-
- const parentNote = await froca.getNote(branch.parentNoteId);
- if (!parentNote || parentNote.type === "search") {
- return;
- }
-
- this.$treePrefixInput.val(branch.prefix || "");
-
- const noteTitle = await treeService.getNoteTitle(noteId);
- this.$noteTitle.text(` - ${noteTitle}`);
- }
-
- async editBranchPrefixEvent() {
- const notePath = appContext.tabManager.getActiveContextNotePath();
- if (!notePath) {
- return;
- }
-
- await this.refresh(notePath);
- openDialog(this.$widget);
- }
-
- async savePrefix() {
- const prefix = this.$treePrefixInput.val();
-
- await server.put(`branches/${this.branchId}/set-prefix`, { prefix: prefix });
-
- this.modal.hide();
-
- toastService.showMessage(t("branch_prefix.branch_prefix_saved"));
- }
-}
diff --git a/apps/client/src/widgets/dialogs/branch_prefix.tsx b/apps/client/src/widgets/dialogs/branch_prefix.tsx
new file mode 100644
index 000000000..5aba41c49
--- /dev/null
+++ b/apps/client/src/widgets/dialogs/branch_prefix.tsx
@@ -0,0 +1,92 @@
+import { useRef, useState } from "preact/hooks";
+import appContext from "../../components/app_context.js";
+import { closeActiveDialog, openDialog } from "../../services/dialog.js";
+import { t } from "../../services/i18n.js";
+import server from "../../services/server.js";
+import toast from "../../services/toast.js";
+import Modal from "../react/Modal.jsx";
+import ReactBasicWidget from "../react/ReactBasicWidget.js";
+import froca from "../../services/froca.js";
+import tree from "../../services/tree.js";
+import FBranch from "../../entities/fbranch.js";
+
+interface BranchPrefixDialogProps {
+ branch?: FBranch;
+}
+
+function BranchPrefixDialogComponent({ branch }: BranchPrefixDialogProps) {
+ const [ prefix, setPrefix ] = useState(branch?.prefix ?? "");
+ const branchInput = useRef(null);
+
+ async function onSubmit() {
+ if (!branch) {
+ return;
+ }
+
+ savePrefix(branch.branchId, prefix);
+ closeActiveDialog();
+ }
+
+ return (
+ branchInput.current?.focus()}
+ onSubmit={onSubmit}
+ footer={}
+ >
+
+
+ );
+}
+
+export default class BranchPrefixDialog extends ReactBasicWidget {
+ private branch?: FBranch;
+
+ get component() {
+ return ;
+ }
+
+ async editBranchPrefixEvent() {
+ const notePath = appContext.tabManager.getActiveContextNotePath();
+ if (!notePath) {
+ return;
+ }
+
+ const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
+
+ if (!noteId || !parentNoteId) {
+ return;
+ }
+
+ const newBranchId = await froca.getBranchId(parentNoteId, noteId);
+ if (!newBranchId) {
+ return;
+ }
+ const parentNote = await froca.getNote(parentNoteId);
+ if (!parentNote || parentNote.type === "search") {
+ return;
+ }
+
+ this.branch = froca.getBranch(newBranchId);
+
+ // Re-render the component with the new notePath
+ this.doRender();
+ openDialog(this.$widget);
+ }
+
+}
+
+async function savePrefix(branchId: string, prefix: string) {
+ await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
+ toast.showMessage(t("branch_prefix.branch_prefix_saved"));
+}
\ No newline at end of file
diff --git a/apps/client/src/widgets/react/Modal.tsx b/apps/client/src/widgets/react/Modal.tsx
index 0ffceb112..072b8b688 100644
--- a/apps/client/src/widgets/react/Modal.tsx
+++ b/apps/client/src/widgets/react/Modal.tsx
@@ -8,10 +8,15 @@ interface ModalProps {
size: "lg" | "sm";
children: ComponentChildren;
footer?: ComponentChildren;
+ /**
+ * If set, the modal body and footer will be wrapped in a form and the submit event will call this function.
+ * Especially useful for user input that can be submitted with Enter key.
+ */
+ onSubmit?: () => void;
onShown?: () => void;
}
-export default function Modal({ children, className, size, title, footer, onShown }: ModalProps) {
+export default function Modal({ children, className, size, title, footer, onShown, onSubmit }: ModalProps) {
const modalRef = useRef(null);
if (onShown) {
@@ -32,17 +37,36 @@ export default function Modal({ children, className, size, title, footer, onShow
-
- {children}
-
-
- {footer && (
-
- {footer}
-
+ {onSubmit ? (
+
+ ) : (
+
+ {children}
+
)}
);
+}
+
+function ModalInner({ children, footer }: Pick) {
+ return (
+ <>
+
+ {children}
+
+
+ {footer && (
+
+ {footer}
+
+ )}
+ >
+ );
}
\ No newline at end of file