Compare commits

..

4 Commits

5 changed files with 315 additions and 5 deletions

View File

@ -12,11 +12,13 @@
"doctrine/doctrine-bundle": "^2.12", "doctrine/doctrine-bundle": "^2.12",
"doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.1", "doctrine/orm": "^3.1",
"erusev/parsedown": "^1.7",
"phpdocumentor/reflection-docblock": "^5.4", "phpdocumentor/reflection-docblock": "^5.4",
"phpstan/phpdoc-parser": "^1.28", "phpstan/phpdoc-parser": "^1.28",
"symfony/asset": "7.0.*", "symfony/asset": "7.0.*",
"symfony/asset-mapper": "7.0.*", "symfony/asset-mapper": "7.0.*",
"symfony/console": "7.0.*", "symfony/console": "7.0.*",
"symfony/debug-bundle": "7.0.*",
"symfony/doctrine-messenger": "7.0.*", "symfony/doctrine-messenger": "7.0.*",
"symfony/dotenv": "7.0.*", "symfony/dotenv": "7.0.*",
"symfony/expression-language": "7.0.*", "symfony/expression-language": "7.0.*",
@ -45,8 +47,7 @@
"symfony/web-link": "7.0.*", "symfony/web-link": "7.0.*",
"symfony/yaml": "7.0.*", "symfony/yaml": "7.0.*",
"twig/extra-bundle": "^2.12|^3.0", "twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0", "twig/twig": "^2.12|^3.0"
"symfony/debug-bundle": "7.0.*"
}, },
"config": { "config": {
"allow-plugins": { "allow-plugins": {

View File

@ -28,8 +28,15 @@ $(function () {
}, },
] ]
}); });
$('#shareBtn').on('click', openShareNote);
$('#modal-backdrop').on('click', closeShareNote);
}); });
/**
* Fetches data from '/js/data.json', assigns it to BOOKS, and handles errors.
*
* @return {void}
*/
function setBooks() { function setBooks() {
fetch('/js/data.json') fetch('/js/data.json')
.then((res) => { .then((res) => {
@ -46,6 +53,11 @@ function setBooks() {
}) })
} }
/**
* Sets event listeners for keyup events on the document and the '#notes' element.
*
* @return {void}
*/
function setEventListeners() { function setEventListeners() {
document.addEventListener('keyup', function (event) { document.addEventListener('keyup', function (event) {
if (event.key == "F3") { if (event.key == "F3") {
@ -63,6 +75,12 @@ function setEventListeners() {
}); });
} }
/**
* Sets the height of various elements on the page based on the window's inner height.
* Also initializes a datepicker and event listener for the search input field.
*
* @return {void}
*/
function setHeight() { function setHeight() {
md = new markdownit({ md = new markdownit({
html: true, html: true,
@ -107,6 +125,13 @@ function setHeight() {
} }
} }
/**
* Searches for notes based on the query entered in the search field.
* Sends a POST request to the '/index.php/search' endpoint with the query as a JSON payload.
* Updates the '#old-notes' element with the search results.
*
* @return {Promise} A Promise that resolves with the search results.
*/
function search() { function search() {
query = document.querySelector('#query').value; query = document.querySelector('#query').value;
fetch('/index.php/search', { fetch('/index.php/search', {
@ -140,6 +165,15 @@ function search() {
}); });
} }
/**
* 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.
*/
function newNote() { function newNote() {
notes = document.querySelector('#notes'); notes = document.querySelector('#notes');
notes.text = ''; notes.text = '';
@ -243,6 +277,11 @@ function saveNote(event) {
}); });
} }
/**
* Validates a note by checking if all required fields are filled.
*
* @return {boolean} Returns true if all required fields are filled, false otherwise.
*/
function validateNote() { function validateNote() {
const note = document.querySelector('#notes'); const note = document.querySelector('#notes');
const date = document.querySelector('#noteDate'); const date = document.querySelector('#noteDate');
@ -261,11 +300,23 @@ function validateNote() {
return true; return true;
} }
/**
* Checks if a given UUID is valid.
*
* @param {string} uuid - The UUID to be validated.
* @return {boolean} Returns true if the UUID is valid, false otherwise.
*/
function isUuidValid(uuid) { function isUuidValid(uuid) {
const regex = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[8|9|a|b][a-f0-9]{3}-[a-f0-9]{12}$/i; const regex = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[8|9|a|b][a-f0-9]{3}-[a-f0-9]{12}$/i;
return regex.test(uuid); return regex.test(uuid);
} }
/**
* 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.
*/
function startSave() { function startSave() {
document.querySelector('#save-check').classList.remove('error', 'fa-times-circle', 'fa-save'); 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').classList.add('saving', 'fa-save');
@ -335,6 +386,11 @@ function deleteNote(noteId, link) {
}); });
} }
/**
* Toggles the display of the new speaker input field and hides the speaker select field.
*
* @return {void} This function does not return anything.
*/
function newSpeaker() { function newSpeaker() {
if (document.querySelector('#speaker').value == 'new') { if (document.querySelector('#speaker').value == 'new') {
document.querySelector('#newSpeaker').style.display = 'inline-block'; document.querySelector('#newSpeaker').style.display = 'inline-block';
@ -345,6 +401,12 @@ function newSpeaker() {
textDirty = true; textDirty = true;
} }
/**
* Saves a new speaker to the database and updates the UI with the new speaker option.
*
* @param {Event} event - The keydown event triggered by the user.
* @return {Promise} A Promise that resolves with the results of the fetch request.
*/
function saveSpeaker(event) { function saveSpeaker(event) {
if (event.keyCode == 13) { if (event.keyCode == 13) {
fetch('/index.php/save-speaker', { fetch('/index.php/save-speaker', {
@ -373,6 +435,10 @@ function saveSpeaker(event) {
} }
} }
/**
* A description of the entire function.
*
*/
function newSeries() { function newSeries() {
if (document.querySelector('#series').value == 'new') { if (document.querySelector('#series').value == 'new') {
document.querySelector('#newSeries').style.display = 'inline-block'; document.querySelector('#newSeries').style.display = 'inline-block';
@ -383,6 +449,12 @@ function newSeries() {
textDirty = true; textDirty = true;
} }
/**
* Saves a series by making a POST request to '/index.php/save-series' with the series name as the request body.
*
* @param {Event} event - The keydown event.
* @return {Promise} A Promise that resolves with the response from the server.
*/
function saveSeries(event) { function saveSeries(event) {
if (event.keyCode == 13) { if (event.keyCode == 13) {
fetch('/index.php/save-series', { fetch('/index.php/save-series', {
@ -411,6 +483,11 @@ function saveSeries(event) {
} }
} }
/**
* Opens the reference with the option to close the sidebar.
*
* @param {boolean} closeSidebar - Indicates whether to close the sidebar when opening the reference.
*/
function openRef(closeSidebar = true) { function openRef(closeSidebar = true) {
document.querySelector('#openRefBtn').classList.add('active'); document.querySelector('#openRefBtn').classList.add('active');
@ -425,6 +502,11 @@ function openRef(closeSidebar = true) {
} }
} }
/**
* Closes the reference query and resets the reference search form.
*
* @return {void} This function does not return anything.
*/
function closeRef() { function closeRef() {
document.querySelector('#referenceSearch').value = ''; document.querySelector('#referenceSearch').value = '';
document.querySelector('#referenceSearch').style.display = ''; document.querySelector('#referenceSearch').style.display = '';
@ -438,6 +520,14 @@ function closeRef() {
document.querySelector('#openRefBtn').classList.remove('active'); document.querySelector('#openRefBtn').classList.remove('active');
} }
/**
* Fetches a reference based on the provided type, book, and input.
*
* @param {string} type - The type of reference.
* @param {string} book - The book of the reference.
* @param {string} input - The input for the reference.
* @return {void} This function does not return anything directly, but processes the fetched reference data.
*/
function queryRef(type = null, book = null, input = null) { function queryRef(type = null, book = null, input = null) {
if (!input) { if (!input) {
var input = document.querySelector('#refQuery #referenceSearch').value; var input = document.querySelector('#refQuery #referenceSearch').value;
@ -482,6 +572,12 @@ function queryRef(type = null, book = null, input = null) {
}); });
} }
/**
* A function to create a button element with the specified title and event listeners for click and double click actions.
*
* @param {string} title - The title to be displayed on the button.
* @return {Element} The created button element.
*/
function makeButton(title) { function makeButton(title) {
var btn = document.createElement('button'); var btn = document.createElement('button');
btn.innerText = title; btn.innerText = title;
@ -509,6 +605,11 @@ function makeButton(title) {
return btn; return btn;
} }
/**
* Removes the 'active' class from all elements with the class 'active'.
*
* @return {void} This function does not return a value.
*/
function removeActiveRef() { function removeActiveRef() {
tabs = document.querySelectorAll('.active'); tabs = document.querySelectorAll('.active');
for (var t in tabs) { for (var t in tabs) {
@ -518,6 +619,13 @@ function removeActiveRef() {
} }
} }
/**
* Retrieves a template from the server and sets it as the value of a specified destination element.
*
* @param {string} orig - The ID of the element containing the original template value.
* @param {string} dest - The ID of the destination element where the retrieved template will be set.
* @return {Promise} A Promise that resolves when the template is successfully retrieved and set as the value of the destination element.
*/
function retrieveTemplate(orig, dest) { function retrieveTemplate(orig, dest) {
const temp = document.querySelector('#' + orig); const temp = document.querySelector('#' + orig);
if (temp.value == '0') { if (temp.value == '0') {
@ -561,6 +669,11 @@ function saveTemplate() {
}); });
} }
/**
* Toggles the visibility of the fields container and updates the active state of the show/hide button.
*
* @return {void}
*/
function toggleFields() { function toggleFields() {
const fieldsContainer = document.getElementById('fields-container'); const fieldsContainer = document.getElementById('fields-container');
const showHideBtn = document.getElementById('show-hide-btn'); const showHideBtn = document.getElementById('show-hide-btn');
@ -578,6 +691,11 @@ function toggleFields() {
setHeight(); setHeight();
} }
/**
* Retrieves the list of books based on the selected reference type.
*
* @return {void}
*/
function retrieveBooks() { function retrieveBooks() {
document.querySelector('#chapter-range').innerText = ''; document.querySelector('#chapter-range').innerText = '';
document.querySelector('#verse-range').innerText = ''; document.querySelector('#verse-range').innerText = '';
@ -661,6 +779,11 @@ function retrieveBooks() {
} }
} }
/**
* Filters the books based on the selected reference type and updates the chapter range.
*
* @return {void} This function does not return anything.
*/
function filterBooks() { function filterBooks() {
document.querySelector('#chapter-range').innerText = ''; document.querySelector('#chapter-range').innerText = '';
document.querySelector('#verse-range').innerText = ''; document.querySelector('#verse-range').innerText = '';
@ -676,6 +799,11 @@ function filterBooks() {
chapterRange.innerText = 'Chapters: ' + max; chapterRange.innerText = 'Chapters: ' + max;
} }
/**
* Filters the verse based on the selected book and chapter.
*
* @return {void} This function does not return anything.
*/
function filterVerse() { function filterVerse() {
if (document.querySelector('#referenceType').value != 'bible') { if (document.querySelector('#referenceType').value != 'bible') {
return; return;
@ -694,6 +822,12 @@ function filterVerse() {
verseRange.innerText = 'Verse: ' + verse; verseRange.innerText = 'Verse: ' + verse;
} }
/**
* Retrieves the reference type from the server and populates the reference series dropdown.
*
* @param {HTMLElement} el - The element that triggered the function.
* @return {Promise} A promise that resolves with the response from the server.
*/
function retrieveReferenceType(el) { function retrieveReferenceType(el) {
fetch('/index.php/reference/' + el.value, { fetch('/index.php/reference/' + el.value, {
method: 'GET', method: 'GET',
@ -718,6 +852,12 @@ function retrieveReferenceType(el) {
}) })
} }
/**
* Retrieves a reference based on the provided element value.
*
* @param {Element} el - The element triggering the reference retrieval
* @return {void} No return value
*/
function retrieveReference(el) { function retrieveReference(el) {
if (el.value == 'new') { if (el.value == 'new') {
document.querySelector('#refName').style.display = 'inline-block'; document.querySelector('#refName').style.display = 'inline-block';
@ -738,6 +878,13 @@ function retrieveReference(el) {
}); });
} }
/**
* Saves a reference by sending a POST request to the server with the selected type,
* file, and text values. Displays an alert with the response message, and clears
* the reference and file input fields.
*
* @return {Promise} A Promise that resolves with the response message from the server.
*/
function saveReference() { function saveReference() {
var select = document.querySelector('#references'); var select = document.querySelector('#references');
fetch('/index.php/save-reference', { fetch('/index.php/save-reference', {
@ -760,6 +907,12 @@ function saveReference() {
}); });
} }
/**
* Previews a note by rendering the markdown content of the note in a preview section.
* Toggles between the note text and preview sections.
*
* @return {void} This function does not return anything.
*/
function previewNote() { function previewNote() {
var noteText = document.querySelector('#notes'); var noteText = document.querySelector('#notes');
var notePreview = document.querySelector('#notePreview'); var notePreview = document.querySelector('#notePreview');
@ -787,6 +940,11 @@ function previewNote() {
findLinks(); findLinks();
} }
/**
* Finds all links in the note preview and adds event listeners to them.
*
* @return {void}
*/
function findLinks() { function findLinks() {
var links = document.querySelector('#notePreview').querySelectorAll('a'); var links = document.querySelector('#notePreview').querySelectorAll('a');
@ -829,6 +987,9 @@ function findLinks() {
} }
} }
/**
* Function that finds reference links and fetches passage data when clicked.
*/
function findRefLinks() { function findRefLinks() {
var links = document.querySelector('#ref').querySelectorAll('a'); var links = document.querySelector('#ref').querySelectorAll('a');
@ -871,6 +1032,13 @@ function findRefLinks() {
} }
} }
/**
* 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.
*/
function showPassage(event, text) { function showPassage(event, text) {
// Create a new div element for the popup // Create a new div element for the popup
const popup = document.querySelector('#passage-popup'); const popup = document.querySelector('#passage-popup');
@ -886,12 +1054,23 @@ function showPassage(event, text) {
popup.style.display = 'block'; popup.style.display = 'block';
} }
/**
* Closes the passage popup by clearing its content and hiding it.
*
* @return {void} This function does not return anything.
*/
function closePopup() { function closePopup() {
const popup = document.querySelector('#passage-popup'); const popup = document.querySelector('#passage-popup');
popup.innerHTML = ''; popup.innerHTML = '';
popup.style.display = 'none'; popup.style.display = 'none';
} }
/**
* 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}
*/
function openNote(openSidebar = true) { function openNote(openSidebar = true) {
const noteList = document.querySelector('#note-list'); const noteList = document.querySelector('#note-list');
const refs = document.querySelector('#ref'); const refs = document.querySelector('#ref');
@ -909,6 +1088,13 @@ function openNote(openSidebar = true) {
} }
} }
/**
* 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<void>} A promise that resolves when the note is successfully retrieved and the UI is updated.
*/
function retrieveNote(id, runOpen = true) { function retrieveNote(id, runOpen = true) {
fetch('/index.php/get-note', { fetch('/index.php/get-note', {
method: 'POST', method: 'POST',
@ -956,11 +1142,84 @@ function retrieveNote(id, runOpen = true) {
}); });
} }
/**
* Opens the share note functionality.
*/
function openShareNote() {
var id = document.querySelector('#noteId').value;
if (!id) {
alert('No Open Note Found');
return;
}
bd = document.querySelector('#modal-backdrop');
bd.style.display = 'block';
cont = document.querySelector('#modal-container');
cont.style.display = bd.style.display;
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.
*/
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.
*/
function shareNote(event) {
var id = document.querySelector('#noteId').value;
var email = document.querySelector('#shareEmail').value;
if (!id || !email) {
alert('Invalid Input');
return;
}
fetch('/index.php/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();
}
/**
* Increases the font size of the element with the id 'ref' by 1 point.
*
* @return {void} This function does not return a value.
*/
function increaseFont() { function increaseFont() {
var currentSize = document.querySelector('#ref').style.fontSize; var currentSize = document.querySelector('#ref').style.fontSize;
document.querySelector('#ref').style.fontSize = (parseInt(currentSize) + 1) + 'pt'; document.querySelector('#ref').style.fontSize = (parseInt(currentSize) + 1) + 'pt';
} }
/**
* Decreases the font size of the element with the id 'ref' by 1 point.
*
* @return {void} This function does not return a value.
*/
function decreaseFont() { function decreaseFont() {
var currentSize = document.querySelector('#ref').style.fontSize; var currentSize = document.querySelector('#ref').style.fontSize;
document.querySelector('#ref').style.fontSize = (parseInt(currentSize) - 1) + 'pt'; document.querySelector('#ref').style.fontSize = (parseInt(currentSize) - 1) + 'pt';

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,9 @@
#notePreview { #notePreview {
font-size: {{ meta.noteTextSize }}pt; font-size: {{ meta.noteTextSize }}pt;
} }
button.button i {
font-size: 1.5em;
}
</style> </style>
{% endblock %} {% endblock %}
@ -80,10 +83,12 @@ let saveFailureCount = {{ meta.saveFailureCount }};
</select>&nbsp; </select>&nbsp;
<button id="previewBtn" class='button' onclick='previewNote()'> <button id="previewBtn" class='button' onclick='previewNote()'>
<i class='fa fa-eye'></i> <i class='fa fa-eye'></i>
</button> </button>&nbsp;
&nbsp;
<button id='show-hide-btn' class='button' onclick='toggleFields()'> <button id='show-hide-btn' class='button' onclick='toggleFields()'>
<i class='fa fa-table'></i> <i class='fa fa-table'></i>
</button>&nbsp;
<button id='shareBtn' class='button'>
<i class='fas fa-share-alt'></i>
</button> </button>
</div> </div>
@ -152,4 +157,25 @@ let saveFailureCount = {{ meta.saveFailureCount }};
<div id='passage-popup'> <div id='passage-popup'>
</div> </div>
<!-- The modal background -->
<div id='modal-backdrop' class="modal-backdrop fade in" style='display:none;'></div>
<!-- The modal container -->
<div id='modal-container' class="modal-container" style='display:none;'>
<!-- The modal header -->
<header class="modal-header">Share Note</header>
<!-- The modal body -->
<form id="emailForm" class="modal-body">
<label for="email">Enter Friends Email:</label>
<input type="email" id="shareEmail" name="email" required />
<button type='button' id="submit" class="btn btn-primary" onclick='shareNote()'>Submit</button>
</form>
<!-- The modal footer -->
<footer class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" onclick='closeShareNote()'>Close</button>
</footer>
</div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<p>
{{ owner.name }} has shared a note with you, what follows are their formatted notes.
{% if isRegistered %}
You can see shared notes <a href='{{ domain }}/index.php/shared-notes'>here</a>.
{% else %}
You can register for an account <a href='{{ domain }}/index.php/register'>here</a>, or just review the notes below
{% endif %}
</p>
<h1>{{ note.title }}</h1>
<blockquote>
Passage: {{ note.passage }}<br/>
Date: {{ note.date | date("F j Y") }}<br/>
Speaker: {{ note.speaker.name }}<br/>
Series: {{ note.series.name }}<br/>
</blockquote>
{{ formattedText | raw }}
</body>
</html>