@@ -152,6 +152,9 @@ public record EditorResult(Note savedNote, Set<String> newTags) {
152152 @ FXML
153153 private Button copyLinkButton ;
154154
155+ @ FXML
156+ private Button saveAsTemplateButton ;
157+
155158 private Stage dialogStage ;
156159 private Note noteCopy ; // The editable copy of the note
157160 private Note initialNoteState ; // A snapshot of the note's state when the editor was opened
@@ -306,6 +309,13 @@ protected void updateItem(Note.Comment item, boolean empty) {
306309 setupReferencePaneDragAndDrop ();
307310 }
308311
312+ // --- NEW: Save as Template Button ---
313+ if (saveAsTemplateButton != null ) {
314+ saveAsTemplateButton .setGraphic (new FontIcon (MaterialDesignC .CONTENT_SAVE_COG_OUTLINE ));
315+ Tooltip .install (saveAsTemplateButton , new Tooltip ("Save as Template" ));
316+ saveAsTemplateButton .setOnAction (e -> handleSaveAsTemplate ());
317+ }
318+
309319 // --- Debounced Markdown Rendering ---
310320 // This PauseTransition ensures we don't re-render the markdown on every single keystroke,
311321 // which would be inefficient. Instead, it waits for a brief pause in typing.
@@ -776,29 +786,59 @@ protected void updateItem(String item, boolean empty) {
776786 attachmentsListView .setContextMenu (contextMenu );
777787 }
778788
789+ private void setupAttachmentPaneDragAndDrop () {
790+ attachmentsListView .setOnDragOver (event -> {
791+ if (event .getGestureSource () != attachmentsListView && event .getDragboard ().hasFiles ()) {
792+ event .acceptTransferModes (TransferMode .COPY );
793+ }
794+ event .consume ();
795+ });
796+
797+ attachmentsListView .setOnDragDropped (event -> {
798+ Dragboard db = event .getDragboard ();
799+ boolean success = false ;
800+ if (db .hasFiles ()) {
801+ db .getFiles ().forEach (file -> {
802+ try {
803+ addAttachmentFromFile (file );
804+ } catch (IOException e ) {
805+ showError ("Attachment Failed" , "Could not attach the dragged file. Error: " + e .getMessage ());
806+ }
807+ });
808+ success = true ;
809+ }
810+ event .setDropCompleted (success );
811+ event .consume ();
812+ });
813+ }
814+
779815 private void handleAddAttachment () {
780816 FileChooser fileChooser = new FileChooser ();
781817 fileChooser .setTitle ("Attach File" );
782818 File selectedFile = fileChooser .showOpenDialog (dialogStage );
783819
784820 if (selectedFile != null ) {
785821 try {
786- Path attachmentsDir = MainApp .getAttachmentsDirectory ();
787- // Create a unique filename to prevent collisions, but keep the original name for context
788- String uniqueFileName = UUID .randomUUID ().toString ().substring (0 , 8 ) + "-" + selectedFile .getName ();
789- Path targetPath = attachmentsDir .resolve (uniqueFileName );
790-
791- Files .copy (selectedFile .toPath (), targetPath , StandardCopyOption .REPLACE_EXISTING );
792-
793- tempAttachmentPaths .add (uniqueFileName );
794- attachmentsListView .getItems ().add (uniqueFileName );
795-
822+ addAttachmentFromFile (selectedFile );
796823 } catch (IOException e ) {
797824 showError ("Attachment Failed" , "Could not attach the file. Error: " + e .getMessage ());
798825 }
799826 }
800827 }
801828
829+ private void addAttachmentFromFile (File file ) throws IOException {
830+ if (file == null ) return ;
831+
832+ Path attachmentsDir = MainApp .getAttachmentsDirectory ();
833+ // Create a unique filename to prevent collisions, but keep the original name for context
834+ String uniqueFileName = UUID .randomUUID ().toString ().substring (0 , 8 ) + "-" + file .getName ();
835+ Path targetPath = attachmentsDir .resolve (uniqueFileName );
836+
837+ Files .copy (file .toPath (), targetPath , StandardCopyOption .REPLACE_EXISTING );
838+
839+ tempAttachmentPaths .add (uniqueFileName );
840+ attachmentsListView .getItems ().add (uniqueFileName );
841+ }
802842 /**
803843 * Handles text input in the main content area to provide image suggestions.
804844 * When the user types '@' followed by text, it shows a popup with matching
@@ -1040,6 +1080,22 @@ private void handleAddDependency() {
10401080 });
10411081 }
10421082
1083+ private void handleSaveAsTemplate () {
1084+ if (noteManager == null ) {
1085+ showError ("Error" , "Cannot save template. NoteManager is not available." );
1086+ return ;
1087+ }
1088+ // Use the current state of the note copy to create a template
1089+ Note templateNote = noteCopy .asTemplate ();
1090+ noteManager .saveNoteAsTemplate (templateNote );
1091+
1092+ // Provide user feedback
1093+ Alert info = new Alert (Alert .AlertType .INFORMATION );
1094+ info .initOwner (dialogStage );
1095+ info .setTitle ("Template Saved" );
1096+ info .setHeaderText ("Note template '" + templateNote .getTitle () + "' was saved successfully." );
1097+ info .showAndWait ();
1098+ }
10431099 private Set <String > findNewTags () {
10441100 Set <String > newTags = new HashSet <>();
10451101 if (tagComboBox != null ) {
0 commit comments