| 
									
										
										
										
											2025-08-25 13:51:43 +03:00
										 |  |  | import { useEffect, useRef, useState } from "preact/hooks"; | 
					
						
							| 
									
										
										
										
											2025-08-21 10:05:53 +03:00
										 |  |  | import { t } from "../services/i18n"; | 
					
						
							|  |  |  | import FormTextBox from "./react/FormTextBox"; | 
					
						
							| 
									
										
										
										
											2025-08-27 16:16:25 +03:00
										 |  |  | import { useNoteContext, useNoteProperty, useSpacedUpdate, useTriliumEvent, useTriliumEvents } from "./react/hooks"; | 
					
						
							| 
									
										
										
										
											2025-08-21 10:05:53 +03:00
										 |  |  | import protected_session_holder from "../services/protected_session_holder"; | 
					
						
							|  |  |  | import server from "../services/server"; | 
					
						
							| 
									
										
										
										
											2025-08-21 10:34:30 +03:00
										 |  |  | import "./note_title.css"; | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  | import { isLaunchBarConfig } from "../services/utils"; | 
					
						
							| 
									
										
										
										
											2025-08-21 12:55:33 +03:00
										 |  |  | import appContext from "../components/app_context"; | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  | import branches from "../services/branches"; | 
					
						
							| 
									
										
										
										
											2025-09-01 16:21:58 +00:00
										 |  |  | import { isIMEComposing } from "../services/shortcuts"; | 
					
						
							| 
									
										
										
										
											2025-08-20 23:53:13 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-20 21:50:06 +03:00
										 |  |  | export default function NoteTitleWidget() { | 
					
						
							| 
									
										
										
										
											2025-08-25 13:51:43 +03:00
										 |  |  |     const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext();     | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  |     const title = useNoteProperty(note, "title", componentId);     | 
					
						
							| 
									
										
										
										
											2025-08-21 10:44:58 +03:00
										 |  |  |     const isProtected = useNoteProperty(note, "isProtected"); | 
					
						
							| 
									
										
										
										
											2025-08-21 10:53:59 +03:00
										 |  |  |     const newTitle = useRef(""); | 
					
						
							| 
									
										
										
										
											2025-08-21 12:13:30 +03:00
										 |  |  |      | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  |     const [ isReadOnly, setReadOnly ] = useState<boolean>(false); | 
					
						
							|  |  |  |     const [ navigationTitle, setNavigationTitle ] = useState<string | null>(null);     | 
					
						
							| 
									
										
										
										
											2025-08-21 12:13:30 +03:00
										 |  |  |      | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  |     // Manage read-only
 | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         const isReadOnly = note === null | 
					
						
							|  |  |  |             || note === undefined | 
					
						
							|  |  |  |             || (note.isProtected && !protected_session_holder.isProtectedSessionAvailable()) | 
					
						
							|  |  |  |             || isLaunchBarConfig(note.noteId) | 
					
						
							|  |  |  |             || viewScope?.viewMode !== "default"; | 
					
						
							|  |  |  |         setReadOnly(isReadOnly); | 
					
						
							| 
									
										
										
										
											2025-08-21 12:13:30 +03:00
										 |  |  |     }, [ note, note?.noteId, note?.isProtected, viewScope?.viewMode ]); | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  |     // Manage the title for read-only notes
 | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         if (isReadOnly) { | 
					
						
							| 
									
										
										
										
											2025-08-25 13:51:43 +03:00
										 |  |  |             noteContext?.getNavigationTitle().then(setNavigationTitle); | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  |         } | 
					
						
							|  |  |  |     }, [isReadOnly]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  |     // Save changes to title.
 | 
					
						
							| 
									
										
										
										
											2025-08-21 10:05:53 +03:00
										 |  |  |     const spacedUpdate = useSpacedUpdate(async () => { | 
					
						
							|  |  |  |         if (!note) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         protected_session_holder.touchProtectedSessionIfNecessary(note); | 
					
						
							| 
									
										
										
										
											2025-08-21 10:53:59 +03:00
										 |  |  |         await server.put<void>(`notes/${noteId}/title`, { title: newTitle.current }, componentId); | 
					
						
							| 
									
										
										
										
											2025-08-21 10:05:53 +03:00
										 |  |  |     });     | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  |     // Prevent user from navigating away if the spaced update is not done.
 | 
					
						
							| 
									
										
										
										
											2025-08-21 12:55:33 +03:00
										 |  |  |     useEffect(() => { | 
					
						
							| 
									
										
										
										
											2025-08-21 13:00:05 +03:00
										 |  |  |         appContext.addBeforeUnloadListener(() => spacedUpdate.isAllSavedAndTriggerUpdate());         | 
					
						
							| 
									
										
										
										
											2025-08-21 12:55:33 +03:00
										 |  |  |     }, []); | 
					
						
							| 
									
										
										
										
											2025-08-27 16:16:25 +03:00
										 |  |  |     useTriliumEvents([ "beforeNoteSwitch", "beforeNoteContextRemove" ], () => spacedUpdate.updateNowIfNecessary()); | 
					
						
							| 
									
										
										
										
											2025-08-21 12:55:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  |     // Manage focus.
 | 
					
						
							|  |  |  |     const textBoxRef = useRef<HTMLInputElement>(null); | 
					
						
							|  |  |  |     const isNewNote = useRef<boolean>(); | 
					
						
							| 
									
										
										
										
											2025-08-27 16:28:07 +03:00
										 |  |  |     useTriliumEvents([ "focusOnTitle", "focusAndSelectTitle" ], (e, eventName) => { | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  |         if (noteContext?.isActive() && textBoxRef.current) { | 
					
						
							|  |  |  |             textBoxRef.current.focus(); | 
					
						
							| 
									
										
										
										
											2025-08-27 16:28:07 +03:00
										 |  |  |             if (eventName === "focusAndSelectTitle") { | 
					
						
							|  |  |  |                 textBoxRef.current.select(); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-08-21 13:17:28 +03:00
										 |  |  |             isNewNote.current = ("isNewNote" in e ? e.isNewNote : false); | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-20 21:50:06 +03:00
										 |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2025-08-21 10:34:30 +03:00
										 |  |  |         <div className="note-title-widget"> | 
					
						
							| 
									
										
										
										
											2025-08-21 12:15:12 +03:00
										 |  |  |             {note && <FormTextBox | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  |                 inputRef={textBoxRef} | 
					
						
							| 
									
										
										
										
											2025-08-21 10:05:53 +03:00
										 |  |  |                 autocomplete="off" | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  |                 currentValue={(!isReadOnly ? title : navigationTitle) ?? ""} | 
					
						
							| 
									
										
										
										
											2025-08-21 10:05:53 +03:00
										 |  |  |                 placeholder={t("note_title.placeholder")} | 
					
						
							| 
									
										
										
										
											2025-08-21 10:34:30 +03:00
										 |  |  |                 className={`note-title ${isProtected ? "protected" : ""}`} | 
					
						
							| 
									
										
										
										
											2025-08-21 10:05:53 +03:00
										 |  |  |                 tabIndex={100} | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  |                 readOnly={isReadOnly} | 
					
						
							| 
									
										
										
										
											2025-08-21 10:05:53 +03:00
										 |  |  |                 onChange={(newValue) => { | 
					
						
							| 
									
										
										
										
											2025-08-21 10:53:59 +03:00
										 |  |  |                     newTitle.current = newValue; | 
					
						
							| 
									
										
										
										
											2025-08-21 10:05:53 +03:00
										 |  |  |                     spacedUpdate.scheduleUpdate(); | 
					
						
							|  |  |  |                 }} | 
					
						
							| 
									
										
										
										
											2025-08-21 13:00:05 +03:00
										 |  |  |                 onKeyDown={(e) => { | 
					
						
							| 
									
										
										
										
											2025-09-01 16:21:58 +00:00
										 |  |  |                     // Skip processing if IME is composing to prevent interference
 | 
					
						
							|  |  |  |                     // with text input in CJK languages
 | 
					
						
							|  |  |  |                     if (isIMEComposing(e)) { | 
					
						
							|  |  |  |                         return; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                      | 
					
						
							| 
									
										
										
										
											2025-08-21 13:00:05 +03:00
										 |  |  |                     // Focus on the note content when pressing enter.
 | 
					
						
							|  |  |  |                     if (e.key === "Enter") { | 
					
						
							|  |  |  |                         e.preventDefault(); | 
					
						
							| 
									
										
										
										
											2025-08-25 13:51:43 +03:00
										 |  |  |                         parentComponent.triggerCommand("focusOnDetail", { ntxId: noteContext?.ntxId }); | 
					
						
							| 
									
										
										
										
											2025-08-21 13:00:05 +03:00
										 |  |  |                         return; | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     if (e.key === "Escape" && isNewNote.current && noteContext?.isActive() && note) { | 
					
						
							|  |  |  |                         branches.deleteNotes(Object.values(note.parentToBranch)); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }} | 
					
						
							|  |  |  |                 onBlur={() => { | 
					
						
							|  |  |  |                     spacedUpdate.updateNowIfNecessary(); | 
					
						
							|  |  |  |                     isNewNote.current = false; | 
					
						
							| 
									
										
										
										
											2025-08-21 13:00:05 +03:00
										 |  |  |                 }} | 
					
						
							| 
									
										
										
										
											2025-08-21 12:15:12 +03:00
										 |  |  |             />} | 
					
						
							| 
									
										
										
										
											2025-08-21 10:34:30 +03:00
										 |  |  |         </div> | 
					
						
							| 
									
										
										
										
											2025-08-20 21:50:06 +03:00
										 |  |  |     ); | 
					
						
							|  |  |  | } |