import { state } from './state.js'; import { toggleFields, makeButton } from './home.js'; var failureCount = state.saveFailureCount; /** * Retrieves a note from the server based on the provided ID. * * @param {string} id - The ID of the note to retrieve. * @param {boolean} [runOpen=true] - Whether to open the note sidebar after retrieving the note. * @return {Promise} A promise that resolves when the note is successfully retrieved and the UI is updated. */ export function retrieveNote(id, runOpen = true) { fetch('/get-note', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ 'id': id }) }) .then(response => response.json()) .then(result => { var dt = new Date(result.date.date); document.querySelector('#notes').value = result.text; document.querySelector('#passage').value = result.passage; document.querySelector('#series').value = result.series.id; document.querySelector('#speaker').value = result.speaker.id; document.querySelector('#noteTitle').value = result.title; document.querySelector('#noteDate').value = ''; document.querySelector('#noteDate').value = (dt.getMonth() < 9 ? '0' + (dt.getMonth() + 1) : (dt.getMonth() + 1)) + '/' + (dt.getDate() < 10 ? '0' + dt.getDate() : dt.getDate()) + '/' + dt.getFullYear(); document.querySelector('#noteId').value = result.id; if (result.recording) { document.querySelector('#recording').value = result.recording; } if (result.refs) { state.references = result.refs; } const list = document.querySelector('#ref-list'); list.innerHTML = ''; var newList = null; for (var x in state.references) { var newList = document.createElement('li'); newList.className = 'tab'; var button = makeButton(x); newList.appendChild(button); list.appendChild(newList); } if (runOpen) { note.openNote(false); } }); } /** * Toggles the visibility of the note list and reference elements. * * @param {boolean} [openSidebar=true] - Whether to open the sidebar after toggling the visibility. * @return {void} */ export function openNote(openSidebar = true) { const noteList = document.querySelector('#note-list'); const refs = document.querySelector('#ref-text'); if (noteList.style.display == 'block') { noteList.style.display = 'none'; refs.style.display = 'block'; } else { noteList.style.display = 'block'; refs.style.display = 'none'; } if (openSidebar && !document.querySelector('.container').classList.contains('sidebar-collapsed')) { document.querySelector('.container').classList.add('sidebar-collapsed'); } } /** * Starts the save process by updating the save-check element's classList, removing error, fa-times-circle, and fa-save classes, * adding the 'saving' and 'fa-save' classes, and setting the opacity to 1. * * @return {void} This function does not return anything. */ export function startSave() { document.querySelector('#save-check').classList.remove('error', 'fa-times-circle', 'fa-save'); document.querySelector('#save-check').classList.add('saving', 'fa-save'); document.querySelector('#save-check').style.opacity = 1; } /** * Displays a checkmark animation on the screen. * * @param {none} - This function does not take any parameters. * @return {none} - This function does not return any value. */ export function showSave() { if (state.saved) { return; } var checkmark = document.getElementById("save-check"); checkmark.classList.add('fa-save'); // Schedule the animation to run every 1 second (which is equivalent to a 1-second delay between each iteration) var si = setInterval(function () { // Increment the opacity of the checkmark by 0.01 each time let op = parseFloat(checkmark.style.opacity); checkmark.style.opacity = op - 0.1; // If the opacity is greater than or equal to 1, reset it back to 0 and stop the animation if (checkmark.style.opacity == 0.1) { checkmark.style.opacity = 0; clearInterval(si); state.saved = false; } }, 100); } /** * function to discard the note by clearing all input fields and closing the menu. */ export function deleteNote(noteId, link) { document.querySelector('#noteTitle').value = ''; document.querySelector('#speaker').value = 0; document.querySelector('#series').value = 0; document.querySelector('#template').value = 0; document.querySelector('#passage').value = ''; document.querySelector('#notes').value = ''; document.querySelector('#recording').value = ''; document.querySelector('#noteDate').value = ''; document.querySelector('#noteId').value = ''; var row = link.parentElement.parentElement; fetch('/delete-note', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ 'id': noteId }) }) .then(response => response.json()) .then(data => { if (data.msg != 'deleted') { return; } alert('Note deleted.'); row.remove(); }); } /** * Validates a note by checking if all required fields are filled. * * @return {boolean} Returns true if all required fields are filled, false otherwise. */ export function validateNote() { const note = document.querySelector('#notes'); const date = document.querySelector('#noteDate'); const speaker = document.querySelector('#speaker'); const series = document.querySelector('#series'); const title = document.querySelector('#noteTitle'); const psg = document.querySelector('#passage'); let ret = true; if (!title.value.length) { title.classList.add('input-error'); ret = false; } if (!date.value) { date.classList.add('input-error'); ret = false; } if (!parseInt(speaker.value)) { speaker.classList.add('input-error'); ret = false; } if (!parseInt(series.value)) { series.classList.add('input-error'); ret = false; } if (!psg.value) { psg.classList.add('input-error'); ret = false; } if (!note.value.length) { note.classList.add('input-error'); ret = false; } if (!ret) { toggleFields(null, true); } return ret; } /** * Resets the state of the note editor by clearing the text and form fields, * resetting the references, and removing any dirty classes. It also sets the * date to the current date, clears the speaker, series, template, passage, * recording, and note ID fields. Finally, it clears the reference list and * reference display. * * @return {void} This function does not return anything. */ export function newNote() { notes = document.querySelector('#notes'); notes.text = ''; notes.value = ''; state.references = {}; state.saved = true; state.textDirty = false; document.querySelector('.note-header-column h2').classList.remove('dirty'); var dt = new Date(); document.querySelector('#noteDate').value = dt.getFullYear() + '-' + (dt.getMonth() < 9 ? '0' + (dt.getMonth() + 1) : (dt.getMonth() + 1)) + '-' + (dt.getDate() < 10 ? '0' + dt.getDate() : dt.getDate()); document.querySelector('#noteTitle').value = ''; document.querySelector('#speaker').value = 0; document.querySelector('#series').value = 0; document.querySelector('#template').value = 0; document.querySelector('#passage').value = ''; document.querySelector('#recording').value = ''; document.querySelector('#noteId').value = ''; document.querySelector('#ref-list').innerHTML = ''; document.querySelector('#ref-text').innerHTML = ''; document.querySelector('.container').classList.add('sidebar-collapsed'); } /** * Save a note by sending it to the server for storage. * * @param {Event} event - The event object triggering the save action. * @return {void} No explicit return value. */ export function saveNote(event) { console.debug('called saveNote '+new Date()); if (event) { event.preventDefault(); } if (!state.textDirty) { clearTimeout(state.to); state.to = setTimeout(saveNote, state.saveInterval); return; } document.querySelector('#noteTitle').classList.remove('input-error'); document.querySelector('#noteDate').classList.remove('input-error'); document.querySelector('#speaker').classList.remove('input-error'); document.querySelector('#series').classList.remove('input-error'); document.querySelector('#passage').classList.remove('input-error'); document.querySelector('#notes').classList.remove('input-error'); if (!validateNote()) { clearTimeout(state.to); state.to = setTimeout(saveNote, state.saveInterval); return; } let saveCheck = document.querySelector('#save-check'); var noteText = document.querySelector('#notes').value; startSave(); var note = { id: document.querySelector("#noteId").value, date: document.querySelector('#noteDate').value, title: document.querySelector('#noteTitle').value, speaker: document.querySelector('#speaker').value, series: document.querySelector('#series').value, passage: document.querySelector('#passage').value, note: document.querySelector('#notes').value, recording: document.querySelector('#recording').value, refs: state.references }; $.ajax({ url: '/save-note', method: 'POST', contentType: 'application/json', data: JSON.stringify(note), dataType: 'json', timeout: state.saveTimeout }) .done(function (data) { if (data.msg == 'saved' && !state.saved) { saveCheck.classList.remove('saving', 'error', 'fa-times-circle', 'fa-save'); showSave(); if (noteText == document.querySelector('#notes').value) { state.saved = true; state.textDirty = false; document.querySelector('.note-header h2').classList.remove('dirty'); document.querySelector('.mobile-note-header h2').classList.remove('dirty'); } if (data.new) { document.querySelector('#noteId').value = data.id; } } }) .fail(function (xhr, status, error) { state.failureCount--; saveCheck.classList.remove('saving', 'fa-save'); saveCheck.classList.add('fa-times-circle', 'error'); console.error(error); }) .always(function (xhr, status) { if (status == 'timeout') { saveCheck.classList.remove('saving', 'fa-save'); saveCheck.classList.add('error', 'fa-times-circle'); } clearTimeout(state.to); if (state.failureCount > 0) { state.to = setTimeout(saveNote, state.saveInterval); } else { state.failureCount = state.saveFailureCount; } }); } /** * Function that finds reference links and fetches passage data when clicked. */ export function findRefLinks() { var links = document.querySelector('#ref-text').querySelectorAll('a'); for (var i = 0; i < links.length; i++) { links[i].addEventListener('click', function (e) { e.preventDefault(); if (!this.href.includes('get-passage')) { return; } var passage = this.href.split('/'); passage = passage[passage.length - 1]; fetch(this.href, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ 'passage': passage }) }) .then(response => response.text()) .then(result => { let psg, book, cv; passage = passage.replace(/\+/g, ' '); psg = passage.split(' '); if (psg.length > 2) { book = psg[0] + ' ' + psg[1]; cv = psg[2]; } else { book = psg[0]; cv = psg[1]; } showPassage( e, "  " + "
" + result); }); }); } } /** * Finds all links in the note preview and adds event listeners to them. * * @return {void} */ export function findLinks() { var links = document.querySelector('#notePreview').querySelectorAll('a'); for (var i = 0; i < links.length; i++) { links[i].addEventListener('click', function (e) { e.preventDefault(); if (!this.href.includes('get-passage')) { return; } var passage = this.href.split('/'); passage = passage[passage.length - 1]; fetch(this.href, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ 'passage': passage }) }) .then(response => response.text()) .then(result => { passage = passage.replace(/\+/g, ' '); psg = passage.split(' '); if (psg.length > 2) { book = psg[0] + ' ' + psg[1]; cv = psg[2]; } else { book = psg[0]; cv = psg[1]; } showPassage( e, "  " + "
" + result); }); }); } } /** * Shows a passage in a popup element relative to the cursor position. * * @param {Event} event - The event that triggered the function. * @param {string} text - The text to be displayed in the popup. * @return {void} This function does not return a value. */ export function showPassage(event, text) { // Create a new div element for the popup const popup = document.querySelector('#passage-popup'); popup.innerHTML = state.md.render(text); // Position the popup relative to the cursor let x = event.clientX + window.scrollX; let y = event.clientY + window.scrollY; // Set the position of the popup element popup.style.top = `${y}px`; popup.style.left = `${x}px`; popup.style.display = 'block'; } /** * Closes the passage popup by clearing its content and hiding it. * * @return {void} This function does not return anything. */ export function closePopup() { const popup = document.querySelector('#passage-popup'); popup.innerHTML = ''; popup.style.display = 'none'; } /** * Opens the share note functionality. */ export function openShareNote() { let id = document.querySelector('#noteId').value; if (!id) { alert('No Open Note Found'); return; } let bd = document.querySelector('#modal-backdrop'); bd.style.display = 'block'; let cont = document.querySelector('#modal-container'); cont.style.display = bd.style.display; let emailCont = document.querySelector('#modal-container'); emailCont.style.left = ((window.innerWidth / 2) - (emailCont.clientWidth / 2)) + 'px'; emailCont.style.top = ((window.innerHeight / 2) - (emailCont.clientHeight / 2)) + 'px'; } /** * Closes the share note modal by hiding the backdrop and container, * and clears the email input value. */ export function closeShareNote() { var bd = document.querySelector('#modal-backdrop'); var cont = document.querySelector('#modal-container'); bd.style.display = 'none'; cont.style.display = 'none'; document.querySelector('#shareEmail').value = ''; } /** * Function to share a note by sending the note ID and email to the server. */ export function shareNote(event) { var id = document.querySelector('#noteId').value; var email = document.querySelector('#shareEmail').value; if (!id || !email) { alert('Invalid Input'); return; } fetch('/share-note', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ 'id': id, 'email': email }) }) .then(response => response.json()) .then(result => { if (result) { alert(result.msg); } }); closeShareNote(); }