diff --git a/application/controllers/Logbook.php b/application/controllers/Logbook.php index 56d75669c..13e3970d9 100644 --- a/application/controllers/Logbook.php +++ b/application/controllers/Logbook.php @@ -621,6 +621,12 @@ class Logbook extends CI_Controller { $data['query'] = $this->logbook_model->get_qso($id); $data['dxccFlag'] = $this->dxccflag->get($data['query']->result()[0]->COL_DXCC); + // Check for note for this callsign and current user + $callsign = $data['query']->result()[0]->COL_CALL; + $user_id = $this->session->userdata('user_id'); + $this->load->model('note'); + $data['contacts_note_id'] = $this->note->get_note_id_by_category($user_id, 'Contacts', $callsign); + if ($this->session->userdata('user_measurement_base') == NULL) { $data['measurement_base'] = $this->config->item('measurement_base'); } diff --git a/application/controllers/Notes.php b/application/controllers/Notes.php index 991b2e591..546d7bbe4 100644 --- a/application/controllers/Notes.php +++ b/application/controllers/Notes.php @@ -2,7 +2,6 @@ // Notes controller: handles all note actions, with security and input validation class Notes extends CI_Controller { - // API endpoint: check for duplicate note title in category for user // Ensure only authorized users can access Notes controller function __construct() { parent::__construct(); @@ -41,19 +40,35 @@ class Notes extends CI_Controller { function add() { $this->load->model('note'); $this->load->library('form_validation'); - $this->load->library('callbook'); // Used for callsign parsing + $this->load->library('callbook'); // Used for callsign parsing + + // Support prefilled title/category from query string + $prefill_title = $this->input->get('title', TRUE); + $prefill_category = $this->input->get('category', TRUE); $suggested_title = null; // Validate form fields $this->form_validation->set_rules('title', 'Note Title', 'required|callback_contacts_title_unique'); // Custom callback for Contacts category $this->form_validation->set_rules('content', 'Content', 'required'); if ($this->form_validation->run() == FALSE) { + // Use POST if available, otherwise use prefill from query string $category = $this->input->post('category', TRUE); - if ($category === 'Contacts') { - - $suggested_title = strtoupper($this->callbook->get_plaincall($this->input->post('title', TRUE))); + if (empty($category) && !empty($prefill_category)) { + $category = $prefill_category; } + if ($category === 'Contacts') { + $title_input = $this->input->post('title', TRUE); + if (empty($title_input) && !empty($prefill_title)) { + $title_input = $prefill_title; + } + $suggested_title = strtoupper($this->callbook->get_plaincall($title_input)); + $suggested_title = str_replace('0', 'Ø', $suggested_title); + } + // Pass prefill values to view $data['suggested_title'] = $suggested_title; + $data['prefill_title'] = $prefill_title; + $data['prefill_category'] = $prefill_category; + $data['category'] = $category; $data['page_title'] = __("Add Notes"); $this->load->view('interface_assets/header', $data); $this->load->view('notes/add'); @@ -107,6 +122,7 @@ class Notes extends CI_Controller { $category = $this->input->post('category', TRUE); if ($category === 'Contacts') { $suggested_title = strtoupper($this->callbook->get_plaincall($this->input->post('title', TRUE))); + $suggested_title = str_replace('0', 'Ø', $suggested_title); } $data['suggested_title'] = $suggested_title; $data['page_title'] = __("Edit Note"); @@ -199,7 +215,7 @@ class Notes extends CI_Controller { 'title' => $check_title ])->num_rows(); if ($existing > 0) { - $this->output->set_content_type('application/json')->set_output(json_encode(['status' => 'error', 'message' => 'Duplicate note title for this category and user - not allowed for Contacts category.'])); + $this->output->set_content_type('application/json')->set_output(json_encode(['status' => 'error', 'message' => __("Duplicate note title for this category and user - not allowed for Contacts category.")])); return; } // Duplicate note with new title @@ -239,28 +255,112 @@ class Notes extends CI_Controller { $category = $this->input->get('category', TRUE); $title = $this->input->get('title', TRUE); $id = $this->input->get('id', TRUE); // Optional, for edit - $check_title = $title; - if ($category === 'Contacts') { - $check_title = strtoupper($title); + $exists = false; + $note_id = null; + $this->load->model('note'); + $note_id_found = $this->note->get_note_id_by_category($user_id, $category, $title); + if ($note_id_found) { + // If editing, ignore current note + if ($id && $note_id_found == $id) { + $exists = false; + } else { + $exists = true; + $note_id = $note_id_found; + } } - $where = [ - 'category' => $category, - 'user_id' => $user_id, - 'title' => $check_title - ]; - $query = $this->db->get_where('notes', $where); - $duplicate = false; - if ($id) { - foreach ($query->result() as $note) { - if ($note->id != $id) { - $duplicate = true; - break; - } + $response = ['exists' => $exists]; + if ($exists && $note_id) { + $response['id'] = $note_id; + } + $this->output->set_content_type('application/json')->set_output(json_encode($response)); + } + + // API endpoint to get note details by ID + public function get($id = null) { + $this->load->model('note'); + $clean_id = $this->security->xss_clean($id); + if (!is_numeric($clean_id) || !$this->note->belongs_to_user($clean_id, $this->session->userdata('user_id'))) { + $this->output->set_content_type('application/json')->set_output(json_encode(['error' => __("Not found or not allowed")])); + return; + } + $query = $this->note->view($clean_id); + if ($query && $query->num_rows() > 0) { + $row = $query->row(); + $response = [ + 'id' => $row->id, + 'category' => $row->cat, + 'user_id' => $row->user_id, + 'title' => $row->title, + 'content' => $row->note + ]; + $this->output->set_content_type('application/json')->set_output(json_encode($response)); + } else { + $this->output->set_content_type('application/json')->set_output(json_encode(['error' => __("Not found")])); + } + } + + // API endpoint to save note (create new or update existing or delete, based on presence of ID and content) + public function save($id = null) { + $this->load->model('note'); + $this->load->library('callbook'); + + $user_id = $this->session->userdata('user_id'); + $category = $this->input->post('category', TRUE); + $title = $this->input->post('title', TRUE); + $content = $this->input->post('content', TRUE); + + // Validate required fields + if (empty($category) || empty($title)) { + $this->output->set_content_type('application/json')->set_output(json_encode(['success' => false, 'message' => __("Category and title are required")])); + return; + } + + // Clean title for Contacts category + if ($category === 'Contacts') { + $title = strtoupper($this->callbook->get_plaincall($title)); + $title = str_replace('0', 'Ø', $title); + } + + if ($id !== null) { + // Edit existing note + $clean_id = $this->security->xss_clean($id); + if (!is_numeric($clean_id) || !$this->note->belongs_to_user($clean_id, $user_id)) { + $this->output->set_content_type('application/json')->set_output(json_encode(['success' => false, 'message' => __("Note not found or not allowed")])); + return; + } + + // If content is empty, delete the note + if (empty(trim($content))) { + $this->note->delete($clean_id); + $this->output->set_content_type('application/json')->set_output(json_encode(['success' => true, 'message' => __("Note deleted"), 'deleted' => true])); + } else { + // Update the note + $this->note->edit($clean_id, $category, $title, $content); + $this->output->set_content_type('application/json')->set_output(json_encode(['success' => true, 'message' => __("Note updated"), 'id' => $clean_id])); } } else { - $duplicate = $query->num_rows() > 0; + // Create new note + if (empty(trim($content))) { + $this->output->set_content_type('application/json')->set_output(json_encode(['success' => false, 'message' => __("Cannot create empty note")])); + return; + } + + // Check for duplicate in Contacts category + if ($category === 'Contacts') { + $existing_id = $this->note->get_note_id_by_category($user_id, $category, $title); + if ($existing_id) { + $this->output->set_content_type('application/json')->set_output(json_encode(['success' => false, 'message' => __("A note with this callsign already exists")])); + return; + } + } + + // Create the note + $this->note->add($category, $title, $content); + + // Get the new note ID + $new_id = $this->note->get_note_id_by_category($user_id, $category, $title); + $this->output->set_content_type('application/json')->set_output(json_encode(['success' => true, 'message' => __("Note created"), 'id' => $new_id])); } - $this->output->set_content_type('application/json')->set_output(json_encode(['duplicate' => $duplicate])); } // Form validation callback for add: unique Contacts note title for user, only core callsign @@ -272,15 +372,13 @@ class Notes extends CI_Controller { $core = strtoupper($this->callbook->get_plaincall($title)); // Only fail if prefix or suffix is present if (strtoupper($title) <> $core) { + $core = str_replace('0', 'Ø', $core); $this->form_validation->set_message('contacts_title_unique', sprintf(__("Contacts note title must be a callsign only, without prefix/suffix. Suggested: %s"), $core)); return FALSE; } - $existing = $this->db->get_where('notes', [ - 'cat' => 'Contacts', - 'user_id' => $user_id, - 'title' => $core - ])->num_rows(); - if ($existing > 0) { + // Check for existing note with the same title + $this->load->model('note'); + if ($this->note->get_note_id_by_category($user_id, 'Contacts', $core) > 0) { $this->form_validation->set_message('contacts_title_unique', __("A note with this callsign already exists in your Contacts. Please enter a unique callsign.")); return FALSE; } @@ -298,23 +396,23 @@ class Notes extends CI_Controller { $core = strtoupper($this->callbook->get_plaincall($title)); // Only fail if prefix or suffix is present if (strtoupper($title) <> $core) { + $core = str_replace('0', 'Ø', $core); $this->form_validation->set_message('contacts_title_unique_edit', sprintf(__("Contacts note title must be a callsign only, without prefix/suffix. Suggested: %s"),$core)); return FALSE; } - $query = $this->db->get_where('notes', [ - 'cat' => 'Contacts', - 'user_id' => $user_id, - 'title' => $core - ]); - foreach ($query->result() as $note) { - if ($note->id != $note_id) { - $this->form_validation->set_message('contacts_title_unique_edit', __("A note with this callsign already exists in your Contacts. Please enter a unique callsign.")); - return FALSE; - } - } + + // Check for existing note with the same title + $this->load->model('note'); + $existing_id = $this->note->get_note_id_by_category($user_id, 'Contacts', $core); + if ($existing_id > 0 && $existing_id != $note_id) { + $this->form_validation->set_message('contacts_title_unique_edit', __("A note with this callsign already exists in your Contacts. Please enter a unique callsign.")); + return FALSE; + } return TRUE; } return TRUE; } + + } diff --git a/application/models/Note.php b/application/models/Note.php index ba1ef8438..044a93aa1 100644 --- a/application/models/Note.php +++ b/application/models/Note.php @@ -16,6 +16,22 @@ class Note extends CI_Model { return array_keys(self::get_possible_categories()); } + // Return note ID for given category and title (callsign for Contacts), else false + public function get_note_id_by_category($user_id, $category, $title) { + $check_title = $title; + if ($category === 'Contacts') { + $this->load->library('callbook'); // Used for callsign parsing + $check_title = strtoupper($this->callbook->get_plaincall($title)); + $check_title_slashed = str_replace('0', 'Ø', $check_title); + } + $sql = "SELECT id FROM notes WHERE cat = ? AND user_id = ? AND (title = ? OR title = ?) LIMIT 1"; + $query = $this->db->query($sql, array($category, $user_id, $check_title, $check_title_slashed)); + if ($query->num_rows() > 0) { + return $query->row()->id; + } + return false; + } + // List all notes for a user or API key function list_all($api_key = null) { // Determine user ID @@ -37,11 +53,11 @@ class Note extends CI_Model { $user_id = $this->session->userdata('user_id'); $check_title = $title; if ($category === 'Contacts') { - $check_title = strtoupper($title); + $check_title = trim(strtoupper($title)); + $title = str_replace('0', 'Ø', $check_title); } - $sql = "SELECT COUNT(*) as count FROM notes WHERE cat = ? AND user_id = ? AND title = ?"; - $check_result = $this->db->query($sql, array($category, $user_id, $check_title)); - if ($check_result->row()->count > 0 && $category === 'Contacts') { + // Check for existing note with same title in Contacts category + if ($this->get_note_id_by_category($user_id, $category, $check_title) && $category === 'Contacts') { show_error(__("In Contacts category, the titles of the notes need to be unique.")); return; } @@ -59,17 +75,23 @@ class Note extends CI_Model { function edit($note_id, $category, $title, $content, $local_time = null) { $user_id = $this->session->userdata('user_id'); $check_title = $title; + + if($this->belongs_to_user($note_id, $user_id) === false) { + show_404(); + return; + } + if ($category === 'Contacts') { - $check_title = strtoupper($title); + $check_title = trim(strtoupper($title)); + $title = str_replace('0', 'Ø', $check_title); } - $check_sql = "SELECT id FROM notes WHERE cat = ? AND user_id = ? AND title = ?"; - $check_result = $this->db->query($check_sql, array($category, $user_id, $check_title)); - foreach ($check_result->result() as $note) { - if ($note->id != $note_id && $category === 'Contacts') { + // Check for existing note with same title in Contacts category + $existing_id = $this->get_note_id_by_category($user_id, $category, $check_title); + if ($existing_id > 0 && $existing_id != $note_id && $category === 'Contacts') { show_error(__("In Contacts category, the titles of the notes need to be unique.")); - return; - } + return; } + $last_modified_utc = gmdate('Y-m-d H:i:s'); if ($local_time) { $dt = new DateTime($local_time, new DateTimeZone(date_default_timezone_get())); diff --git a/application/views/interface_assets/footer.php b/application/views/interface_assets/footer.php index 9b700f4f2..c010aac2f 100644 --- a/application/views/interface_assets/footer.php +++ b/application/views/interface_assets/footer.php @@ -68,7 +68,12 @@ var lang_notes_duplicate_confirmation = ""; var lang_notes_duplication_disabled_short = ""; var lang_notes_not_found = ""; - + var lang_qso_note_missing = ""; + var lang_qso_note_toast_title = ""; + var lang_qso_note_deleted = ""; + var lang_qso_note_created = ""; + var lang_qso_note_saved = ""; + var lang_qso_note_error_saving = ""; @@ -299,6 +304,11 @@ function stopImpersonate_modal() { +uri->segment(1) == "qso" ) { ?> + + + + uri->segment(1) == "notes" && ($this->uri->segment(2) == "view") ) { ?> diff --git a/application/views/interface_assets/header.php b/application/views/interface_assets/header.php index 512ab1422..46f2d7ee2 100644 --- a/application/views/interface_assets/header.php +++ b/application/views/interface_assets/header.php @@ -38,7 +38,7 @@ - uri->segment(1) == "notes" && ($this->uri->segment(2) == "add" || $this->uri->segment(2) == "edit" || $this->uri->segment(2) == "view")) { ?> + uri->segment(1) == "notes" && ($this->uri->segment(2) == "add" || $this->uri->segment(2) == "edit" || $this->uri->segment(2) == "view")) || $this->uri->segment(1) == "qso") { ?> diff --git a/application/views/notes/add.php b/application/views/notes/add.php index 177ef3d50..1ad0172b1 100644 --- a/application/views/notes/add.php +++ b/application/views/notes/add.php @@ -29,7 +29,18 @@
- +
diff --git a/application/views/notes/main.php b/application/views/notes/main.php index a4ba462c8..2b23abfba 100644 --- a/application/views/notes/main.php +++ b/application/views/notes/main.php @@ -24,7 +24,7 @@ - $value) { @@ -51,6 +51,9 @@
"> + diff --git a/application/views/qso/index.php b/application/views/qso/index.php index 964f44c32..5cc5e116d 100644 --- a/application/views/qso/index.php +++ b/application/views/qso/index.php @@ -68,7 +68,7 @@ switch ($date_format) {
- +
+ + + + +
+
+

+ + "> + + +

+ +
+
+ + + + +
+
+ - -
@@ -833,6 +855,10 @@ switch ($date_format) {
+ + +
+ diff --git a/application/views/view_log/qso.php b/application/views/view_log/qso.php index 3090954c0..4780f53db 100644 --- a/application/views/view_log/qso.php +++ b/application/views/view_log/qso.php @@ -81,7 +81,13 @@ - COL_CALL)); ?> Lookup <?php echo strtoupper($row->COL_CALL); ?> on QRZ.com Lookup <?php echo strtoupper($row->COL_CALL); ?> on HamQTH Lookup <?php echo strtoupper($row->COL_CALL); ?> on eQSL.cc Clublog Log Search + COL_CALL)); ?> Lookup <?php echo strtoupper($row->COL_CALL); ?> on QRZ.com Lookup <?php echo strtoupper($row->COL_CALL); ?> on HamQTH Lookup <?php echo strtoupper($row->COL_CALL); ?> on eQSL.cc Clublog Log Search + session->userdata('user_show_notes')==1) { ?> + " style="margin-left:2px;vertical-align:middle;"> + + + + diff --git a/assets/js/sections/notes.js b/assets/js/sections/notes.js index bfc5c5f22..d86d9b282 100644 --- a/assets/js/sections/notes.js +++ b/assets/js/sections/notes.js @@ -92,6 +92,18 @@ if (typeof EasyMDE !== 'undefined') { // Main notes page functionality document.addEventListener('DOMContentLoaded', function() { + // Replace 0 with Ø in inputTitle as user types + var inputTitle = document.getElementById('inputTitle'); + if (inputTitle) { + inputTitle.addEventListener('input', function() { + var caret = inputTitle.selectionStart; + var newValue = inputTitle.value.replace(/0/g, 'Ø'); + if (inputTitle.value !== newValue) { + inputTitle.value = newValue; + inputTitle.setSelectionRange(caret, caret); + } + }); + } // Early exit if we're not on a notes page var notesTableBody = document.querySelector('#notesTable tbody'); var isNotesMainPage = notesTableBody !== null; @@ -164,12 +176,18 @@ document.addEventListener('DOMContentLoaded', function() { function reloadCategoryCounters() { fetch(base_url + 'index.php/notes/get_category_counts', { method: 'POST' }) .then(response => response.json()) - .then(counts => { + .then(data => { domCache.categoryButtons.forEach(function(btn) { var cat = btn.getAttribute('data-category'); var countSpan = btn.querySelector('.badge'); - if (countSpan && counts[cat] !== undefined) { - countSpan.textContent = counts[cat]; + if (countSpan) { + if (cat === '__all__') { + // Handle "All Categories" button + countSpan.textContent = data.all_notes_count; + } else if (data.category_counts && data.category_counts[cat] !== undefined) { + // Handle specific category buttons + countSpan.textContent = data.category_counts[cat]; + } } }); }); @@ -557,4 +575,29 @@ document.addEventListener('DOMContentLoaded', function() { if (domCache.notesTableBody) { performNotesSearch(); } + + // Add stroked zero (Ø) to search box + var addStrokedZeroBtn = document.getElementById('notesAddStrokedZero'); + if (addStrokedZeroBtn) { + addStrokedZeroBtn.addEventListener('click', function() { + var searchBox = domCache.searchBox; + if (searchBox) { + var currentValue = searchBox.value; + var cursorPos = searchBox.selectionStart; + + // Insert Ø at cursor position + var newValue = currentValue.slice(0, cursorPos) + 'Ø' + currentValue.slice(cursorPos); + searchBox.value = newValue; + + // Set cursor position after the inserted character + searchBox.focus(); + searchBox.setSelectionRange(cursorPos + 1, cursorPos + 1); + + // Trigger search if minimum length is met + if (newValue.length >= SEARCH_MIN_LENGTH) { + performNotesSearch(); + } + } + }); + } }); diff --git a/assets/js/sections/qso.js b/assets/js/sections/qso.js index f727f056a..ff1601956 100644 --- a/assets/js/sections/qso.js +++ b/assets/js/sections/qso.js @@ -20,6 +20,45 @@ function resetTimers(qso_manual) { } } +// Show Bootstrap Toast - TBD move to general JS file +function showToast(title, text, type = 'bg-success text-white', delay = 3000) { + /* + Examples: + showToast('Saved', 'Your data was saved!', 'bg-success text-white', 3000); + showToast('Error', 'Failed to connect to server.', 'bg-danger text-white', 5000); + showToast('Warning', 'Please check your input.', 'bg-warning text-dark', 4000); + showToast('Info', 'System will restart soon.', 'bg-info text-dark', 4000); + */ + + const container = document.getElementById('toast-container'); + + // Create toast element + const toastEl = document.createElement('div'); + toastEl.className = `toast align-items-center ${type}`; + toastEl.setAttribute('role', 'alert'); + toastEl.setAttribute('aria-live', 'assertive'); + toastEl.setAttribute('aria-atomic', 'true'); + toastEl.setAttribute('data-bs-delay', delay); + + // Toast inner HTML + toastEl.innerHTML = ` +
+
+ ${title}
${text} +
+ +
+ `; + + // Append and show + container.appendChild(toastEl); + const bsToast = new bootstrap.Toast(toastEl); + bsToast.show(); + + // Remove from DOM when hidden + toastEl.addEventListener('hidden.bs.toast', () => toastEl.remove()); +} + function getUTCTimeStamp(el) { var now = new Date(); $(el).attr('value', ("0" + now.getUTCHours()).slice(-2) + ':' + ("0" + now.getUTCMinutes()).slice(-2) + ':' + ("0" + now.getUTCSeconds()).slice(-2)); @@ -73,6 +112,76 @@ function getUTCDateStamp(el) { $(el).attr('value', formatted_date); } +// Note card state logic including EasyMDE initialization and handling +function setNotesVisibility(state, noteText = "",show_notes = user_show_notes) { + var $noteCard = $('#callsign-notes'); + var $saveBtn = $('#callsign-note-save-btn'); + var $editorElem = $('#callsign_note_content'); + var noteEditor = $editorElem.data('easymde'); + var $editBtn = $('#callsign-note-edit-btn'); + + // Do nothing if user preference is to hide notes + if (!show_notes) { + $noteCard.hide(); + return; + } + + // Initialize EasyMDE if not already done + if (!noteEditor && typeof EasyMDE !== 'undefined') { + noteEditor = new EasyMDE({ + element: $editorElem[0], + spellChecker: false, + toolbar: [ + "bold", "italic", "heading", "|","preview", "|", + "quote", "unordered-list", "ordered-list", "|", + "link", "image", "|", + "guide" + ], + forceSync: true, + status: false, + maxHeight: '150px', + autoDownloadFontAwesome: false, + autoRefresh: { delay: 250 }, + }); + $editorElem.data('easymde', noteEditor); + } + + if (state === 0) { + // No callsign - Hide note card + $noteCard.hide(); + + } else if (state === 1) { + // Callsign, no note yet - show note card with message + $noteCard.show(); + + // Hide editor toolbar, set value and show preview + document.querySelector('.EasyMDEContainer .editor-toolbar').style.display = 'none'; + noteEditor.value(lang_qso_note_missing); + noteEditor.togglePreview(); + noteEditor.codemirror.setOption('readOnly', true); + + } else if (state === 2) { + // Callsign with existing notes - show note card with notes + $noteCard.show(); + + // Hide editor toolbar, set value and show preview + document.querySelector('.EasyMDEContainer .editor-toolbar').style.display = 'none'; + noteEditor.value(noteText); + noteEditor.togglePreview(); + noteEditor.codemirror.setOption('readOnly', true); + } + + // Hide buttons per default here + $saveBtn.addClass('d-none').hide(); + $editBtn.addClass('d-none').hide(); + + // Show Edit button for states 1 and 2 + if (state === 1 || state === 2) { + $editBtn.removeClass('d-none').show(); + } else { + $editBtn.addClass('d-none').hide(); + } +} $('#stationProfile').on('change', function () { var stationProfile = $('#stationProfile').val(); @@ -892,8 +1001,50 @@ function reset_fields() { clearTimeout(); set_timers(); resetTimers(qso_manual); + setNotesVisibility(0); // Set note card to hidden } +// Get status of notes for this callsign +function get_note_status(callsign){ + $.get( + window.base_url + 'index.php/notes/check_duplicate', + { + category: 'Contacts', + title: callsign + }, + function(data) { + if (typeof data === 'string') { + try { data = JSON.parse(data); } catch (e) { data = {}; } + } + if (data && data.exists === true && data.id) { + // Get the note content using the note ID + $.get( + window.base_url + 'index.php/notes/get/' + data.id, + function(noteData) { + if (typeof noteData === 'string') { + try { noteData = JSON.parse(noteData); } catch (e) { noteData = {}; } + } + if (noteData && noteData.content) { + $('#callsign-note-id').val(data.id); + setNotesVisibility(2, noteData.content); + } else { + $('#callsign-note-id').val(''); + setNotesVisibility(2, lang_general_word_error); + } + } + ).fail(function() { + $('#callsign-note-id').val(''); + setNotesVisibility(2, lang_general_word_error); + }); + } else { + $('#callsign-note-id').val(''); + setNotesVisibility(1); + } + } + ); +} + +// Lookup callsign on focusout - if the callsign is 3 chars or longer $("#callsign").on("focusout", function () { if ($(this).val().length >= 3 && preventLookup == false) { @@ -936,6 +1087,9 @@ $("#callsign").on("focusout", function () { // Reset QSO fields resetDefaultQSOFields(); + // Set qso icon + get_note_status(result.callsign); + if (result.dxcc.entity != undefined) { $('#country').val(convert_case(result.dxcc.entity)); $('#callsign_info').text(convert_case(result.dxcc.entity)); @@ -2117,6 +2271,8 @@ function resetDefaultQSOFields() { $('#callsign-image-content').text(""); $('.awardpane').remove(); $('#timesWorked').html(lang_qso_title_previous_contacts); + + setNotesVisibility(0); // Set default note card visibility to 0 (hidden) } function closeModal() { @@ -2179,6 +2335,8 @@ $(document).ready(function () { set_timers(); updateStateDropdown('#dxcc_id', '#stateInputLabel', '#location_us_county', '#stationCntyInputQso'); + setNotesVisibility(0); // Set default note card visibility to 0 (hidden) + // Clear the localStorage for the qrg units, except the quicklogCallsign and a possible backlog clearQrgUnits(); set_qrg(); @@ -2406,6 +2564,112 @@ $(document).ready(function () { }); } + // Edit button click handler for inline editing + $(document).on('click', '#callsign-note-edit-btn', function() { + var $editorElem = $('#callsign_note_content'); + var noteEditor = $editorElem.data('easymde'); + var $saveBtn = $('#callsign-note-save-btn'); + var $editBtn = $('#callsign-note-edit-btn'); + var noteId = $('#callsign-note-id').val(); + + if (noteEditor) { + // Switch to edit mode + noteEditor.codemirror.setOption('readOnly', false); + if (noteEditor.isPreviewActive()) { + noteEditor.togglePreview(); // Exit preview mode + } + + // If no note exists (state 1), set dynamic timestamp content + if (!noteId || noteId === '') { + var timestamp = new Date().toLocaleString(); + noteEditor.value('#' + timestamp + '\n'); + noteEditor.codemirror.refresh(); + } + + // Show toolbar and buttons + document.querySelector('.EasyMDEContainer .editor-toolbar').style.display = ''; + $saveBtn.removeClass('d-none').show(); + $editBtn.addClass('d-none').hide(); + } + }); + + // Save button click handler for saving notes + $(document).on('click', '#callsign-note-save-btn', function() { + var $editorElem = $('#callsign_note_content'); + var noteEditor = $editorElem.data('easymde'); + var noteId = $('#callsign-note-id').val(); + var callsign = $('#callsign').val().trim(); + var noteContent = noteEditor ? noteEditor.value() : ''; + + if (!callsign || callsign.length < 3) { + return; + } + + var isEdit = noteId && noteId !== ''; + var url = isEdit ? + window.base_url + 'index.php/notes/save/' + noteId : + window.base_url + 'index.php/notes/save'; + + var postData = { + category: 'Contacts', + title: callsign, + content: noteContent + }; + + if (isEdit) { + postData.id = noteId; + } + + $.post(url, postData) + .done(function(response) { + if (typeof response === 'string') { + try { response = JSON.parse(response); } catch (e) { response = {}; } + } + + if (response.success || response.status === 'ok') { + // Check if note was deleted (empty content) + if (response.deleted) { + // Clear the note ID since note was deleted + $('#callsign-note-id').val(''); + // Reset to state 1 (callsign, no note) + setNotesVisibility(1); + // Show success message + showToast(lang_qso_note_toast_title, lang_qso_note_deleted); + } else { + // Success - switch back to preview mode + if (noteEditor) { + noteEditor.codemirror.setOption('readOnly', true); + if (!noteEditor.isPreviewActive()) { + noteEditor.togglePreview(); // Switch to preview mode + } + document.querySelector('.EasyMDEContainer .editor-toolbar').style.display = 'none'; + } + $('#callsign-note-save-btn').addClass('d-none').hide(); + $('#callsign-note-edit-btn').removeClass('d-none').show(); + + // If it was a new note, store the returned ID + if (!isEdit && response.id) { + $('#callsign-note-id').val(response.id); + + // Show success message briefly + showToast(lang_qso_note_toast_title, lang_qso_note_created); + } else { + // Show success message briefly + showToast(lang_qso_note_toast_title, lang_qso_note_saved); + } + + + } + } else { + alert(lang_qso_note_error_saving + ': ' + (response.message || lang_general_word_error)); + } + }) + .fail(function() { + alert(lang_qso_note_error_saving); + }); + }); + // everything loaded and ready 2 go bc.postMessage('ready'); -}); + + });