Merge pull request 'Release 1.1' (#18) from 1.1 into main

Reviewed-on: #18
This commit is contained in:
2025-10-10 23:32:42 -04:00
141 changed files with 2450 additions and 1619 deletions

3
.gitmessage Normal file
View File

@@ -0,0 +1,3 @@
{type}: {component}
{summary}

View File

@@ -9,7 +9,7 @@
#ServerName www.example.com #ServerName www.example.com
ServerAdmin webmaster@localhost ServerAdmin webmaster@localhost
DocumentRoot /var/www/html/public DocumentRoot /var/www/html/public/index.php
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn, # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg. # error, crit, alert, emerg.

View File

@@ -1,4 +1,4 @@
FROM php:8.2-apache FROM php:8.4-apache
RUN apt update && \ RUN apt update && \
apt upgrade -y && \ apt upgrade -y && \
@@ -19,7 +19,6 @@ RUN docker-php-ext-configure gd --with-jpeg
RUN docker-php-ext-configure zip RUN docker-php-ext-configure zip
RUN docker-php-ext-install \ RUN docker-php-ext-install \
#pdo_sqlite \
zip \ zip \
mbstring \ mbstring \
exif \ exif \
@@ -45,10 +44,14 @@ RUN rm -rf /var/www/html/tests
RUN rm -rf /var/www/html/translations RUN rm -rf /var/www/html/translations
RUN COMPOSER_ALLOW_SUPERUSER=1 composer install --no-scripts --no-dev --optimize-autoloader RUN COMPOSER_ALLOW_SUPERUSER=1 composer install --no-scripts --no-dev --optimize-autoloader
RUN mv /var/www/html/data/data.db /var/www/html/var/ RUN mkdir /data
COPY data/data.db /data/data.db
RUN mkdir /var/www/html/var/cache RUN mkdir /var/www/html/var/cache
RUN mkdir /var/www/html/var/log RUN mkdir /var/www/html/var/log
#RUN symfony console asset-map:compile
RUN chown -R www-data:www-data /var/www/html RUN chown -R 33:33 /var/www/html /data
RUN chmod -R 755 /var/www/html RUN chmod -R 755 /var/www/html /data
EXPOSE 80

View File

@@ -2,11 +2,26 @@
A program to take notes during a sermon. The web app was built with PHP and Symfony. A program to take notes during a sermon. The web app was built with PHP and Symfony.
## ATTN: !!!!BREAKING CHANGE!!!! v1.0 -> v1.1
This was my first publicly available docker container so I did not realize what some decisions would do. If you are upgrading from v1 you first need to save your database OR you will lose all your current notes!! Follow the steps below to do that
1. You need to make sure that you have a running SSH server on your host computer
2. On your host computer, `docker exec -it sermon-notes bash`
3. `cd var/`
4. `scp data.db {user}@{host computer IP}:{path}`
5. Authenticate with the password
6. This will copy the file over SFTP to the host computer
7. After this then you run the `docker run...` command in Step 1 of the `Installation` instructions below, once the container is running you need to copy the `data.db` file into the working directory of the docker container.
- For example, if you have `~/docker/sermon-notes` as the path for the container on the host computer, you'll copy the `data.db` to `~/docker/sermon-notes/data`
## Installation ## Installation
1. Run `docker run -d --name sermon-notes -p 80:80 ryanprather/sermon-notes:latest`, this will download and start the container and keep it running in the background. If you already have something on port 80 change the first `80` to whatever open port you'd like. 1. Make a directory in your desired docker storage folder (e.g. `~/docker/sermon-notes`), then `cd` into it.
2. Run `docker exec -it sermon-notes bash install.sh` This will run an install script to create an .env file specific to your install, populate with the beginning factors, and then run a `composer` command to download the necessary package dependancies. 2. Create a file called `.env` in that folder, no need to add anything to it right now.
3. Once complete you have a running system that you can navigate to in your browser with `http://{ip}:{port}|{hostname}:{port}`. Then you just need to register for an account. The first account that is created is made an admin so that you can access the `Reference Editor` and update any reference material if necessary. 3. Run `docker run -d --name sermon-notes -p 80:80 -v $PWD/data:/data -v $PWD/.env:/var/www/html/.env gitea.rkprather.com/ryan/sermon-notes:latest`, this will download and start the container and keep it running in the background. If you already have something on port 80 change the first `80` to whatever open port you'd like.
4. Run `docker exec -it sermon-notes bash install.sh` This will run an install script to create an .env file specific to your install, populate with the beginning factors, and then run a `composer` command to download the necessary package dependancies.
5. Once complete you have a running system that you can navigate to in your browser with `http://{ip}:{port}`|`http://{hostname}:{port}`. Then you just need to register for an account. The first account that is created is made an admin so that you can access the `Reference Editor` and update any reference material if necessary.
## Operation ## Operation

View File

@@ -6,5 +6,6 @@ import './bootstrap.js';
* which should already be in your base.html.twig. * which should already be in your base.html.twig.
*/ */
import './styles/app.css'; import './styles/app.css';
const $ = require('jquery');
console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); // console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');

View File

Before

Width:  |  Height:  |  Size: 448 B

After

Width:  |  Height:  |  Size: 448 B

View File

Before

Width:  |  Height:  |  Size: 429 B

After

Width:  |  Height:  |  Size: 429 B

View File

Before

Width:  |  Height:  |  Size: 448 B

After

Width:  |  Height:  |  Size: 448 B

View File

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 421 B

View File

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 408 B

View File

Before

Width:  |  Height:  |  Size: 417 B

After

Width:  |  Height:  |  Size: 417 B

View File

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 497 B

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -106,6 +106,7 @@ menu,
nav { nav {
display: block; display: block;
width: 250px; width: 250px;
margin-bottom: 1em;
} }
body { body {
@@ -306,7 +307,7 @@ h6 a {
} }
h1 { h1 {
font-size: 4em; font-size: 2em;
margin: 0 0 0.5em 0; margin: 0 0 0.5em 0;
line-height: 1.3; line-height: 1.3;
} }
@@ -1891,10 +1892,11 @@ header p {
text-transform: uppercase; text-transform: uppercase;
} }
header.major> :last-child { header.major {
/*> :last-child {*/
border-bottom: solid 3px #f56a6a; border-bottom: solid 3px #f56a6a;
display: inline-block; display: inline-block;
margin: 0 0 2em 0; margin: 0 0 10px 0;
padding: 0 0.75em 0.5em 0; padding: 0 0.75em 0.5em 0;
} }
@@ -1921,6 +1923,7 @@ input[type="email"],
input[type="tel"], input[type="tel"],
input[type="search"], input[type="search"],
input[type="url"], input[type="url"],
input[type="number"],
select, select,
textarea { textarea {
-moz-appearance: none; -moz-appearance: none;
@@ -1967,7 +1970,7 @@ select {
background-size: 1.25em; background-size: 1.25em;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: calc(100% - 1em) center; background-position: calc(100% - 1em) center;
height: 2.75em; height: 2em;
padding-right: 2.75em; padding-right: 2.75em;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
@@ -1992,8 +1995,9 @@ input[type="tel"],
input[type="search"], input[type="search"],
input[type="url"], input[type="url"],
input[type="date"], input[type="date"],
input[type="number"],
select { select {
height: 2.75em; height: 1.5em;
} }
textarea { textarea {
@@ -2233,7 +2237,6 @@ a.image:hover img {
ol { ol {
list-style: decimal; list-style: decimal;
margin: 0 0 2em 0; margin: 0 0 2em 0;
padding-left: 1.5em;
} }
ol li { ol li {
@@ -2243,7 +2246,6 @@ ol li {
ul { ul {
list-style: disc; list-style: disc;
margin: 0 0 2em 0; margin: 0 0 2em 0;
padding-left: 1em;
} }
ul.alt { ul.alt {
@@ -2679,8 +2681,7 @@ button:disabled,
/* Mini Posts */ /* Mini Posts */
.mini-posts article { .mini-posts article {
border-top: solid 1px rgba(210, 215, 217, 0.75); border-top: solid 1px rgba(210, 215, 217, 0.75);
margin-top: 2em; padding-top: 1em;
padding-top: 2em;
} }
.mini-posts article .image { .mini-posts article .image {
@@ -2695,7 +2696,7 @@ button:disabled,
.mini-posts article:first-child { .mini-posts article:first-child {
border-top: 0; border-top: 0;
margin-top: 0; margin-top: 1em;
padding-top: 0; padding-top: 0;
} }
@@ -3088,13 +3089,13 @@ button:disabled,
} }
#main>.inner { #main>.inner {
padding: 0 0.5em 0.1em 0.5em; padding: 0 0.25em 0.1em 0.25em;
margin: 0 auto; margin: 0 auto;
max-width: 110em; max-width: 110em;
} }
#main>.inner>section { #main>.inner>section {
padding: 0.5em 0 0.5em 0; padding: 0.25em 0 0.25em 0;
border-top: solid 2px rgba(210, 215, 217, 0.75); border-top: solid 2px rgba(210, 215, 217, 0.75);
} }
@@ -3104,13 +3105,13 @@ button:disabled,
@media screen and (max-width: 1680px) { @media screen and (max-width: 1680px) {
#main>.inner { #main>.inner {
padding: 0 0 0 2em; padding: 0 0 0 0;
} }
} }
@media screen and (max-width: 1280px) { @media screen and (max-width: 1280px) {
#main>.inner { #main>.inner {
padding: 0 0 0 2em; padding: 0 0 0 0;
} }
} }
@@ -3199,7 +3200,7 @@ button:disabled,
#sidebar>.inner>* { #sidebar>.inner>* {
border-bottom: solid 2px rgba(210, 215, 217, 0.75); border-bottom: solid 2px rgba(210, 215, 217, 0.75);
margin: 0 0 3.5em 0; margin: 0 0 3.5em 0;
padding: 0 0 3.5em 0; /*padding: 0 0 3.5em 0;*/
} }
#sidebar>.inner>*> :last-child { #sidebar>.inner>*> :last-child {
@@ -3229,9 +3230,9 @@ button:disabled,
-webkit-tap-highlight-color: rgba(255, 255, 255, 0); -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
border: 0; border: 0;
display: block; display: block;
height: 7.5em; height: 4.5em;
left: 26em; left: 26em;
line-height: 7.5em; line-height: 4.5em;
outline: 0; outline: 0;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
@@ -3283,17 +3284,19 @@ button:disabled,
} }
#sidebar>.inner>.alt { #sidebar>.inner>.alt {
margin: -1.66667em 0 3.33333em -1.66667em; margin: 0;
padding: 1.66667em; padding: 1.66667em;
width: calc(100% + 3.33333em); width: calc(100% + 3.33333em);
} }
#sidebar .toggle { #sidebar .toggle {
height: 6.25em; height: 4.5em;
left: 24em; left: 23em;
line-height: 6.25em; line-height: 4.5em;
text-indent: 5em; text-indent: 5em;
width: 5em; width: 5em;
background-color: #fff;
margin-left: 1em;
} }
#sidebar .toggle:before { #sidebar .toggle:before {
@@ -3338,7 +3341,7 @@ button:disabled,
#sidebar .toggle { #sidebar .toggle {
text-indent: 6em; text-indent: 6em;
width: 6em; width: 4.5em;
} }
#sidebar .toggle:before { #sidebar .toggle:before {
@@ -3408,7 +3411,18 @@ button:disabled,
} }
@media screen and (max-width: 1680px) { @media screen and (max-width: 1680px) {
#header {} #header .logo {
font-size: 1.25em;
margin: 0;
}
#header .icons {
height: 5em;
line-height: 5em;
position: absolute;
right: -0.5em;
top: 0;
}
} }
@media screen and (max-width: 736px) { @media screen and (max-width: 736px) {

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

85
assets/js/reference.js Normal file
View File

@@ -0,0 +1,85 @@
/**
* 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.
*/
export function retrieveReferenceType(el) {
fetch('/reference/' + el.value, {
method: 'GET',
header: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(results => {
document.querySelector('#referenceSeries').innerHTML = '';
var none = document.createElement('option');
none.value = '';
none.text = '-- Select --';
document.querySelector('#referenceSeries').appendChild(none);
for (var x in results) {
var newSeries = document.createElement('option');
newSeries.value = results[x].id;
newSeries.text = results[x].label;
document.querySelector('#referenceSeries').appendChild(newSeries);
}
});
}
/**
* Retrieves a reference based on the provided element value.
*
* @param {Element} el - The element triggering the reference retrieval
* @return {void} No return value
*/
export function retrieveReference(el) {
if (el.value == 'new') {
document.querySelector('#refName').style.display = 'inline-block';
return;
}
fetch('/get-reference', {
method: "POST",
header: {
"Content-Type": "application/json"
},
body: JSON.stringify({
id: el.value
})
})
.then(response => response.json())
.then(results => {
document.querySelector('#reference').value = results.text;
});
}
/**
* 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.
*/
export function saveReference() {
let ref = document.querySelector('#referenceSeries');
let cont = document.querySelector('#reference');
fetch('/save-reference', {
method: 'POST',
header: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
refId: ref.value,
text: cont.value
})
})
.then(response => response.json())
.then(results => {
//alert(results.msg);
document.querySelector('#reference').value = '';
document.querySelector('#referenceType').value = '';
document.querySelector('#referenceSeries').value = '';
});
}

View File

@@ -1,9 +1,8 @@
// Get references to the form elements // Get references to the form elements
const nameInput = document.getElementById("name"); const nameInput = document.getElementById("registration_form_name");
const emailInput = document.getElementById("emailAddress"); const emailInput = document.getElementById("registration_form_email");
const passwordInput = document.getElementById("password"); const passwordInput = document.getElementById("registration_form_plainPassword");
const confirmPasswordInput = document.getElementById("confirmPassword"); const csrfToken = document.getElementById("registration_form__token").value;
const csrfToken = document.getElementById("csrfToken").value;
// Add event listeners to the form // Add event listeners to the form
const registerBtn = document.querySelector("#register-btn"); const registerBtn = document.querySelector("#register-btn");
@@ -18,28 +17,21 @@ function handleSubmit(event) {
const name = nameInput.value; const name = nameInput.value;
const email = emailInput.value; const email = emailInput.value;
const password = passwordInput.value; const password = passwordInput.value;
const confirmPassword = confirmPasswordInput.value;
if (name === "" || email === "" || password === "") { if (name === "" || email === "" || password === "") {
alert("Please fill in all fields."); alert("Please fill in all fields.");
return; return;
} }
if (password !== confirmPassword) {
alert("Passwords do not match.");
return;
}
// Send data to server for processing // Send data to server for processing
const data = { const data = {
"name": name, "name": name,
"email": email, "email": email,
"password": password,
"plainPassword": password, "plainPassword": password,
"csrf_token": csrfToken "_token": csrfToken
}; };
fetch("/index.php/register", { fetch("/register", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@@ -4,87 +4,21 @@ var references = {};
var tabs = []; var tabs = [];
let saved = false; let saved = false;
let textDirty = false; let textDirty = false;
let saveTimeout = 10000;
var to = null; var to = null;
let controller; let controller;
var BOOKS = {}; var BOOKS = {};
fetch('/js/data.json')
.then((res) => {
if (!res.ok) {
throw new Error('HTTP Error: Status: ${res.status}');
}
return res.json();
})
.then((data) => {
BOOKS = data;
})
.catch((error) => {
console.log(error);
})
document.addEventListener('keyup', function (event) {
if (event.key == "F3") {
openRef(false);
}
});
document.querySelector('#notes').addEventListener('keyup', function (event) {
let key = event.keyCode;
if (key >= 48 && key <= 90 || key >= 96 && key <= 111 || key >= 186 && key <= 222) {
textDirty = true;
document.querySelector('#note-header-left h2').classList.add('dirty');
}
});
function setHeight() {
md = new markdownit({
html: true,
linkify: true,
breaks: true
});
body = document.querySelector('body');
body.style.height = window.innerHeight + 'px';
cont = document.querySelector('#main');
cont.style.height = (window.innerHeight) + 'px';
tabs = document.querySelector('.ref-tab');
tabs.style.height = (window.innerHeight - 13) + 'px';
ref = document.querySelector('.ref');
ref.style.height = (window.innerHeight - 50) + 'px';
noteList = document.querySelector('#note-list');
noteList.style.height = (window.innerHeight - 50) + 'px';
notes = document.querySelector('.notes');
notes.style.height = (window.innerHeight - 50) + 'px';
notePreview = document.querySelector('#notePreview');
notePreview.style.height = (window.innerHeight - 50) + 'px';
if ($('#noteDate')) {
$('#noteDate').datepicker();
}
if ($('#query')) {
document.querySelector('#query').addEventListener('keyup', function (event) {
if (event.key == "Enter") {
search();
}
});
}
if (!to) {
to = setTimeout(saveNote, saveTimeout);
}
}
/**
* Searches for notes based on the query entered in the search field.
* Sends a POST request to the '/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('/search', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -115,6 +49,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 = '';
@@ -133,7 +76,8 @@ function newNote() {
document.querySelector('#series').value = 0; document.querySelector('#series').value = 0;
document.querySelector('#template').value = 0; document.querySelector('#template').value = 0;
document.querySelector('#passage').value = ''; document.querySelector('#passage').value = '';
document.querySelector('#noteId').value = uuidv4(); document.querySelector('#recording').value = '';
document.querySelector('#noteId').value = '';
document.querySelector('#ref-list').innerHTML = ''; document.querySelector('#ref-list').innerHTML = '';
document.querySelector('#ref').innerHTML = ''; document.querySelector('#ref').innerHTML = '';
@@ -151,13 +95,21 @@ function saveNote(event) {
event.preventDefault(); event.preventDefault();
} }
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 (!textDirty || !validateNote()) { if (!textDirty || !validateNote()) {
clearTimeout(to); clearTimeout(to);
to = setTimeout(saveNote, saveTimeout); to = setTimeout(saveNote, saveInterval);
return; return;
} }
let saveCheck = document.querySelector('#save-check'); let saveCheck = document.querySelector('#save-check');
var noteText = document.querySelector('#notes').value;
startSave(); startSave();
@@ -169,44 +121,58 @@ function saveNote(event) {
series: document.querySelector('#series').value, series: document.querySelector('#series').value,
passage: document.querySelector('#passage').value, passage: document.querySelector('#passage').value,
note: document.querySelector('#notes').value, note: document.querySelector('#notes').value,
recording: document.querySelector('#recording').value,
refs: references refs: references
}; };
$.ajax({ $.ajax({
url: '/index.php/save-note', url: '/save-note',
method: 'POST', method: 'POST',
contentType: 'application/json', contentType: 'application/json',
data: JSON.stringify(note), data: JSON.stringify(note),
dataType: 'json', dataType: 'json',
timeout: 5000 timeout: saveTimeout
}) })
.done(function (data) { .done(function (data) {
if (data.msg == 'saved' && !saved) { if (data.msg == 'saved' && !saved) {
saveCheck.classList.remove('saving'); saveFailureCount = SAVE_FAILURE_LIMIT;
saveCheck.classList.remove('saving', 'error', 'fa-times-circle', 'fa-save');
showSave(); showSave();
saved = true; if (noteText == document.querySelector('#notes').value) {
textDirty = false; saved = true;
document.querySelector('#note-header-left h2').classList.remove('dirty'); textDirty = false;
document.querySelector('#note-header-left h2').classList.remove('dirty');
}
if (data.new) { if (data.new) {
document.querySelector('#noteId').value = data.id; document.querySelector('#noteId').value = data.id;
} }
} }
}) })
.fail(function (data) { .fail(function (xhr, status, error) {
saveCheck.classList.remove('saving'); saveFailureCount--;
saveCheck.classList.add('error'); saveCheck.classList.remove('saving', 'fa-save');
console.error(data); saveCheck.classList.add('fa-times-circle', 'error');
console.error(error);
}) })
.always(function (xhr, status) { .always(function (xhr, status) {
if (status == 'timeout') { if (status == 'timeout') {
saveCheck.classList.remove('saving'); saveCheck.classList.remove('saving', 'fa-save');
saveCheck.classList.add('error'); saveCheck.classList.add('error', 'fa-times-circle');
} }
clearTimeout(to); clearTimeout(to);
to = setTimeout(saveNote, saveTimeout); if (saveFailureCount > 0) {
to = setTimeout(saveNote, saveInterval);
} else {
saveFailureCount = SAVE_FAILURE_LIMIT;
}
}); });
} }
/**
* 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');
@@ -215,23 +181,42 @@ function validateNote() {
const title = document.querySelector('#noteTitle'); const title = document.querySelector('#noteTitle');
const psg = document.querySelector('#passage'); const psg = document.querySelector('#passage');
if (!title.value.length) { return false; } let ret = true;
if (!date.value) { return false; }
if (!parseInt(speaker.value)) { return false; }
if (!parseInt(series.value)) { return false; }
if (!psg.value) { return false; }
if (!note.value.length) { return false; }
return 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(true);
}
return ret;
} }
/**
* 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.add('saving'); 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; document.querySelector('#save-check').style.opacity = 1;
} }
@@ -245,6 +230,7 @@ function showSave() {
if (saved) { return; } if (saved) { return; }
var checkmark = document.getElementById("save-check"); 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) // Schedule the animation to run every 1 second (which is equivalent to a 1-second delay between each iteration)
var si = setInterval(function () { var si = setInterval(function () {
@@ -264,33 +250,44 @@ function showSave() {
/** /**
* Function to discard the note by clearing all input fields and closing the menu. * Function to discard the note by clearing all input fields and closing the menu.
*/ */
function discardNote() { function deleteNote(noteId, link) {
document.querySelector('#noteTitle').value = ''; document.querySelector('#noteTitle').value = '';
document.querySelector('#speaker').value = 0; document.querySelector('#speaker').value = 0;
document.querySelector('#series').value = 0; document.querySelector('#series').value = 0;
document.querySelector('#template').value = 0; document.querySelector('#template').value = 0;
document.querySelector('#passage').value = ''; document.querySelector('#passage').value = '';
document.querySelector('#notes').value = ''; document.querySelector('#notes').value = '';
document.querySelector('#recording').value = '';
document.querySelector('#noteDate').value = '';
document.querySelector('#noteId').value = '';
fetch('/index.php/discard-note', { var row = link.parentElement.parentElement;
fetch('/delete-note', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
'id': document.querySelector('#noteId').value 'id': noteId
}) })
.then(response => response.json()) })
.then(data => { .then(response => response.json())
if (data.msg == 'deleted') { .then(data => {
alert('Note deleted.'); if (data.msg != 'deleted') {
} return;
}) }
});
openRef(); alert('Note deleted.');
row.remove();
});
} }
/**
* 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';
@@ -301,9 +298,20 @@ 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 == 27) {
fetch('/index.php/save-speaker', { document.querySelector('#newSpeaker').style.display = 'none';
document.querySelector('#speaker').style.display = 'inline-block';
document.querySelector('#speaker').value = 0;
}
if (event.keyCode == 13 && document.querySelector('#newSpeaker').value != '') {
fetch('/save-speaker', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -329,6 +337,9 @@ 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';
@@ -339,9 +350,20 @@ function newSeries() {
textDirty = true; textDirty = true;
} }
/**
* Saves a series by making a POST request to '/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 == 27) {
fetch('/index.php/save-series', { document.querySelector('#newSeries').style.display = 'none';
document.querySelector('#series').style.display = 'inline-block';
document.querySelector('#series').value = 0;
}
if (event.keyCode == 13 && document.querySelector('#newSeries').value != '') {
fetch('/save-series', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -367,7 +389,14 @@ 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');
refQuery = document.querySelector('#refQuery'); refQuery = document.querySelector('#refQuery');
refQuery.style.display = 'block'; refQuery.style.display = 'block';
@@ -379,6 +408,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 = '';
@@ -389,8 +423,17 @@ function closeRef() {
document.querySelector('#verse-range').innerText = ''; document.querySelector('#verse-range').innerText = '';
document.querySelector('#refQuery').style.display = 'none'; document.querySelector('#refQuery').style.display = 'none';
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;
@@ -401,7 +444,7 @@ function queryRef(type = null, book = null, input = null) {
if (!book) { if (!book) {
var book = document.querySelector('#referenceBook').value; var book = document.querySelector('#referenceBook').value;
} }
fetch('/index.php/retrieve-reference', { fetch('/retrieve-reference', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -435,13 +478,21 @@ 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;
btn.class = 'button';
btn.style = 'line-height:normal;'
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
removeActiveRef(); removeActiveRef();
document.querySelector('#ref').innerHTML = md.render(references[title]); document.querySelector('#ref').innerHTML = md.render(references[title]);
this.classList.add('activeRef'); this.classList.add('active');
findRefLinks(); findRefLinks();
}); });
@@ -456,80 +507,53 @@ function makeButton(title) {
}); });
removeActiveRef(); removeActiveRef();
btn.classList.add('activeRef'); btn.classList.add('active');
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('.activeRef'); tabs = document.querySelectorAll('.active');
for (var t in tabs) { for (var t in tabs) {
if (isFinite(parseInt(t))) { if (isFinite(parseInt(t))) {
tabs[t].classList.remove('activeRef'); tabs[t].classList.remove('active');
} }
} }
} }
function retrieveTemplate(orig, dest) {
const temp = document.querySelector('#' + orig);
if (temp.value == '0') {
document.querySelector('#' + dest).value = '';
return;
}
fetch('/index.php/retrieve-template', {
method: 'POST',
headers: {
'Content-Type': 'plain/text'
},
body: JSON.stringify({
'template': temp.value
})
})
.then(response => response.text())
.then(results => {
const div = document.querySelector('#' + dest);
div.value = results;
});
}
/** /**
* Saves the template by sending a POST request to the server with template data. * Toggles the visibility of the fields container and updates the active state of the show/hide button.
*
* @param boolean show
* @return {void}
*/ */
function saveTemplate() { function toggleFields(show = false) {
fetch('/index.php/save-template', {
method: 'POST',
headers: {
'Content-Type': 'plain/text'
},
body: JSON.stringify({
'template_id': document.querySelector('#template_id').value,
'template_name': document.querySelector('#template_name').value,
'template_value': document.querySelector('#template_value').value,
})
})
.then(response => response.text())
.then(results => {
alert(results);
});
}
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');
if (fieldsContainer.classList.contains('show')) { if (show || !fieldsContainer.classList.contains('show')) {
fieldsContainer.classList.remove('show');
fieldsContainer.style.display = 'none';
showHideBtn.classList.remove('active');
} else {
fieldsContainer.classList.add('show'); fieldsContainer.classList.add('show');
fieldsContainer.style.display = 'block'; fieldsContainer.style.display = 'block';
showHideBtn.classList.add('active'); showHideBtn.classList.add('active');
} else {
fieldsContainer.classList.remove('show');
fieldsContainer.style.display = 'none';
showHideBtn.classList.remove('active');
} }
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 = '';
@@ -573,6 +597,51 @@ function retrieveBooks() {
newBook.text = BOOKS.cd[x]; newBook.text = BOOKS.cd[x];
bookList.appendChild(newBook); bookList.appendChild(newBook);
} }
} else if (selectedType == 'hc') {
var none = document.createElement("option");
none.value = '';
none.text = '-- Select --';
bookList.appendChild(none);
for (var x in BOOKS[selectedType]) {
var newBook = document.createElement("optgroup");
newBook.label = "Lord's Day " + (parseInt(x) + 1)
var ld = document.createElement("option");
ld.value = 'ld' + (parseInt(x) + 1);
ld.text = "LD " + (parseInt(x) + 1) + " All";
newBook.appendChild(ld);
for (var y in BOOKS[selectedType][x]) {
var question = document.createElement("option");
question.value = 'hc' + BOOKS[selectedType][x][y];
question.text = "HC" + BOOKS[selectedType][x][y];
newBook.appendChild(question);
}
bookList.appendChild(newBook);
}
} else if (selectedType == 'note') {
var none = document.createElement("option");
none.value = '';
none.text = '-- Select --';
bookList.appendChild(none);
fetch('/retrieve-reference', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'type': 'note'
})
})
.then(response => response.json())
.then(results => {
for (var x in results) {
var newBook = document.createElement("option");
newBook.value = results[x].id;
newBook.text = results[x].title;
bookList.appendChild(newBook);
}
});
} else { } else {
var min = BOOKS[selectedType][0]; var min = BOOKS[selectedType][0];
var max = BOOKS[selectedType][1]; var max = BOOKS[selectedType][1];
@@ -589,6 +658,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 = '';
@@ -604,6 +678,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;
@@ -622,45 +701,12 @@ function filterVerse() {
verseRange.innerText = 'Verse: ' + verse; verseRange.innerText = 'Verse: ' + verse;
} }
function retrieveReference(el) { /**
fetch('/index.php/get-reference', { * Previews a note by rendering the markdown content of the note in a preview section.
method: "POST", * Toggles between the note text and preview sections.
header: { *
"Content-Type": "application/json" * @return {void} This function does not return anything.
}, */
body: JSON.stringify({
file: el.value,
type: el.options[el.selectedIndex].getAttribute('type')
})
})
.then(response => response.json())
.then(results => {
document.querySelector('#reference').value = results.text;
});
}
function saveReference() {
var select = document.querySelector('#references');
fetch('/index.php/save-reference', {
method: 'POST',
header: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: select.options[select.selectedIndex].getAttribute('type'),
file: select.value,
text: document.querySelector('#reference').value
})
})
.then(response => response.json())
.then(results => {
alert(results.msg);
document.querySelector('#reference').value = '';
document.querySelector('#references').value = '';
});
}
function previewNote() { function previewNote() {
var noteText = document.querySelector('#notes'); var noteText = document.querySelector('#notes');
var notePreview = document.querySelector('#notePreview'); var notePreview = document.querySelector('#notePreview');
@@ -688,6 +734,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');
@@ -730,6 +781,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');
@@ -772,6 +826,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');
@@ -787,12 +848,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');
@@ -810,8 +882,15 @@ 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('/get-note', {
method: 'POST', method: 'POST',
header: { header: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -857,14 +936,91 @@ 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('/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';
document.querySelector('#notes').style.fontSize = (parseInt(currentSize) + 1) + 'pt';
document.querySelector('#notePreview').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';
document.querySelector('#notes').style.fontSize = (parseInt(currentSize) - 1) + 'pt';
document.querySelector('#notePreview').style.fontSize = (parseInt(currentSize) - 1) + 'pt';
} }
/** /**

1
assets/js/script.min.js vendored Normal file

File diff suppressed because one or more lines are too long

116
assets/js/site.js Normal file
View File

@@ -0,0 +1,116 @@
export function initHome() {
setHeight();
setBooks();
setEventListeners();
$('#note-table').DataTable({
paging: false,
ajax: {
url: '/get-notes',
type: 'POST'
},
columns: [
{ data: 'link' },
{ data: 'speaker.name' },
{ data: 'passage' },
{
data: 'date.date',
render: DataTable.render.date("L")
},
]
});
$('#shareBtn').on('click', openShareNote);
$('#modal-backdrop').on('click', closeShareNote);
}
/**
* Fetches data from '/js/data.json', assigns it to BOOKS, and handles errors.
*
* @return {void}
*/
export function setBooks() {
fetch('js/data.json')
.then((res) => {
if (!res.ok) {
throw new Error('HTTP Error: Status: ${res.status}');
}
return res.json();
})
.then((data) => {
BOOKS = data;
})
.catch((error) => {
console.log(error);
})
}
/**
* Sets event listeners for keyup events on the document and the '#notes' element.
*
* @return {void}
*/
export function setEventListeners() {
document.addEventListener('keyup', function (event) {
if (event.key == "F3") {
openRef(false);
}
});
document.querySelector('#notes').addEventListener('keyup', function (event) {
let key = event.keyCode;
if (key == 8 || key >= 48 && key <= 90 || key >= 96 && key <= 111 || key >= 186 && key <= 222) {
textDirty = true;
document.querySelector('#note-header-left h2').classList.add('dirty');
}
});
}
/**
* 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}
*/
export function setHeight() {
md = new markdownit({
html: true,
linkify: true,
breaks: true
});
var body = document.querySelector('body');
body.style.height = window.innerHeight + 'px';
var cont = document.querySelector('#main');
cont.style.height = (window.innerHeight) + 'px';
var tabs = document.querySelector('.ref-tab');
tabs.style.height = (window.innerHeight - 13) + 'px';
var ref = document.querySelector('.ref');
ref.style.height = (window.innerHeight - 60) + 'px';
var noteList = document.querySelector('#note-list');
noteList.style.height = (window.innerHeight - 60) + 'px';
var notes = document.querySelector('.notes');
notes.style.height = (window.innerHeight - 60) + 'px';
var notePreview = document.querySelector('#notePreview');
notePreview.style.height = (window.innerHeight - 50) + 'px';
if ($('#noteDate')) {
$('#noteDate').datepicker();
}
if ($('#query')) {
document.querySelector('#query').addEventListener('keyup', function (event) {
if (event.key == "Enter") {
search();
}
});
}
if (!to) {
to = setTimeout(saveNote, saveInterval);
}
}

49
assets/js/template.js Normal file
View File

@@ -0,0 +1,49 @@
/**
* 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.
*/
export function retrieveTemplate(orig, dest) {
const temp = document.querySelector('#' + orig);
if (temp.value == '0') {
document.querySelector('#' + dest).value = '';
return;
}
fetch('/retrieve-template', {
method: 'POST',
headers: {
'Content-Type': 'plain/text'
},
body: JSON.stringify({
'template': temp.value
})
})
.then(response => response.text())
.then(results => {
const div = document.querySelector('#' + dest);
div.value = results;
});
}
/**
* Saves the template by sending a POST request to the server with template data.
*/
export function saveTemplate() {
fetch('/save-template', {
method: 'POST',
headers: {
'Content-Type': 'plain/text'
},
body: JSON.stringify({
'template_id': document.querySelector('#template_id').value,
'template_name': document.querySelector('#template_name').value,
'template_value': document.querySelector('#template_value').value,
})
})
.then(response => response.text())
.then(results => {
alert(results);
});
}

View File

@@ -18,7 +18,7 @@ body {
.ref-tab { .ref-tab {
width: 60px; width: 60px;
padding-top: 75px !important; padding-top: 60px !important;
} }
.ref-tab ul { .ref-tab ul {
@@ -43,24 +43,12 @@ body {
height: 80px; height: 80px;
width: 100%; width: 100%;
text-align: center; text-align: center;
background-color: #f56a6a;
color: #fff !important;
border: none;
border-radius: 3px;
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.8);
font-size: 14px; font-size: 14px;
margin-bottom: 3px; margin-bottom: 3px;
} }
.tab button:active {
background-color: #7a0016;
}
.activeRef {
background-color: #3e8e41 !important;
}
.ref { .ref {
width: 35%; width: 36%;
} }
.ref > div#ref { .ref > div#ref {
@@ -76,7 +64,7 @@ body {
} }
#ref { #ref {
padding: 3px 3px 3px 10px; padding: 3px 3px 3px 3px;
} }
#passage { #passage {
@@ -102,7 +90,7 @@ body {
} }
.notes { .notes {
width: 55%; width: 57%;
} }
textarea#notes { textarea#notes {
@@ -114,10 +102,18 @@ textarea#notes {
#notePreview { #notePreview {
display: none; display: none;
overflow-x: scroll; overflow-x: scroll;
padding-left: 10px;
}
#notePreview ul,
#notePreview ol {
list-style-position: inside;
} }
#previewBtn.active, #previewBtn.active,
#show-hide-btn.active { #show-hide-btn.active,
.tab button.active,
#openRefBtn.active {
background-color: #f56a6a !important; background-color: #f56a6a !important;
color: white !important; color: white !important;
} }
@@ -140,6 +136,8 @@ textarea#notes {
#note-list { #note-list {
display: none; display: none;
height: 100%;
overflow-y: scroll;
} }
#note-list ul { #note-list ul {
@@ -205,7 +203,7 @@ div#refQuery #referenceSearch {
#fields-container input, #fields-container input,
#fields-container select { #fields-container select {
width: 19.5%; width: 32.5%;
display: inline-block; display: inline-block;
} }
@@ -234,6 +232,65 @@ div#refQuery #referenceSearch {
font-size: 12pt; font-size: 12pt;
} }
#ref ol,
#ref ul {
list-style-position: inside;
}
#referenceBook { #referenceBook {
display: none; display: none;
}
.recording-link {
font-size: 8pt;
color: blue;
text-decoration: none;
}
.fas-trash-alt {
color: red;
cursor: pointer;
}
.modal-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #000;
opacity: 0.5;
z-index: 1000;
}
.modal-container {
position: absolute;
width: 300px;
/* adjust this to your desired modal width */
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
z-index: 1001;
}
.modal-header {
background-color: #f5f5f5;
padding: 10px;
border-bottom: 1px solid #ddd;
}
.modal-body {
padding: 20px;
}
.modal-footer {
background-color: #f5f5f5;
padding: 10px;
border-top: 1px solid #ddd;
}
.btn-secondary {
color: #337ab7;
}/*# sourceMappingURL=style.css.map */ }/*# sourceMappingURL=style.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["style.scss","style.css"],"names":[],"mappings":"AAAA,kCAAA;AACA;EACI,SAAA;ACCJ;;ADEA,6BAAA;AAEA;EACI,aAAA;EACA,mBAAA;EACA,eAAA;EACA,6BAAA;EACA,oBAAA;EACA,yBAAA;EACA,iBAAA;EACA,iCAAA;EACA,cAAA;ACAJ;;ADGA;EACI,WAAA;EACA,4BAAA;ACAJ;;ADGA;EACI,SAAA;EACA,UAAA;ACAJ;;ADGA;EACI,gBAAA;EACA,qBAAA;ACAJ;;ADGA;EACI,kBAAA;ACAJ;;ADGA,GAAA;AACA;EACI,aAAA;EACA,uBAAA;EACA,mBAAA;EACA,YAAA;EACA,WAAA;EACA,kBAAA;EACA,eAAA;EACA,kBAAA;ACAJ;;ADIA;EACI,UAAA;ACDJ;;ADIA;EACI,mBAAA;EACA,2BAAA;EACA,yBAAA;EACA,kBAAA;EACA,WAAA;EACA,YAAA;EACA,uBAAA;EACA,kBAAA;EACA,wCAAA;ACDJ;;ADIA;EACI,wBAAA;ACDJ;;ADIA;EACI,YAAA;ACDJ;;ADIA;EACI,aAAA;EACA,YAAA;ACDJ;;ADIA;EACI,aAAA;EACA,YAAA;ACDJ;;ADIA;EACI,aAAA;ACDJ;;ADIA;EACI,cAAA;ACDJ;;ADIA;EACI,UAAA;ACDJ;;ADIA;EACI,WAAA;EACA,YAAA;EACA,eAAA;ACDJ;;ADIA;EACI,aAAA;EACA,kBAAA;EACA,kBAAA;ACDJ;;ADIA;;EAEI,2BAAA;ACDJ;;ADIA;;;;EAII,oCAAA;EACA,uBAAA;ACDJ;;ADIA;EACI,oBAAA;EACA,mBAAA;EACA,UAAA;ACDJ;;ADIA;EACI,oBAAA;EACA,2BAAA;EACA,UAAA;ACDJ;;ADIA;EACI,iBAAA;ACDJ;;ADIA;EACI,aAAA;EACA,YAAA;EACA,kBAAA;ACDJ;;ADIA;EACI,SAAA;EACA,UAAA;EACA,qBAAA;EACA,gBAAA;ACDJ;;ADIA;EACI,aAAA;EACA,kBAAA;EACA,YAAA;EACA,oCAAA;EACA,YAAA;EACA,aAAA;ACDJ;;ADIA;EACI,YAAA;EACA,kBAAA;EACA,kBAAA;EACA,eAAA;EACA,gBAAA;EACA,WAAA;EACA,yBAAA;EACA,YAAA;EACA,YAAA;EACA,eAAA;EACA,aAAA;ACDJ;;ADIA;EACI,aAAA;EACA,kBAAA;EACA,YAAA;EACA,sBAAA;EACA,YAAA;EACA,aAAA;EACA,sBAAA;EACA,kBAAA;EACA,wCAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;ACDJ;;ADIA;EACI,UAAA;EACA,iBAAA;EACA,eAAA;EACA,iBAAA;EACA,YAAA;ACDJ;;ADIA;EACI,aAAA;ACDJ;;ADIA;EACI,UAAA;ACDJ;;ADIA;;EAEI,YAAA;EACA,qBAAA;ACDJ;;ADIA;EACI,2BAAA;EACA,eAAA;ACDJ;;ADIA;EACI,eAAA;ACDJ;;ADIA;EACI,eAAA;ACDJ;;ADIA;EACI,eAAA;ACDJ;;ADIA;EACI,qBAAA;ACDJ;;ADIA;EACI,eAAA;ACDJ;;ADIA;;EAEI,2BAAA;ACDJ;;ADIA;EACI,aAAA;ACDJ;;ADIA;EACI,cAAA;EACA,WAAA;EACA,qBAAA;ACDJ;;ADIA;EACI,UAAA;EACA,eAAA;ACDJ;;ADIA;EACI,eAAA;EACA,MAAA;EACA,QAAA;EACA,SAAA;EACA,OAAA;EACA,sBAAA;EACA,YAAA;EACA,aAAA;ACDJ;;ADIA;EACI,kBAAA;EACA,YAAA;EACA,4CAAA;EACA,cAAA;EACA,aAAA;EACA,sBAAA;EACA,sBAAA;EACA,uCAAA;EACA,aAAA;ACDJ;;ADIA;EACI,yBAAA;EACA,aAAA;EACA,6BAAA;ACDJ;;ADIA;EACI,aAAA;ACDJ;;ADIA;EACI,yBAAA;EACA,aAAA;EACA,0BAAA;ACDJ;;ADIA;EACI,cAAA;ACDJ","file":"style.css"}

View File

@@ -19,7 +19,7 @@ body {
.ref-tab { .ref-tab {
width: 60px; width: 60px;
padding-top: 75px !important; padding-top: 60px !important;
} }
.ref-tab ul { .ref-tab ul {
@@ -44,25 +44,13 @@ body {
height: 80px; height: 80px;
width: 100%; width: 100%;
text-align: center; text-align: center;
background-color: #f56a6a;
color: #fff !important;
border: none;
border-radius: 3px;
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.8);
font-size: 14px; font-size: 14px;
margin-bottom: 3px; margin-bottom: 3px;
&:active {
background-color: #7a0016;
}
} }
.activeRef {
background-color: #3e8e41 !important;
}
.ref { .ref {
width: 35%; width: 36%;
} }
.ref>div#ref { .ref>div#ref {
@@ -78,7 +66,7 @@ body {
} }
#ref { #ref {
padding: 3px 3px 3px 10px; padding: 3px 3px 3px 3px;
} }
#passage { #passage {
@@ -104,7 +92,7 @@ body {
} }
.notes { .notes {
width: 55% width: 57%
} }
textarea#notes { textarea#notes {
@@ -116,10 +104,18 @@ textarea#notes {
#notePreview { #notePreview {
display: none; display: none;
overflow-x: scroll; overflow-x: scroll;
padding-left: 10px;
}
#notePreview ul,
#notePreview ol {
list-style-position: inside;
} }
#previewBtn.active, #previewBtn.active,
#show-hide-btn.active { #show-hide-btn.active,
.tab button.active,
#openRefBtn.active {
background-color: #f56a6a !important; background-color: #f56a6a !important;
color: white !important; color: white !important;
} }
@@ -142,6 +138,8 @@ textarea#notes {
#note-list { #note-list {
display: none; display: none;
height: 100%;
overflow-y: scroll;
} }
#note-list ul { #note-list ul {
@@ -207,7 +205,7 @@ div#refQuery #referenceSearch {
#fields-container input, #fields-container input,
#fields-container select { #fields-container select {
width: 19.5%; width: 32.5%;
display: inline-block; display: inline-block;
} }
@@ -236,6 +234,65 @@ div#refQuery #referenceSearch {
font-size: 12pt; font-size: 12pt;
} }
#ref ol,
#ref ul {
list-style-position: inside;
}
#referenceBook { #referenceBook {
display: none; display: none;
}
.recording-link {
font-size: 8pt;
color: blue;
text-decoration: none;
}
.fas-trash-alt {
color: red;
cursor: pointer;
}
.modal-backdrop {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #000;
opacity: 0.5;
z-index: 1000;
}
.modal-container {
position: absolute;
width: 300px;
/* adjust this to your desired modal width */
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
z-index: 1001;
}
.modal-header {
background-color: #f5f5f5;
padding: 10px;
border-bottom: 1px solid #ddd;
}
.modal-body {
padding: 20px;
}
.modal-footer {
background-color: #f5f5f5;
padding: 10px;
border-top: 1px solid #ddd;
}
.btn-secondary {
color: #337ab7;
} }

View File

Before

Width:  |  Height:  |  Size: 730 KiB

After

Width:  |  Height:  |  Size: 730 KiB

View File

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

Before

Width:  |  Height:  |  Size: 898 KiB

After

Width:  |  Height:  |  Size: 898 KiB

View File

@@ -12,41 +12,42 @@
"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.3.*",
"symfony/asset-mapper": "7.0.*", "symfony/asset-mapper": "7.3.*",
"symfony/console": "7.0.*", "symfony/console": "7.3.*",
"symfony/doctrine-messenger": "7.0.*", "symfony/debug-bundle": "7.3.*",
"symfony/dotenv": "7.0.*", "symfony/doctrine-messenger": "7.3.*",
"symfony/expression-language": "7.0.*", "symfony/dotenv": "7.3.*",
"symfony/expression-language": "7.3.*",
"symfony/flex": "^2", "symfony/flex": "^2",
"symfony/form": "7.0.*", "symfony/form": "7.3.*",
"symfony/framework-bundle": "7.0.*", "symfony/framework-bundle": "7.3.*",
"symfony/http-client": "7.0.*", "symfony/http-client": "7.3.*",
"symfony/intl": "7.0.*", "symfony/intl": "7.3.*",
"symfony/mailer": "7.0.*", "symfony/mailer": "7.3.*",
"symfony/mime": "7.0.*", "symfony/mime": "7.3.*",
"symfony/monolog-bundle": "^3.0", "symfony/monolog-bundle": "^3.0",
"symfony/notifier": "7.0.*", "symfony/notifier": "7.3.*",
"symfony/process": "7.0.*", "symfony/process": "7.3.*",
"symfony/property-access": "7.0.*", "symfony/property-access": "7.3.*",
"symfony/property-info": "7.0.*", "symfony/property-info": "7.3.*",
"symfony/runtime": "7.0.*", "symfony/runtime": "7.3.*",
"symfony/security-bundle": "7.0.*", "symfony/security-bundle": "7.3.*",
"symfony/serializer": "7.0.*", "symfony/serializer": "7.3.*",
"symfony/stimulus-bundle": "^2.17", "symfony/stimulus-bundle": "^2.17",
"symfony/string": "7.0.*", "symfony/string": "7.3.*",
"symfony/translation": "7.0.*", "symfony/translation": "7.3.*",
"symfony/twig-bundle": "7.0.*", "symfony/twig-bundle": "7.3.*",
"symfony/uid": "7.0.*", "symfony/uid": "7.3.*",
"symfony/ux-turbo": "^2.17", "symfony/ux-turbo": "^2.17",
"symfony/validator": "7.0.*", "symfony/validator": "7.3.*",
"symfony/web-link": "7.0.*", "symfony/web-link": "7.3.*",
"symfony/yaml": "7.0.*", "symfony/yaml": "7.3.*",
"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": {
@@ -95,16 +96,16 @@
"extra": { "extra": {
"symfony": { "symfony": {
"allow-contrib": false, "allow-contrib": false,
"require": "7.0.*" "require": "7.3.*"
} }
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.5", "phpunit/phpunit": "^9.5",
"symfony/browser-kit": "7.0.*", "symfony/browser-kit": "7.3.*",
"symfony/css-selector": "7.0.*", "symfony/css-selector": "7.3.*",
"symfony/maker-bundle": "^1.0", "symfony/maker-bundle": "^1.0",
"symfony/phpunit-bridge": "^7.0", "symfony/phpunit-bridge": "^7.2",
"symfony/stopwatch": "7.0.*", "symfony/stopwatch": "7.3.*",
"symfony/web-profiler-bundle": "7.0.*" "symfony/web-profiler-bundle": "7.3.*"
} }
} }

View File

@@ -1,3 +1,4 @@
framework: framework:
mailer: mailer:
dsn: '%env(MAILER_DSN)%' dsn: "%env(MAILER_DSN)%"
message_bus: false

Binary file not shown.

View File

@@ -1,8 +1,10 @@
version: "3"
services: services:
sermon-notes: sermon-notes:
container_name: sermon-notes container_name: sermon-notes
image: ryanprather/sermon-notes:latest image: gitea.rkprather.com/ryan/sermon-notes:1.1
ports: ports:
- 80:80 - 80:80
volumes:
- ${PWD}/data:/data
- ${PWD}/.env:/var/www/html/.env
restart: unless-stopped

View File

@@ -25,4 +25,13 @@ return [
'@hotwired/turbo' => [ '@hotwired/turbo' => [
'version' => '7.3.0', 'version' => '7.3.0',
], ],
'jquery' => [
'version' => '3.3.1',
],
'jquery-ui' => [
'version' => '1.14.1',
],
'datatables' => [
'version' => '1.10.18',
],
]; ];

View File

@@ -1,6 +1,8 @@
#!/bin/bash #!/bin/bash
rm -rf .env* if [ ! -f /var/www/html/.env ]; then
exit 0
fi
echo "APP_ENV=prod" > .env echo "APP_ENV=prod" > .env
echo "APP_DEBUG=0" >> .env echo "APP_DEBUG=0" >> .env
@@ -10,8 +12,11 @@ LENGTH=32
SECRET_KEY=$(openssl rand -base64 $LENGTH | tr -d '=' | tr -d '+' | tr -d '/' | tr -d ' ') SECRET_KEY=$(openssl rand -base64 $LENGTH | tr -d '=' | tr -d '+' | tr -d '/' | tr -d ' ')
TRIMMED_KEY=$(cut -c1-32 <<< $SECRET_KEY) TRIMMED_KEY=$(cut -c1-32 <<< $SECRET_KEY)
echo "APP_SECRET=$TRIMMED_KEY" >> .env echo "APP_SECRET=$TRIMMED_KEY" >> .env
echo "DATABASE_URL=\"sqlite:///%kernel.project_dir%/var/data.db\"" >> .env echo "DATABASE_URL=\"sqlite:////data/data.db\"" >> .env
echo "MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0" >> .env echo "MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0" >> .env
symfony console doctrine:migrations:migrate --no-interaction
COMPOSER_ALLOW_SUPERUSER=1 composer update COMPOSER_ALLOW_SUPERUSER=1 composer update
symfony console asset-map:compile
symfony console doctrine:migrations:migrate --no-interaction
chown -R www-data:www-data /data

Some files were not shown because too many files have changed in this diff Show More