From 5d9bd0f6d355e0c8ec617eacf3713e6b8cfa9ca5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 3 Aug 2025 19:44:15 +0300 Subject: [PATCH] feat(react): port branch prefix --- .../src/widgets/dialogs/branch_prefix.ts | 108 ------------------ .../src/widgets/dialogs/branch_prefix.tsx | 92 +++++++++++++++ apps/client/src/widgets/react/Modal.tsx | 42 +++++-- 3 files changed, 125 insertions(+), 117 deletions(-) delete mode 100644 apps/client/src/widgets/dialogs/branch_prefix.ts create mode 100644 apps/client/src/widgets/dialogs/branch_prefix.tsx 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={} + > +
+   + +
+ setPrefix((e.target as HTMLInputElement).value)} /> +
- {branch && branch.getNoteFromCache().title}
+
+
+
+ ); +} + +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 ? ( +
{ + e.preventDefault(); + onSubmit(); + }}> + {children} +
+ ) : ( + + {children} + )} ); +} + +function ModalInner({ children, footer }: Pick) { + return ( + <> +
+ {children} +
+ + {footer && ( +
+ {footer} +
+ )} + + ); } \ No newline at end of file