| 
									
										
										
										
											2025-08-10 15:21:49 +03:00
										 |  |  | import { useRef, useState } from "preact/hooks"; | 
					
						
							| 
									
										
										
										
											2025-08-10 00:32:26 +03:00
										 |  |  | import appContext from "../../components/app_context"; | 
					
						
							| 
									
										
										
										
											2025-08-04 21:17:35 +03:00
										 |  |  | import { t } from "../../services/i18n"; | 
					
						
							|  |  |  | import Modal from "../react/Modal"; | 
					
						
							|  |  |  | import ReactBasicWidget from "../react/ReactBasicWidget"; | 
					
						
							|  |  |  | import NoteAutocomplete from "../react/NoteAutocomplete"; | 
					
						
							|  |  |  | import froca from "../../services/froca"; | 
					
						
							|  |  |  | import FormGroup from "../react/FormGroup"; | 
					
						
							|  |  |  | import FormTextBox from "../react/FormTextBox"; | 
					
						
							|  |  |  | import Button from "../react/Button"; | 
					
						
							| 
									
										
										
										
											2025-08-05 21:06:12 +03:00
										 |  |  | import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete"; | 
					
						
							| 
									
										
										
										
											2025-08-04 21:17:35 +03:00
										 |  |  | import { logError } from "../../services/ws"; | 
					
						
							|  |  |  | import tree from "../../services/tree"; | 
					
						
							|  |  |  | import branches from "../../services/branches"; | 
					
						
							|  |  |  | import toast from "../../services/toast"; | 
					
						
							| 
									
										
										
										
											2025-08-04 22:37:31 +03:00
										 |  |  | import NoteList from "../react/NoteList"; | 
					
						
							| 
									
										
										
										
											2025-08-10 00:32:26 +03:00
										 |  |  | import useTriliumEvent from "../react/hooks"; | 
					
						
							| 
									
										
										
										
											2025-08-04 21:17:35 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-10 00:32:26 +03:00
										 |  |  | function CloneToDialogComponent() { | 
					
						
							|  |  |  |     const [ clonedNoteIds, setClonedNoteIds ] = useState<string[]>(); | 
					
						
							| 
									
										
										
										
											2025-08-04 21:17:35 +03:00
										 |  |  |     const [ prefix, setPrefix ] = useState(""); | 
					
						
							| 
									
										
										
										
											2025-08-06 18:38:52 +03:00
										 |  |  |     const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null); | 
					
						
							| 
									
										
										
										
											2025-08-10 00:32:26 +03:00
										 |  |  |     const [ shown, setShown ] = useState(false); | 
					
						
							| 
									
										
										
										
											2025-08-04 21:17:35 +03:00
										 |  |  |     const autoCompleteRef = useRef<HTMLInputElement>(null); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-10 00:32:26 +03:00
										 |  |  |     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); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 21:17:35 +03:00
										 |  |  |     function onSubmit() { | 
					
						
							| 
									
										
										
										
											2025-08-06 18:38:52 +03:00
										 |  |  |         if (!clonedNoteIds) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-04 21:17:35 +03:00
										 |  |  |         const notePath = suggestion?.notePath; | 
					
						
							|  |  |  |         if (!notePath) { | 
					
						
							|  |  |  |             logError(t("clone_to.no_path_to_clone_to")); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-10 12:22:11 +03:00
										 |  |  |         setShown(false); | 
					
						
							| 
									
										
										
										
											2025-08-04 21:17:35 +03:00
										 |  |  |         cloneNotesTo(notePath, clonedNoteIds, prefix); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |         <Modal | 
					
						
							|  |  |  |             className="clone-to-dialog" | 
					
						
							|  |  |  |             title={t("clone_to.clone_notes_to")} | 
					
						
							|  |  |  |             helpPageId="IakOLONlIfGI" | 
					
						
							|  |  |  |             size="lg" | 
					
						
							|  |  |  |             footer={<Button text={t("clone_to.clone_to_selected_note")} keyboardShortcut="Enter" />} | 
					
						
							|  |  |  |             onSubmit={onSubmit} | 
					
						
							| 
									
										
										
										
											2025-08-05 21:06:12 +03:00
										 |  |  |             onShown={() => triggerRecentNotes(autoCompleteRef.current)} | 
					
						
							| 
									
										
										
										
											2025-08-10 00:32:26 +03:00
										 |  |  |             onHidden={() => setShown(false)} | 
					
						
							|  |  |  |             show={shown} | 
					
						
							| 
									
										
										
										
											2025-08-04 21:17:35 +03:00
										 |  |  |         > | 
					
						
							|  |  |  |             <h5>{t("clone_to.notes_to_clone")}</h5> | 
					
						
							|  |  |  |             <NoteList style={{ maxHeight: "200px", overflow: "auto" }} noteIds={clonedNoteIds} /> | 
					
						
							|  |  |  |             <FormGroup label={t("clone_to.target_parent_note")}> | 
					
						
							|  |  |  |                 <NoteAutocomplete | 
					
						
							|  |  |  |                     placeholder={t("clone_to.search_for_note_by_its_name")} | 
					
						
							|  |  |  |                     onChange={setSuggestion} | 
					
						
							|  |  |  |                     inputRef={autoCompleteRef} | 
					
						
							|  |  |  |                 />       | 
					
						
							|  |  |  |             </FormGroup> | 
					
						
							|  |  |  |             <FormGroup label={t("clone_to.prefix_optional")} title={t("clone_to.cloned_note_prefix_title")}> | 
					
						
							|  |  |  |                 <FormTextBox name="clone-prefix" onChange={setPrefix} /> | 
					
						
							|  |  |  |             </FormGroup> | 
					
						
							|  |  |  |         </Modal> | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default class CloneToDialog extends ReactBasicWidget { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     get component() { | 
					
						
							| 
									
										
										
										
											2025-08-10 00:32:26 +03:00
										 |  |  |         return <CloneToDialogComponent />; | 
					
						
							| 
									
										
										
										
											2025-08-04 21:17:35 +03:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function cloneNotesTo(notePath: string, clonedNoteIds: string[], prefix?: string) { | 
					
						
							|  |  |  |     const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath); | 
					
						
							|  |  |  |     if (!noteId || !parentNoteId) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const targetBranchId = await froca.getBranchId(parentNoteId, noteId); | 
					
						
							|  |  |  |     if (!targetBranchId || !clonedNoteIds) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (const cloneNoteId of clonedNoteIds) { | 
					
						
							|  |  |  |         await branches.cloneNoteToBranch(cloneNoteId, targetBranchId, prefix); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const clonedNote = await froca.getNote(cloneNoteId); | 
					
						
							|  |  |  |         const targetBranch = froca.getBranch(targetBranchId); | 
					
						
							|  |  |  |         if (!clonedNote || !targetBranch) { | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const targetNote = await targetBranch.getNote(); | 
					
						
							|  |  |  |         if (!targetNote) { | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         toast.showMessage(t("clone_to.note_cloned", { clonedTitle: clonedNote.title, targetTitle: targetNote.title })); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |