Compare commits
51
Commits
1.2
..
05874c025f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05874c025f | ||
|
|
10267a261d | ||
|
|
ccab4ba655 | ||
|
|
dec1b801d8 | ||
|
|
723d046ce3 | ||
|
|
52bdf2f16e | ||
|
|
788190328b | ||
|
|
0a55500892 | ||
|
|
29f8b7461f | ||
|
|
eb28e41930 | ||
|
|
559ab4e2a8 | ||
|
|
4d1cfeadcd | ||
|
|
819f8b35b9 | ||
|
|
0e97468f7d | ||
|
|
9f9ee82c6b | ||
|
|
e03dd0aaf7 | ||
|
|
525c6a47a6 | ||
|
|
5af898a702 | ||
|
|
7ee5437f8a | ||
|
|
fa7af9d5c7 | ||
|
|
77ae65c2bd | ||
|
|
9a496c07b0 | ||
|
|
95911a210a | ||
|
|
9482ea3708 | ||
|
|
82d03aeb6a | ||
|
|
3f1c9841a2 | ||
|
|
9fdda7e2ba | ||
|
|
2e4028d623 | ||
|
|
5e6d63ef26 | ||
|
|
39ef5a4c49 | ||
|
|
db9e1cd469 | ||
|
|
b1726bba34 | ||
|
|
4ed6c18825 | ||
|
|
5ac8736004 | ||
|
|
36f3ab3bd1 | ||
|
|
4a1777d160 | ||
|
|
2a22a3e027 | ||
|
|
c948b1e39d | ||
|
|
840058873a | ||
|
|
d12b94b4b1 | ||
|
|
26b9e6fe97 | ||
|
|
3aab29cd03 | ||
|
|
d06f24b1fa | ||
|
|
ed774a5a37 | ||
|
|
6664a7c71e | ||
|
|
4be33834d4 | ||
|
|
b445295959 | ||
|
|
20ba17c684 | ||
|
|
323e668ac9 | ||
|
|
0d384a8fa3 | ||
|
|
b14a0c23f6 |
@@ -5,6 +5,7 @@
|
|||||||
/public/bundles/
|
/public/bundles/
|
||||||
/var/
|
/var/
|
||||||
/vendor/
|
/vendor/
|
||||||
|
/migrations/
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
###> phpunit/phpunit ###
|
###> phpunit/phpunit ###
|
||||||
@@ -23,3 +24,5 @@
|
|||||||
###< symfony/asset-mapper ###
|
###< symfony/asset-mapper ###
|
||||||
|
|
||||||
/references/
|
/references/
|
||||||
|
composer.lock
|
||||||
|
.continue
|
||||||
+31
-11
@@ -1,4 +1,4 @@
|
|||||||
FROM php:8.4-apache
|
FROM php:8.5-apache
|
||||||
|
|
||||||
RUN apt update && \
|
RUN apt update && \
|
||||||
apt upgrade -y && \
|
apt upgrade -y && \
|
||||||
@@ -11,11 +11,14 @@ RUN apt update && \
|
|||||||
libjpeg-dev \
|
libjpeg-dev \
|
||||||
libicu-dev \
|
libicu-dev \
|
||||||
libpq-dev \
|
libpq-dev \
|
||||||
|
libsqlite3-dev \
|
||||||
sqlite3 \
|
sqlite3 \
|
||||||
curl \
|
curl \
|
||||||
git \
|
git \
|
||||||
|
cron \
|
||||||
nano
|
nano
|
||||||
|
|
||||||
|
|
||||||
RUN docker-php-ext-configure gd --with-jpeg
|
RUN docker-php-ext-configure gd --with-jpeg
|
||||||
RUN docker-php-ext-configure zip
|
RUN docker-php-ext-configure zip
|
||||||
|
|
||||||
@@ -28,7 +31,8 @@ RUN docker-php-ext-install \
|
|||||||
xml \
|
xml \
|
||||||
intl \
|
intl \
|
||||||
pdo_mysql \
|
pdo_mysql \
|
||||||
pdo_pgsql
|
pdo_pgsql \
|
||||||
|
pdo_sqlite
|
||||||
|
|
||||||
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
|
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
|
||||||
php composer-setup.php --install-dir=/usr/local/bin --filename=composer && \
|
php composer-setup.php --install-dir=/usr/local/bin --filename=composer && \
|
||||||
@@ -37,22 +41,38 @@ RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" &&
|
|||||||
RUN curl -sS https://get.symfony.com/cli/installer | bash && \
|
RUN curl -sS https://get.symfony.com/cli/installer | bash && \
|
||||||
mv /root/.symfony5/bin/symfony /usr/local/bin/symfony
|
mv /root/.symfony5/bin/symfony /usr/local/bin/symfony
|
||||||
|
|
||||||
COPY . /var/www/html/
|
ARG CACHEBURST=1
|
||||||
|
ARG BRANCH=master
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
RUN git clone -b ${BRANCH} --single-branch https://gitea.rkprather.com/ryan/sermon-notes.git ./
|
||||||
|
RUN git config --global --add safe.directory /var/www/html
|
||||||
|
|
||||||
RUN mv 000-default.conf /etc/apache2/sites-available/
|
RUN mv 000-default.conf /etc/apache2/sites-available/
|
||||||
RUN rm /var/www/html/.env*
|
|
||||||
RUN rm -rf /var/www/html/var/*
|
RUN echo "20 1 * * 6 root cd /var/www/html && /usr/local/bin/php bin/console app:get-audio > /proc/1/fd/1 2>&1" > /etc/cron.d/get-audio
|
||||||
RUN rm -rf /var/www/html/vendor
|
RUN chmod 644 /etc/cron.d/get-audio
|
||||||
RUN rm -rf /var/www/html/tests
|
RUN crontab /etc/cron.d/get-audio
|
||||||
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 mkdir /data
|
RUN mkdir /data
|
||||||
|
|
||||||
RUN mkdir /var/www/html/var/cache
|
RUN mkdir -p /var/www/html/var/cache/prod
|
||||||
RUN mkdir /var/www/html/var/log
|
RUN mkdir -p /var/www/html/var/log
|
||||||
|
|
||||||
RUN chown -R 33:33 /var/www/html /data
|
RUN chown -R 33:33 /var/www/html /data
|
||||||
RUN chmod -R 755 /var/www/html /data
|
RUN find /var/www/html -type d -exec chmod 755 '{}' \;
|
||||||
|
RUN find /var/www/html -type f -exec chmod 644 '{}' \;
|
||||||
|
RUN chmod 755 /data
|
||||||
|
RUN a2enmod rewrite setenvif headers
|
||||||
|
|
||||||
|
COPY prod.env /var/www/html/.env
|
||||||
|
RUN /usr/local/bin/php bin/console importmap:install
|
||||||
|
RUN /usr/local/bin/php bin/console asset-map:compile
|
||||||
|
RUN rm /var/www/html/.env
|
||||||
|
RUN chmod +x /var/www/html/bin/entrypoint.sh
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["/var/www/html/bin/entrypoint.sh"]
|
||||||
|
CMD ["apache2-foreground"]
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ This was my first publicly available docker container so I did not realize what
|
|||||||
5. [`compose.shared-db.yml`](https://gitea.rkprather.com/ryan/sermon-notes/raw/branch/main/docker/compose.shared-db.yml) - compose file with no database image because you are planning on using an existing database container or bare metal server
|
5. [`compose.shared-db.yml`](https://gitea.rkprather.com/ryan/sermon-notes/raw/branch/main/docker/compose.shared-db.yml) - compose file with no database image because you are planning on using an existing database container or bare metal server
|
||||||
4. Pull the image `docker pull gitea.rkprather.com/ryan/sermon-notes:latest`
|
4. Pull the image `docker pull gitea.rkprather.com/ryan/sermon-notes:latest`
|
||||||
5. **NOTE: IF UPGRADING SKIP THIS STEP!!!** - Run the setup script, this will setup your .env file so that when you start the container everything will be where it is supposed to be.
|
5. **NOTE: IF UPGRADING SKIP THIS STEP!!!** - Run the setup script, this will setup your .env file so that when you start the container everything will be where it is supposed to be.
|
||||||
- `docker run --rm -it -v ${PWD}/.env:/var/www/html/.env gitea.rkprather.com/ryan/sermon-notes:latest /var/www/html/setup.php --{database-type} {--shared}`
|
- `docker run --rm -it -v ${PWD}/.env:/var/www/html/.env gitea.rkprather.com/ryan/sermon-notes:latest php /var/www/html/setup.php --{database-type} {--shared}`
|
||||||
- `{database-type}` = `sqlite`, `mysql`, `mariadb`, or `pgsql`
|
- `{database-type}` = `sqlite`, `mysql`, `mariadb`, or `pgsql`
|
||||||
- If you intend on this being connected to a shared database make sure that you specify `--shared`.
|
- If you intend on this being connected to a shared database make sure that you specify `--shared`.
|
||||||
6. Start the container with compose `docker compose up -d`
|
6. Start the container with compose `docker compose up -d`
|
||||||
7. **NOTE: IF UPGRADING SKIP THIS STEP!!!** Run `docker exec -it sermon-notes /var/www/html/install.php`. This will run the `php composer` to populate the database with all the desired reference material.
|
7. **NOTE: IF UPGRADING SKIP THIS STEP!!!** Run `docker exec -it sermon-notes php /var/www/html/install.php`. This will run the `php composer` to populate the database with all the desired reference material.
|
||||||
|
- NOTE: You will see deprecation warnings, you can ignore these
|
||||||
8. 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.
|
8. 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
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
:root {
|
||||||
|
--dark: #3d4449;
|
||||||
|
--primary: #00760e;
|
||||||
|
--light: #f5f6f7;
|
||||||
|
--medium: #d1d1d1;
|
||||||
|
--text: var(--light);
|
||||||
|
--background: var(--dark);
|
||||||
|
--recording: #00f;
|
||||||
|
--success: #080;
|
||||||
|
--warning: orange;
|
||||||
|
--error: #f00;
|
||||||
|
--border: var(--primary);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}/*# sourceMappingURL=dark-mode.css.map */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["dark-mode.scss","dark-mode.css"],"names":[],"mappings":"AACA;EACC,eAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,oBAAA;EACA,yBAAA;EACA,iBAAA;EACA,eAAA;EACA,iBAAA;EACA,aAAA;EACA,wBAAA;EAEA,SAAA;EACA,UAAA;EACA,SAAA;EACA,aAAA;EACA,wBAAA;ACDD","file":"dark-mode.css"}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// dark mode color scheme
|
||||||
|
:root {
|
||||||
|
--dark: #3d4449;
|
||||||
|
--primary: #00760e;
|
||||||
|
--light: #f5f6f7;
|
||||||
|
--medium: #d1d1d1;
|
||||||
|
--text: var(--light);
|
||||||
|
--background: var(--dark);
|
||||||
|
--recording: #00f;
|
||||||
|
--success: #080;
|
||||||
|
--warning: orange;
|
||||||
|
--error: #f00;
|
||||||
|
--border: var(--primary);
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
Vendored
-95
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
|||||||
|
:root {
|
||||||
|
--dark: #3d4449;
|
||||||
|
--primary: #f56a6a;
|
||||||
|
--light: #f5f6f7;
|
||||||
|
--medium: #d1d1d1;
|
||||||
|
--text: var(--dark);
|
||||||
|
--background: var(--light);
|
||||||
|
--recording: #00f;
|
||||||
|
--success: #080;
|
||||||
|
--warning: orange;
|
||||||
|
--error: #f00;
|
||||||
|
--border: var(--primary);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}/*# sourceMappingURL=light-mode.css.map */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["light-mode.scss","light-mode.css"],"names":[],"mappings":"AACA;EACC,eAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;EACA,0BAAA;EACA,iBAAA;EACA,eAAA;EACA,iBAAA;EACA,aAAA;EACA,wBAAA;EAEA,SAAA;EACA,UAAA;EACA,SAAA;EACA,aAAA;EACA,wBAAA;ACDD","file":"light-mode.css"}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// light mode color scheme
|
||||||
|
:root {
|
||||||
|
--dark: #3d4449;
|
||||||
|
--primary: #f56a6a;
|
||||||
|
--light: #f5f6f7;
|
||||||
|
--medium: #d1d1d1;
|
||||||
|
--text: var(--dark);
|
||||||
|
--background: var(--light);
|
||||||
|
--recording: #00f;
|
||||||
|
--success: #080;
|
||||||
|
--warning: orange;
|
||||||
|
--error: #f00;
|
||||||
|
--border: var(--primary);
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
@@ -0,0 +1,620 @@
|
|||||||
|
@import url(fontawesome-all.min.css);
|
||||||
|
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,400italic,600italic|Roboto+Slab:400,700");
|
||||||
|
/* Global style settings */
|
||||||
|
@media screen and (max-width: 1050px) {
|
||||||
|
#sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.note-header, #note-list {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 50px 50% 1fr;
|
||||||
|
gap: 5px 0;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-template-areas: "mobile-note-header" "notes" "ref-text";
|
||||||
|
}
|
||||||
|
.container .ref-text {
|
||||||
|
height: 99%;
|
||||||
|
width: 98vw;
|
||||||
|
margin-left: 1vw;
|
||||||
|
}
|
||||||
|
.ref-header, .ref-btn-header, .ref-btn, .note-header, .fields-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.mobile-note-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.mobile-note-header h2 {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
.mobile-note-header button {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
.notes {
|
||||||
|
width: 98vw;
|
||||||
|
margin-left: 1vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 1fr 50px;
|
||||||
|
gap: 2px 0;
|
||||||
|
grid-template-areas: "note-text" "fields-container";
|
||||||
|
grid-area: notes;
|
||||||
|
}
|
||||||
|
.notes .fields-container {
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 99.5%;
|
||||||
|
}
|
||||||
|
.notes .fields-container input[type=text], .notes .fields-container select {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
height: 1.5em;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0.375em;
|
||||||
|
border: solid 1px var(--border);
|
||||||
|
outline: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.65;
|
||||||
|
flex-basis: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 50px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
.notes .note-text {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.notes .note-text textarea {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "Roboto Sans";
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 1051px) and (max-width: 1079px) {
|
||||||
|
#sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.mobile-note-header, #note-list {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
height: 100vh;
|
||||||
|
grid-template-columns: 60px 35% 1fr;
|
||||||
|
grid-template-rows: 60px 1.8fr;
|
||||||
|
gap: 5px 5px;
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-template-areas: "ref-btn-header ref-header note-header" "ref-btn ref-text notes";
|
||||||
|
}
|
||||||
|
.container .fields-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.bottom-row {
|
||||||
|
height: calc(100vh - 75px);
|
||||||
|
}
|
||||||
|
.notes {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 75px 1fr;
|
||||||
|
gap: 2px 0px;
|
||||||
|
grid-template-areas: "fields-container" "note-text";
|
||||||
|
grid-area: notes;
|
||||||
|
}
|
||||||
|
.notes .fields-container {
|
||||||
|
justify-content: space-evenly;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 99.5%;
|
||||||
|
}
|
||||||
|
.notes .fields-container input[type=text], .notes .fields-container select {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
height: 1.5em;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0.375em;
|
||||||
|
border: solid 1px var(--dark);
|
||||||
|
outline: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.65;
|
||||||
|
flex-basis: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 50px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
.notes .note-text {
|
||||||
|
height: 100%;
|
||||||
|
width: 98.5%;
|
||||||
|
}
|
||||||
|
.notes .note-text textarea {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "Roboto Sans";
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
.note-header, .ref-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.note-header h2, .ref-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 1080px) {
|
||||||
|
.mobile-note-header, #note-list {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
height: 100vh;
|
||||||
|
grid-template-columns: 60px 35% 0.995fr;
|
||||||
|
grid-template-rows: 60px 1.8fr;
|
||||||
|
gap: 5px 5px;
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-template-areas: "ref-btn-header ref-header note-header" "ref-btn ref-text notes";
|
||||||
|
}
|
||||||
|
.container .fields-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.bottom-row {
|
||||||
|
height: calc(100vh - 75px);
|
||||||
|
}
|
||||||
|
.notes {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 75px 1fr;
|
||||||
|
gap: 2px 0px;
|
||||||
|
grid-template-areas: "fields-container" "note-text";
|
||||||
|
grid-area: notes;
|
||||||
|
}
|
||||||
|
.notes .fields-container {
|
||||||
|
justify-content: space-evenly;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 99.5%;
|
||||||
|
}
|
||||||
|
.notes .fields-container input[type=text], .notes .fields-container select {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
height: 1.5em;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0.375em;
|
||||||
|
border: solid 1px var(--dark);
|
||||||
|
outline: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.65;
|
||||||
|
flex-basis: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 2;
|
||||||
|
min-width: 50px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
.notes .note-text {
|
||||||
|
height: 100%;
|
||||||
|
width: 98.5%;
|
||||||
|
}
|
||||||
|
.notes .note-text textarea {
|
||||||
|
height: 99%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "Roboto Sans";
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
.note-header, .ref-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.note-header h2, .ref-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.note-header select, .ref-header select {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
height: 1.5em;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0.375em;
|
||||||
|
border: solid 1px var(--dark);
|
||||||
|
outline: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.65;
|
||||||
|
flex-basis: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 2;
|
||||||
|
min-width: 50px;
|
||||||
|
max-width: 200px;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#previewBtn.active,
|
||||||
|
#show-hide-btn.active,
|
||||||
|
.ref-list button.active,
|
||||||
|
#open-ref.active,
|
||||||
|
#mobile-open-ref.active {
|
||||||
|
background-color: var(--primary) !important;
|
||||||
|
color: var(--light) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#save-check {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ui-datepicker-div {
|
||||||
|
z-index: 11 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Roboto Slab", "Open Sans", sans-serif !important;
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
color: var(--text);
|
||||||
|
font-family: "Roboto Slab", serif;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0 0 1.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab button {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 80px;
|
||||||
|
width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
.button {
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: inset 0 0 0 2px var(--primary);
|
||||||
|
color: var(--primary) !important;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
font-family: "Roboto Slab", serif;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: 700;
|
||||||
|
height: 3em;
|
||||||
|
letter-spacing: 0.075em;
|
||||||
|
line-height: 3em;
|
||||||
|
padding: 0 2em;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.button i {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-warning {
|
||||||
|
border: solid 2px var(--warning) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-error {
|
||||||
|
border: solid 2px var(--error) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-btn-header {
|
||||||
|
grid-area: ref-btn-header;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-btn {
|
||||||
|
grid-area: ref-btn;
|
||||||
|
}
|
||||||
|
.ref-btn .ref-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: start;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 1px;
|
||||||
|
margin: 1px;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-text {
|
||||||
|
grid-area: ref-text;
|
||||||
|
display: block;
|
||||||
|
overflow-y: scroll;
|
||||||
|
border: var(--border) solid 1px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.ref-text h2 {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#refQuery {
|
||||||
|
position: fixed;
|
||||||
|
background-color: var(--background);
|
||||||
|
z-index: 999;
|
||||||
|
width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
border: solid 2px var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
|
||||||
|
margin: 10px 50px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chapter-range, #verse-range {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-header {
|
||||||
|
grid-area: note-header;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notePreview {
|
||||||
|
display: none;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-header {
|
||||||
|
grid-area: ref-header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** drowndown button */
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown select {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
.dropdown select option:hover {
|
||||||
|
background-color: var(--primary);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* slider switch */
|
||||||
|
/* The switch - the box around the slider */
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 42px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide default HTML checkbox */
|
||||||
|
.switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The slider */
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
left: 3px;
|
||||||
|
bottom: 3px;
|
||||||
|
background-color: white;
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider {
|
||||||
|
background-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus + .slider {
|
||||||
|
box-shadow: 0 0 1px var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
transform: translateX(18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rounded sliders */
|
||||||
|
.slider.round {
|
||||||
|
border-radius: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider.round:before {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#refQuery, #passage-popup, #modal-backdrop, #modal-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sidebar
|
||||||
|
*/
|
||||||
|
#sidebar {
|
||||||
|
transition: margin-left 0.5s ease, box-shadow 0.5s ease;
|
||||||
|
background-color: var(--background);
|
||||||
|
font-size: 0.9em;
|
||||||
|
position: relative;
|
||||||
|
width: 25em;
|
||||||
|
border-right: solid 2px var(--border);
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
#sidebar .toggle {
|
||||||
|
text-decoration: none;
|
||||||
|
transition: left 0.5s ease;
|
||||||
|
border: 0;
|
||||||
|
display: block;
|
||||||
|
height: 4.5em;
|
||||||
|
left: 25em;
|
||||||
|
line-height: 4.5em;
|
||||||
|
outline: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
text-indent: -15em;
|
||||||
|
white-space: nowrap;
|
||||||
|
top: 0;
|
||||||
|
width: 6em;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
#sidebar > .inner {
|
||||||
|
position: relative;
|
||||||
|
width: 25em;
|
||||||
|
}
|
||||||
|
#sidebar > .inner > * {
|
||||||
|
border-bottom: solid 2px var(--medium);
|
||||||
|
margin: 0 0 3.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: stretch;
|
||||||
|
align-content: flex-start;
|
||||||
|
max-width: 1060px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.inner > .alt {
|
||||||
|
background-color: var(--light);
|
||||||
|
border-bottom: 0;
|
||||||
|
margin: -2.22em -2.33em 1em -2.22em;
|
||||||
|
padding: 2.22em;
|
||||||
|
width: calc(100% - 4.44em);
|
||||||
|
}
|
||||||
|
|
||||||
|
header.major {
|
||||||
|
border-bottom: solid 3px var(--border);
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
padding: 0 0.75em 0.5em 0;
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu ul {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
color: var(--dark);
|
||||||
|
font-family: "Roboto Slab", serif;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.075em;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu ul li {
|
||||||
|
border-top: solid 1px var(--medium);
|
||||||
|
margin: 0.5em 0 0 0;
|
||||||
|
padding: 0.5em 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu ul li:first-child {
|
||||||
|
border-top: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu ul a {
|
||||||
|
border-bottom: 0;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding: 0.625em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar .toggle::before {
|
||||||
|
content: "\f0c9";
|
||||||
|
font-size: 2rem;
|
||||||
|
height: inherit;
|
||||||
|
left: 0;
|
||||||
|
line-height: inherit;
|
||||||
|
position: absolute;
|
||||||
|
text-indent: 0;
|
||||||
|
top: 0;
|
||||||
|
width: inherit;
|
||||||
|
z-index: 99;
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
font-weight: 900;
|
||||||
|
display: inline-block;
|
||||||
|
font-style: normal;
|
||||||
|
font-variant: normal;
|
||||||
|
text-rendering: auto;
|
||||||
|
text-transform: none !important;
|
||||||
|
}/*# sourceMappingURL=styles.css.map */
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,699 @@
|
|||||||
|
// Set up the global font styles
|
||||||
|
@import url(fontawesome-all.min.css);
|
||||||
|
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,400italic,600italic|Roboto+Slab:400,700");
|
||||||
|
/* Global style settings */
|
||||||
|
@media screen and (max-width: 1050px) {
|
||||||
|
#sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-header, #note-list {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 50px 50% 1fr;
|
||||||
|
gap: 5px 0;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-template-areas:
|
||||||
|
"mobile-note-header"
|
||||||
|
"notes"
|
||||||
|
"ref-text";
|
||||||
|
|
||||||
|
.ref-text {
|
||||||
|
height: 99%;
|
||||||
|
width: 98vw;
|
||||||
|
margin-left: 1vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-header, .ref-btn-header, .ref-btn, .note-header, .fields-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-note-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes {
|
||||||
|
width: 98vw;
|
||||||
|
margin-left: 1vw;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 1fr 50px;
|
||||||
|
gap: 2px 0;
|
||||||
|
grid-template-areas: "note-text" "fields-container";
|
||||||
|
|
||||||
|
grid-area: notes;
|
||||||
|
|
||||||
|
.fields-container {
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 99.5%;
|
||||||
|
|
||||||
|
input[type="text"], select {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
height: 1.5em;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-ms-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0.375em;
|
||||||
|
border: solid 1px var(--border);
|
||||||
|
outline: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.65;
|
||||||
|
flex-basis: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 50px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-text {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: 'Roboto Sans';
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1051px) and (max-width: 1079px) {
|
||||||
|
#sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-note-header, #note-list {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
height: 100vh;
|
||||||
|
grid-template-columns: 60px 35% 1fr;
|
||||||
|
grid-template-rows: 60px 1.8fr;
|
||||||
|
gap: 5px 5px;
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-template-areas:
|
||||||
|
"ref-btn-header ref-header note-header"
|
||||||
|
"ref-btn ref-text notes";
|
||||||
|
|
||||||
|
.fields-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-row {
|
||||||
|
height: calc(100vh - 75px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 75px 1fr;
|
||||||
|
gap: 2px 0px;
|
||||||
|
grid-template-areas: "fields-container" "note-text";
|
||||||
|
|
||||||
|
grid-area: notes;
|
||||||
|
|
||||||
|
.fields-container {
|
||||||
|
justify-content: space-evenly;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 99.5%;
|
||||||
|
// height: 75px;
|
||||||
|
|
||||||
|
input[type="text"], select {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
height: 1.5em;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-ms-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0.375em;
|
||||||
|
border: solid 1px var(--dark);
|
||||||
|
outline: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.65;
|
||||||
|
flex-basis: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 50px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-text {
|
||||||
|
height: 100%;
|
||||||
|
width: 98.5%;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: 'Roboto Sans';
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-header, .ref-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1080px) {
|
||||||
|
#sidebar {
|
||||||
|
//display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-note-header, #note-list {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
height: 100vh;
|
||||||
|
grid-template-columns: 60px 35% 0.995fr;
|
||||||
|
grid-template-rows: 60px 1.8fr;
|
||||||
|
gap: 5px 5px;
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-template-areas:
|
||||||
|
"ref-btn-header ref-header note-header"
|
||||||
|
"ref-btn ref-text notes";
|
||||||
|
|
||||||
|
.fields-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-row {
|
||||||
|
height: calc(100vh - 75px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 75px 1fr;
|
||||||
|
gap: 2px 0px;
|
||||||
|
grid-template-areas: "fields-container" "note-text";
|
||||||
|
|
||||||
|
grid-area: notes;
|
||||||
|
|
||||||
|
.fields-container {
|
||||||
|
justify-content: space-evenly;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 99.5%;
|
||||||
|
// height: 75px;
|
||||||
|
|
||||||
|
input[type="text"], select {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
height: 1.5em;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-ms-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0.375em;
|
||||||
|
border: solid 1px var(--dark);
|
||||||
|
outline: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.65;
|
||||||
|
flex-basis: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 2;
|
||||||
|
min-width: 50px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-text {
|
||||||
|
height: 100%;
|
||||||
|
width: 98.5%;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 99%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
font-family: 'Roboto Sans';
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-header, .ref-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
height: 1.5em;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-ms-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0.375em;
|
||||||
|
border: solid 1px var(--dark);
|
||||||
|
outline: 0;
|
||||||
|
padding: 0 1em;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.65;
|
||||||
|
flex-basis: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 2;
|
||||||
|
min-width: 50px;
|
||||||
|
max-width: 200px;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#previewBtn.active,
|
||||||
|
#show-hide-btn.active,
|
||||||
|
.ref-list button.active,
|
||||||
|
#open-ref.active,
|
||||||
|
#mobile-open-ref.active {
|
||||||
|
background-color: var(--primary) !important;
|
||||||
|
color: var(--light) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#save-check {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ui-datepicker-div {
|
||||||
|
z-index: 11 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Roboto Slab', 'Open Sans', sans-serif !important;
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
color: var(--text);
|
||||||
|
font-family: 'Roboto Slab', serif;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0 0 1.5em 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab button {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 80px;
|
||||||
|
width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
.button {
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-ms-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
-moz-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
|
-webkit-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
|
-ms-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
|
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: inset 0 0 0 2px var(--primary);
|
||||||
|
color: var(--primary) !important;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
font-family: "Roboto Slab", serif;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: 700;
|
||||||
|
height: 3em;
|
||||||
|
letter-spacing: 0.075em;
|
||||||
|
line-height: 3em;
|
||||||
|
padding: 0 2em;
|
||||||
|
// min-width: 30px;
|
||||||
|
// max-width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.button i {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-warning {
|
||||||
|
border: solid 2px var(--warning) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-error {
|
||||||
|
border: solid 2px var(--error) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-btn-header { grid-area: ref-btn-header; }
|
||||||
|
|
||||||
|
.ref-btn {
|
||||||
|
grid-area: ref-btn;
|
||||||
|
|
||||||
|
.ref-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: start;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 1px;
|
||||||
|
margin: 1px;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-text {
|
||||||
|
grid-area: ref-text;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
overflow-y: scroll;
|
||||||
|
// width: 100%;
|
||||||
|
border: var(--border) solid 1px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 1;
|
||||||
|
// position: relative;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#refQuery {
|
||||||
|
position: fixed;
|
||||||
|
background-color: var(--background);
|
||||||
|
z-index: 999;
|
||||||
|
width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
border: solid 2px var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
|
||||||
|
margin: 10px 50px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chapter-range, #verse-range {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-header {
|
||||||
|
grid-area: note-header;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notePreview {
|
||||||
|
display: none;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ref-header {
|
||||||
|
grid-area: ref-header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** drowndown button */
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
// height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown select {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
|
||||||
|
option:hover {
|
||||||
|
background-color: var(--primary);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* slider switch */
|
||||||
|
/* The switch - the box around the slider */
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 42px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide default HTML checkbox */
|
||||||
|
.switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The slider */
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
-webkit-transition: .5s;
|
||||||
|
transition: .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
left: 3px;
|
||||||
|
bottom: 3px;
|
||||||
|
background-color: white;
|
||||||
|
-webkit-transition: 0.5s;
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider {
|
||||||
|
background-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus + .slider {
|
||||||
|
box-shadow: 0 0 1px var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
-webkit-transform: translateX(18px);
|
||||||
|
-ms-transform: translateX(18px);
|
||||||
|
transform: translateX(18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rounded sliders */
|
||||||
|
.slider.round {
|
||||||
|
border-radius: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider.round:before {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#refQuery, #passage-popup, #modal-backdrop, #modal-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sidebar
|
||||||
|
*/
|
||||||
|
#sidebar {
|
||||||
|
transition: margin-left 0.5s ease, box-shadow 0.5s ease;
|
||||||
|
background-color: var(--background);
|
||||||
|
font-size: 0.9em;
|
||||||
|
position: relative;
|
||||||
|
width: 25em;
|
||||||
|
border-right: solid 2px var(--border);
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
text-decoration: none;
|
||||||
|
transition: left 0.5s ease;
|
||||||
|
border: 0;
|
||||||
|
display: block;
|
||||||
|
height: 4.5em;
|
||||||
|
left: 25em;
|
||||||
|
line-height: 4.5em;
|
||||||
|
outline: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
text-indent: -15em;
|
||||||
|
white-space: nowrap;
|
||||||
|
top: 0;
|
||||||
|
width: 6em;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .inner {
|
||||||
|
//padding: 2.22em 2.22em 2.44em 2.22em;
|
||||||
|
position: relative;
|
||||||
|
width: 25em;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
border-bottom: solid 2px var(--medium);
|
||||||
|
margin: 0 0 3.5em 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: stretch;
|
||||||
|
align-content: flex-start;
|
||||||
|
max-width: 1060px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
> .alt {
|
||||||
|
background-color: var(--light);
|
||||||
|
border-bottom: 0;
|
||||||
|
margin: -2.22em -2.33em 1.0em -2.22em;
|
||||||
|
padding: 2.22em;
|
||||||
|
width: calc(100% - 4.44em);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header.major {
|
||||||
|
border-bottom: solid 3px var(--border);
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
padding: 0 0.75em 0.5em 0;
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu ul {
|
||||||
|
user-select: none;
|
||||||
|
color: var(--dark);
|
||||||
|
font-family: "Roboto Slab", serif;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.075em;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu ul li {
|
||||||
|
border-top: solid 1px var(--medium);
|
||||||
|
margin: 0.5em 0 0 0;
|
||||||
|
padding: 0.5em 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu ul li:first-child {
|
||||||
|
border-top: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu ul a {
|
||||||
|
border-bottom: 0;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding: 0.625em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar .toggle::before {
|
||||||
|
content: '\f0c9';
|
||||||
|
font-size: 2rem;
|
||||||
|
height: inherit;
|
||||||
|
left: 0;
|
||||||
|
line-height: inherit;
|
||||||
|
position: absolute;
|
||||||
|
text-indent: 0;
|
||||||
|
top: 0;
|
||||||
|
width: inherit;
|
||||||
|
z-index: 99;
|
||||||
|
font-family: 'Font Awesome 5 Free';
|
||||||
|
font-weight: 900;
|
||||||
|
display: inline-block;
|
||||||
|
font-style: normal;
|
||||||
|
font-variant: normal;
|
||||||
|
text-rendering: auto;
|
||||||
|
text-transform: none !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,439 @@
|
|||||||
|
import * as note from './note.js';
|
||||||
|
import { state } from './state.js';
|
||||||
|
import * as ref from './reference.js';
|
||||||
|
|
||||||
|
// Get the link element
|
||||||
|
var tabs = [];
|
||||||
|
let controller;
|
||||||
|
|
||||||
|
// Function to change the CSS file based on checkbox state
|
||||||
|
/**
|
||||||
|
* Method to toggle dark/light mode
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
|
export function toggleDarkMode(event) {
|
||||||
|
let mainCssLink = $('link[data-css="mode"');
|
||||||
|
if(mainCssLink.length > 0) {
|
||||||
|
mainCssLink[0].href = (event.target.checked ? mainCssLink.data('dark') : mainCssLink.data('light'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the visibility of the fields container and updates the active state of the show/hide button.
|
||||||
|
*
|
||||||
|
* @param Event e
|
||||||
|
* @param boolean forceShow
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
export function toggleFields(e, forceShow = false) {
|
||||||
|
const fieldsContainer = document.querySelector('.fields-container');
|
||||||
|
let showHideBtn = document.getElementById('show-hide-btn');
|
||||||
|
let mobileShowHideBtn = document.getElementById('mobile-show-hide-btn');
|
||||||
|
|
||||||
|
if (forceShow || !fieldsContainer.classList.contains('show')) {
|
||||||
|
fieldsContainer.classList.add('show');
|
||||||
|
fieldsContainer.style.display = 'flex';
|
||||||
|
showHideBtn.classList.add('active');
|
||||||
|
mobileShowHideBtn.classList.add('active');
|
||||||
|
} else {
|
||||||
|
fieldsContainer.classList.remove('show');
|
||||||
|
fieldsContainer.style.display = 'none';
|
||||||
|
showHideBtn.classList.remove('active');
|
||||||
|
mobileShowHideBtn.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases the font size of the element with the id 'ref' by 1 point.
|
||||||
|
*
|
||||||
|
* @return {void} This function does not return a value.
|
||||||
|
*/
|
||||||
|
export function increaseFont() {
|
||||||
|
var currentSize = document.querySelector('#ref-text').style.fontSize;
|
||||||
|
const newSize = parseInt(currentSize) + 1;
|
||||||
|
document.querySelector('#ref-text').style.fontSize = newSize + 'pt';
|
||||||
|
document.querySelector('#notes').style.fontSize = newSize + 'pt';
|
||||||
|
document.querySelector('#notePreview').style.fontSize = newSize + 'pt';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decreases the font size of the element with the id 'ref' by 1 point.
|
||||||
|
*
|
||||||
|
* @return {void} This function does not return a value.
|
||||||
|
*/
|
||||||
|
export function decreaseFont() {
|
||||||
|
var currentSize = document.querySelector('#ref-text').style.fontSize;
|
||||||
|
const newSize = parseInt(currentSize) - 1;
|
||||||
|
document.querySelector('#ref-text').style.fontSize = newSize + 'pt';
|
||||||
|
document.querySelector('#notes').style.fontSize = newSize + 'pt';
|
||||||
|
document.querySelector('#notePreview').style.fontSize = newSize + 'pt';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
export function previewNote() {
|
||||||
|
var noteText = document.querySelector('#notes');
|
||||||
|
var notePreview = document.querySelector('#notePreview');
|
||||||
|
var previewButton = document.querySelector('#previewBtn');
|
||||||
|
|
||||||
|
const title = document.querySelector('#noteTitle');
|
||||||
|
const speaker = document.querySelector('#speaker');
|
||||||
|
const passage = document.querySelector('#passage');
|
||||||
|
|
||||||
|
const markdownPreview = "# " + title.value + " - " +
|
||||||
|
speaker.options[speaker.selectedIndex].text + " - " + passage.value + "\n\n" + noteText.value;
|
||||||
|
|
||||||
|
notePreview.innerHTML = state.md.render(markdownPreview);
|
||||||
|
|
||||||
|
if (previewButton.classList.contains('active')) {
|
||||||
|
document.querySelector('.note-text').style.display = 'block';
|
||||||
|
notePreview.style.display = 'none';
|
||||||
|
previewButton.classList.remove('active');
|
||||||
|
} else {
|
||||||
|
document.querySelector('.note-text').style.display = 'none';
|
||||||
|
notePreview.style.display = 'block';
|
||||||
|
previewButton.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
note.findLinks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
state.textDirty = true;
|
||||||
|
document.querySelector('.mobile-note-header h2').classList.add('dirty');
|
||||||
|
document.querySelector('.note-header h2').classList.add('dirty');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('dark-mode-checkbox').addEventListener('click', toggleDarkMode);
|
||||||
|
document.getElementById('mobile-dark-mode-checkbox').addEventListener('click', toggleDarkMode);
|
||||||
|
|
||||||
|
document.getElementById('show-hide-btn').addEventListener('click', toggleFields);
|
||||||
|
document.getElementById('mobile-show-hide-btn').addEventListener('click', toggleFields);
|
||||||
|
|
||||||
|
document.getElementById('increaseFont').addEventListener('click', increaseFont);
|
||||||
|
document.getElementById('decreaseFont').addEventListener('click', decreaseFont);
|
||||||
|
document.getElementById('open-ref').addEventListener('click', openRef, {closeSidebar: false});
|
||||||
|
document.getElementById('mobile-open-ref').addEventListener('click', openRef, {closeSidebar: false});
|
||||||
|
document.getElementById('previewBtn').addEventListener('click', previewNote);
|
||||||
|
|
||||||
|
document.getElementById('searchBtn').addEventListener('click', ref.queryRef);
|
||||||
|
document.getElementById('closeSearch').addEventListener('click', closeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initHome() {
|
||||||
|
setBooks();
|
||||||
|
setEventListeners();
|
||||||
|
|
||||||
|
$('sidebar-link').on('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
$('#sidebar').toggleClass('inactive');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('sidebar').on('click', 'a', function(event) {
|
||||||
|
var $a = $(this), href = $a.attr('href'), target = $a.attr('target');
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
$('sidebar').addClass('inactive');
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
if (target == '_blank')
|
||||||
|
window.open(href);
|
||||||
|
else
|
||||||
|
window.location.href = href;
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#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', note.openShareNote);
|
||||||
|
$('#modal-backdrop').on('click', note.closeShareNote);
|
||||||
|
|
||||||
|
state.md = new markdownit({
|
||||||
|
html: true,
|
||||||
|
linkify: true,
|
||||||
|
breaks: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('#noteDate')) {
|
||||||
|
$('#noteDate').datepicker();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($('#query')) {
|
||||||
|
document.querySelector('#query').addEventListener('keyup', function (event) {
|
||||||
|
if (event.key == "Enter") {
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming 'to' and 'saveInterval' are declared globally elsewhere in your script
|
||||||
|
if (typeof state.to === 'undefined' || !state.to) {
|
||||||
|
state.to = setTimeout(note.saveNote, state.saveInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) => {
|
||||||
|
state.BOOKS = data;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the reference with the option to close the sidebar.
|
||||||
|
*
|
||||||
|
* @param {boolean} closeSidebar - Indicates whether to close the sidebar when opening the reference.
|
||||||
|
*/
|
||||||
|
export function openRef(e, closeSidebar = true) {
|
||||||
|
document.querySelector('#open-ref').classList.add('active');
|
||||||
|
document.querySelector('#mobile-open-ref').classList.add('active');
|
||||||
|
|
||||||
|
let refQuery = document.querySelector('#refQuery');
|
||||||
|
refQuery.style.display = 'block';
|
||||||
|
|
||||||
|
let ref = document.querySelector('#ref-text');
|
||||||
|
refQuery.style.left = ref.offsetLeft + 'px';
|
||||||
|
refQuery.style.top = ref.offsetTop + 'px';
|
||||||
|
if (closeSidebar) {
|
||||||
|
document.querySelector('.toggle').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the reference query and resets the reference search form.
|
||||||
|
*
|
||||||
|
* @return {void} This function does not return anything.
|
||||||
|
*/
|
||||||
|
export function closeRef() {
|
||||||
|
document.querySelector('#referenceSearch').value = '';
|
||||||
|
document.querySelector('#referenceSearch').style.display = '';
|
||||||
|
document.querySelector('#referenceType').value = '';
|
||||||
|
document.querySelector('#referenceBook').value = '';
|
||||||
|
document.querySelector('#referenceBook').style.display = 'none';
|
||||||
|
document.querySelector('#chapter-range').innerText = '';
|
||||||
|
document.querySelector('#verse-range').innerText = '';
|
||||||
|
|
||||||
|
document.querySelector('#refQuery').style.display = 'none';
|
||||||
|
document.querySelector('#open-ref').classList.remove('active');
|
||||||
|
document.querySelector('#mobile-open-ref').classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
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.refs) {
|
||||||
|
references = result.refs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = document.querySelector('#ref-list');
|
||||||
|
list.innerHTML = '';
|
||||||
|
var newList = null;
|
||||||
|
for (var x in references) {
|
||||||
|
var newList = document.createElement('li');
|
||||||
|
newList.className = 'tab';
|
||||||
|
var button = makeButton(x);
|
||||||
|
newList.appendChild(button);
|
||||||
|
list.appendChild(newList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runOpen) {
|
||||||
|
note.openNote(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
export function makeButton(title) {
|
||||||
|
var btn = document.createElement('button');
|
||||||
|
btn.innerText = title;
|
||||||
|
btn.class = 'button';
|
||||||
|
btn.style = 'line-height:normal;'
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
removeActiveRef();
|
||||||
|
document.querySelector('#ref-text').innerHTML = state.md.render(state.references[title]);
|
||||||
|
this.classList.add('active');
|
||||||
|
note.findRefLinks();
|
||||||
|
});
|
||||||
|
|
||||||
|
btn.addEventListener('dblclick', function () {
|
||||||
|
document.querySelector('#ref-text').innerHTML = '';
|
||||||
|
delete state.references[title];
|
||||||
|
var list = this.parentElement;
|
||||||
|
list.remove();
|
||||||
|
state.saved = false;
|
||||||
|
state.textDirty = true;
|
||||||
|
note.saveNote();
|
||||||
|
});
|
||||||
|
|
||||||
|
removeActiveRef();
|
||||||
|
btn.classList.add('active');
|
||||||
|
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the 'active' class from all elements with the class 'active'.
|
||||||
|
*
|
||||||
|
* @return {void} This function does not return a value.
|
||||||
|
*/
|
||||||
|
export function removeActiveRef() {
|
||||||
|
tabs = document.querySelectorAll('#ref-list .active');
|
||||||
|
for (var t in tabs) {
|
||||||
|
if (isFinite(parseInt(t))) {
|
||||||
|
tabs[t].classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random UUIDv4 string.
|
||||||
|
*
|
||||||
|
* @return {string} The generated UUIDv4 string.
|
||||||
|
*/
|
||||||
|
export function uuidv4() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
|
||||||
|
.replace(/[xy]/g, function (c) {
|
||||||
|
const r = Math.random() * 16 | 0,
|
||||||
|
v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
export function search() {
|
||||||
|
query = document.querySelector('#query').value;
|
||||||
|
fetch('/search', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'query': query
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(results => {
|
||||||
|
var oldNotes = document.querySelector('#old-notes');
|
||||||
|
oldNotes.innerHTML = '';
|
||||||
|
for (var n in results) {
|
||||||
|
var link = document.createElement('a');
|
||||||
|
link.href = '#';
|
||||||
|
link.setAttribute('onclick', "note.retrieveNote('" + results[n].id + "');note.openNote();");
|
||||||
|
link.innerHTML = results[n].title;
|
||||||
|
|
||||||
|
var p = document.createElement('p');
|
||||||
|
p.innerHTML = results[n].passage;
|
||||||
|
|
||||||
|
var article = document.createElement('article');
|
||||||
|
article.appendChild(link);
|
||||||
|
article.appendChild(p);
|
||||||
|
|
||||||
|
oldNotes.append(article);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
}
|
||||||
@@ -0,0 +1,412 @@
|
|||||||
|
import { state } from './state.js';
|
||||||
|
import { toggleFields } from './home.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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');
|
||||||
|
|
||||||
|
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('.toggle').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
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-left h2').classList.remove('dirty');
|
||||||
|
|
||||||
|
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').innerHTML = '';
|
||||||
|
document.querySelector('.toggle').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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: 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) {
|
||||||
|
state.saveFailureCount = SAVE_FAILURE_LIMIT;
|
||||||
|
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.saveFailureCount--;
|
||||||
|
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(to);
|
||||||
|
if (state.saveFailureCount > 0) {
|
||||||
|
state.to = setTimeout(saveNote, state.saveInterval);
|
||||||
|
} else {
|
||||||
|
state.saveFailureCount = SAVE_FAILURE_LIMIT;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 => {
|
||||||
|
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,
|
||||||
|
"<button onclick='closePopup()'>Close</button> " +
|
||||||
|
"<button onclick=\"queryRef('bible', '" + book + "', '" + cv + "')\">Open Ref</button><br/>" +
|
||||||
|
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,
|
||||||
|
"<button onclick='home.closePopup()'>Close</button> " +
|
||||||
|
"<button onclick=\"home.queryRef('bible', '" + book + "', '" + cv + "')\">Open Ref</button><br/>" +
|
||||||
|
result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the share note functionality.
|
||||||
|
*/
|
||||||
|
export 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.
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
+212
-5
@@ -1,3 +1,7 @@
|
|||||||
|
import { state } from './state.js';
|
||||||
|
import { closeRef, makeButton } from './home.js';
|
||||||
|
import { saveNote, findRefLinks } from './note.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the reference type from the server and populates the reference series dropdown.
|
* Retrieves the reference type from the server and populates the reference series dropdown.
|
||||||
*
|
*
|
||||||
@@ -7,7 +11,7 @@
|
|||||||
export function retrieveReferenceType(el) {
|
export function retrieveReferenceType(el) {
|
||||||
fetch('/reference/' + el.value, {
|
fetch('/reference/' + el.value, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
header: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -41,7 +45,7 @@ export function retrieveReference(el) {
|
|||||||
}
|
}
|
||||||
fetch('/get-reference', {
|
fetch('/get-reference', {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
header: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -66,7 +70,7 @@ export function saveReference() {
|
|||||||
let cont = document.querySelector('#reference');
|
let cont = document.querySelector('#reference');
|
||||||
fetch('/save-reference', {
|
fetch('/save-reference', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
header: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -76,10 +80,213 @@ export function saveReference() {
|
|||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(results => {
|
.then(results => {
|
||||||
//alert(results.msg);
|
|
||||||
|
|
||||||
document.querySelector('#reference').value = '';
|
document.querySelector('#reference').value = '';
|
||||||
document.querySelector('#referenceType').value = '';
|
document.querySelector('#referenceType').value = '';
|
||||||
document.querySelector('#referenceSeries').value = '';
|
document.querySelector('#referenceSeries').value = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the list of books based on the selected reference type.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
export function retrieveBooks() {
|
||||||
|
document.querySelector('#chapter-range').innerText = '';
|
||||||
|
document.querySelector('#verse-range').innerText = '';
|
||||||
|
document.querySelector('#referenceSearch').value = '';
|
||||||
|
document.querySelector('#referenceSearch').style.display = 'none';
|
||||||
|
const selectedType = document.querySelector('#referenceType').value;
|
||||||
|
if (!selectedType) { return; }
|
||||||
|
|
||||||
|
var bookList = document.querySelector('#referenceBook');
|
||||||
|
bookList.style.display = "block";
|
||||||
|
bookList.innerHTML = '';
|
||||||
|
if (selectedType == 'bible') {
|
||||||
|
document.querySelector('#referenceSearch').style.display = 'block';
|
||||||
|
var none = document.createElement("option");
|
||||||
|
none.value = '';
|
||||||
|
none.text = '-- Select --';
|
||||||
|
bookList.appendChild(none);
|
||||||
|
for (var x in state.BOOKS.bible) {
|
||||||
|
var newBook = document.createElement("option");
|
||||||
|
newBook.text = x;
|
||||||
|
bookList.appendChild(newBook);
|
||||||
|
}
|
||||||
|
} else if (selectedType == 'creed') {
|
||||||
|
var none = document.createElement('option');
|
||||||
|
none.value = '';
|
||||||
|
none.text = '-- Select --';
|
||||||
|
bookList.appendChild(none);
|
||||||
|
for (var x in state.BOOKS.creed) {
|
||||||
|
var newBook = document.createElement('option');
|
||||||
|
newBook.value = x;
|
||||||
|
newBook.text = state.BOOKS.creed[x];
|
||||||
|
bookList.appendChild(newBook);
|
||||||
|
}
|
||||||
|
} else if (selectedType == 'cd') {
|
||||||
|
var none = document.createElement("option");
|
||||||
|
none.value = '';
|
||||||
|
none.text = '-- Select --';
|
||||||
|
bookList.appendChild(none);
|
||||||
|
for (var x in state.BOOKS.cd) {
|
||||||
|
var newBook = document.createElement("option");
|
||||||
|
newBook.text = state.BOOKS.cd[x];
|
||||||
|
bookList.appendChild(newBook);
|
||||||
|
}
|
||||||
|
} else if (selectedType == 'hc') {
|
||||||
|
var none = document.createElement("option");
|
||||||
|
none.value = '';
|
||||||
|
none.text = '-- Select --';
|
||||||
|
bookList.appendChild(none);
|
||||||
|
for (var x in state.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 state.BOOKS[selectedType][x]) {
|
||||||
|
var question = document.createElement("option");
|
||||||
|
question.value = 'hc' + state.BOOKS[selectedType][x][y];
|
||||||
|
question.text = "HC" + state.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 {
|
||||||
|
var min = state.BOOKS[selectedType][0];
|
||||||
|
var max = state.BOOKS[selectedType][1];
|
||||||
|
var none = document.createElement("option");
|
||||||
|
none.value = '';
|
||||||
|
none.text = '-- Select --';
|
||||||
|
bookList.appendChild(none);
|
||||||
|
for (var x = min; x <= max; x++) {
|
||||||
|
var newBook = document.createElement("option");
|
||||||
|
newBook.value = x;
|
||||||
|
newBook.text = x;
|
||||||
|
bookList.appendChild(newBook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the books based on the selected reference type and updates the chapter range.
|
||||||
|
*
|
||||||
|
* @return {void} This function does not return anything.
|
||||||
|
*/
|
||||||
|
export function filterBooks() {
|
||||||
|
document.querySelector('#chapter-range').innerText = '';
|
||||||
|
document.querySelector('#verse-range').innerText = '';
|
||||||
|
if (document.querySelector('#referenceType').value != 'bible') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bookList = document.querySelector('#referenceBook');
|
||||||
|
var book = state.BOOKS.bible[bookList.value];
|
||||||
|
var max = Object.keys(book).length;
|
||||||
|
|
||||||
|
var chapterRange = document.querySelector('#chapter-range');
|
||||||
|
chapterRange.innerText = 'Chapters: ' + max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the verse based on the selected book and chapter.
|
||||||
|
*
|
||||||
|
* @return {void} This function does not return anything.
|
||||||
|
*/
|
||||||
|
export function filterVerse() {
|
||||||
|
if (document.querySelector('#referenceType').value != 'bible') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bookList = document.querySelector('#referenceBook').value;
|
||||||
|
var search = document.querySelector('#referenceSearch').value;
|
||||||
|
var chapter = search.split(':')[0];
|
||||||
|
var verseRange = document.querySelector('#verse-range');
|
||||||
|
|
||||||
|
if (!state.BOOKS.bible[bookList] || !state.BOOKS.bible[bookList][chapter]) {
|
||||||
|
verseRange.innerText = 'Unknown Chapter';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var verse = state.BOOKS.bible[bookList][chapter];
|
||||||
|
verseRange.innerText = 'Verse: ' + verse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
export function queryRef(e, type = null, book = null, input = null) {
|
||||||
|
if (!input) {
|
||||||
|
var input = document.querySelector('#refQuery #referenceSearch').value;
|
||||||
|
}
|
||||||
|
if (!type) {
|
||||||
|
var type = document.querySelector('#referenceType').value;
|
||||||
|
}
|
||||||
|
if (!book) {
|
||||||
|
var book = document.querySelector('#referenceBook').value;
|
||||||
|
}
|
||||||
|
fetch('/retrieve-reference', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'type': type,
|
||||||
|
'book': book,
|
||||||
|
'reference': input,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(results => {
|
||||||
|
const list = document.querySelector('#ref-list');
|
||||||
|
var newList = document.createElement('li');
|
||||||
|
newList.className = 'tab';
|
||||||
|
let button = makeButton(results.title);
|
||||||
|
newList.appendChild(button);
|
||||||
|
list.appendChild(newList);
|
||||||
|
|
||||||
|
const ref = document.querySelector('#ref-text');
|
||||||
|
ref.innerHTML = state.md.render(results.text);
|
||||||
|
|
||||||
|
state.references[results.title] = results.text;
|
||||||
|
|
||||||
|
closeRef();
|
||||||
|
|
||||||
|
state.saved = false;
|
||||||
|
state.textDirty = true;
|
||||||
|
saveNote();
|
||||||
|
findRefLinks();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,13 @@ registerBtn.addEventListener("click", handleSubmit);
|
|||||||
|
|
||||||
// Function to handle form submission
|
// Function to handle form submission
|
||||||
function handleSubmit(event) {
|
function handleSubmit(event) {
|
||||||
// Prevent default form submission behavior
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// Validate input
|
// Validate input
|
||||||
const name = nameInput.value;
|
const name = nameInput.value;
|
||||||
const email = emailInput.value;
|
const email = emailInput.value;
|
||||||
const password = passwordInput.value;
|
const password = passwordInput.value;
|
||||||
|
|
||||||
if (name === "" || email === "" || password === "") {
|
if (name === "" || email === "" || password === "") {
|
||||||
|
event.preventDefault();
|
||||||
alert("Please fill in all fields.");
|
alert("Please fill in all fields.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { state } from './state.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A description of the entire function.
|
||||||
|
*/
|
||||||
|
export function newSeries() {
|
||||||
|
if (document.querySelector('#series').value == 'new') {
|
||||||
|
document.querySelector('#newSeries').style.display = 'inline-block';
|
||||||
|
document.querySelector('#series').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
state.saved = false;
|
||||||
|
state.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.
|
||||||
|
*/
|
||||||
|
export function saveSeries(event) {
|
||||||
|
if (event.keyCode == 27) {
|
||||||
|
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',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'seriesName': document.querySelector('#newSeries').value
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(results => {
|
||||||
|
var newSeries = document.createElement('option');
|
||||||
|
newSeries.text = document.querySelector('#newSeries').value;
|
||||||
|
newSeries.value = results.id;
|
||||||
|
document.querySelector('#series').add(newSeries);
|
||||||
|
|
||||||
|
alert(results.msg);
|
||||||
|
document.querySelector('#newSeries').style.display = 'none';
|
||||||
|
document.querySelector('#series').style.display = 'inline-block';
|
||||||
|
|
||||||
|
document.querySelector('#newSeries').value = '';
|
||||||
|
document.querySelector('#series').value = results.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { state } from './state.js';
|
||||||
|
/**
|
||||||
|
* Toggles the display of the new speaker input field and hides the speaker select field.
|
||||||
|
*
|
||||||
|
* @return {void} This function does not return anything.
|
||||||
|
*/
|
||||||
|
export function newSpeaker() {
|
||||||
|
if (document.querySelector('#speaker').value == 'new') {
|
||||||
|
document.querySelector('#newSpeaker').style.display = 'inline-block';
|
||||||
|
document.querySelector('#speaker').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
state.saved = false;
|
||||||
|
state.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.
|
||||||
|
*/
|
||||||
|
export function saveSpeaker(event) {
|
||||||
|
if (event.keyCode == 27) {
|
||||||
|
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',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'speakerName': document.querySelector('#newSpeaker').value
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(results => {
|
||||||
|
var newSpeaker = document.createElement('option');
|
||||||
|
newSpeaker.text = document.querySelector('#newSpeaker').value;
|
||||||
|
newSpeaker.value = results.id;
|
||||||
|
document.querySelector('#speaker').add(newSpeaker);
|
||||||
|
|
||||||
|
alert(results.msg);
|
||||||
|
document.querySelector('#newSpeaker').style.display = 'none';
|
||||||
|
document.querySelector('#speaker').style.display = 'inline-block';
|
||||||
|
|
||||||
|
document.querySelector('#newSpeaker').value = '';
|
||||||
|
document.querySelector('#speaker').value = results.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export let state = {
|
||||||
|
textDirty: false,
|
||||||
|
saved: true,
|
||||||
|
references: {},
|
||||||
|
saveTimeout: null,
|
||||||
|
saveInterval: 5000,
|
||||||
|
saveFailureCount: 0,
|
||||||
|
to: null,
|
||||||
|
BOOKS: {},
|
||||||
|
md: null,
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { state } from "./state.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a template from the server and sets it as the value of a specified destination element.
|
* Retrieves a template from the server and sets it as the value of a specified destination element.
|
||||||
*
|
*
|
||||||
@@ -24,6 +26,7 @@ export function retrieveTemplate(orig, dest) {
|
|||||||
.then(results => {
|
.then(results => {
|
||||||
const div = document.querySelector('#' + dest);
|
const div = document.querySelector('#' + dest);
|
||||||
div.value = results;
|
div.value = results;
|
||||||
|
state.textDirty = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'fontawesome-all.min.css';
|
@import '../css/fontawesome-all.min.css';
|
||||||
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,400italic,600italic|Roboto+Slab:400,700");
|
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,400italic,600italic|Roboto+Slab:400,700");
|
||||||
/*
|
/*
|
||||||
Editorial by HTML5 UP
|
Editorial by HTML5 UP
|
||||||
|
|||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Start the cron service in the background
|
||||||
|
service cron start
|
||||||
|
|
||||||
|
chown -R www-data:www-data /var/www/html/var
|
||||||
|
|
||||||
|
# Execute the default Docker CMD (which starts Apache in the foreground)
|
||||||
|
exec "$@"
|
||||||
Generated
+2160
-1527
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
|||||||
|
# Enable stateless CSRF protection for forms and logins/logouts
|
||||||
|
framework:
|
||||||
|
form:
|
||||||
|
csrf_protection:
|
||||||
|
token_id: submit
|
||||||
|
|
||||||
|
csrf_protection:
|
||||||
|
stateless_token_ids:
|
||||||
|
- submit
|
||||||
|
- authenticate
|
||||||
|
- logout
|
||||||
@@ -1,20 +1,40 @@
|
|||||||
doctrine:
|
doctrine:
|
||||||
dbal:
|
dbal:
|
||||||
|
connections:
|
||||||
|
default:
|
||||||
url: '%env(resolve:DATABASE_URL)%'
|
url: '%env(resolve:DATABASE_URL)%'
|
||||||
|
profiling_collect_backtrace: '%kernel.debug%'
|
||||||
|
use_savepoints: true
|
||||||
|
transfer:
|
||||||
|
url: '%env(default::XFER_DATABASE_URL)%'
|
||||||
|
profiling_collect_backtrace: '%kernel.debug%'
|
||||||
|
use_savepoints: true
|
||||||
|
|
||||||
# IMPORTANT: You MUST configure your server version,
|
# IMPORTANT: You MUST configure your server version,
|
||||||
# either here or in the DATABASE_URL env var (see .env file)
|
# either here or in the DATABASE_URL env var (see .env file)
|
||||||
#server_version: '16'
|
#server_version: '16'
|
||||||
|
|
||||||
profiling_collect_backtrace: '%kernel.debug%'
|
|
||||||
use_savepoints: true
|
|
||||||
orm:
|
orm:
|
||||||
auto_generate_proxy_classes: true
|
auto_generate_proxy_classes: true
|
||||||
enable_lazy_ghost_objects: true
|
enable_lazy_ghost_objects: true
|
||||||
report_fields_where_declared: true
|
# report_fields_where_declared: true
|
||||||
validate_xml_mapping: true
|
# validate_xml_mapping: true
|
||||||
|
# auto_mapping: true
|
||||||
|
default_entity_manager: default
|
||||||
|
entity_managers:
|
||||||
|
default:
|
||||||
|
connection: default
|
||||||
|
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||||
|
mappings:
|
||||||
|
App:
|
||||||
|
type: attribute
|
||||||
|
is_bundle: false
|
||||||
|
dir: '%kernel.project_dir%/src/Entity'
|
||||||
|
prefix: 'App\Entity'
|
||||||
|
alias: App
|
||||||
|
transfer:
|
||||||
|
connection: transfer
|
||||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||||
auto_mapping: true
|
|
||||||
mappings:
|
mappings:
|
||||||
App:
|
App:
|
||||||
type: attribute
|
type: attribute
|
||||||
@@ -23,7 +43,7 @@ doctrine:
|
|||||||
prefix: 'App\Entity'
|
prefix: 'App\Entity'
|
||||||
alias: App
|
alias: App
|
||||||
controller_resolver:
|
controller_resolver:
|
||||||
auto_mapping: true
|
auto_mapping: false
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
doctrine:
|
doctrine:
|
||||||
@@ -50,3 +70,6 @@ when@prod:
|
|||||||
adapter: cache.app
|
adapter: cache.app
|
||||||
doctrine.system_cache_pool:
|
doctrine.system_cache_pool:
|
||||||
adapter: cache.system
|
adapter: cache.system
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
env(default_url): ''
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
twig:
|
twig:
|
||||||
file_name_pattern: '*.twig'
|
file_name_pattern: '*.twig'
|
||||||
|
|
||||||
|
when@dev:
|
||||||
|
twig:
|
||||||
|
debug: true
|
||||||
|
cache: false
|
||||||
|
|
||||||
when@test:
|
when@test:
|
||||||
twig:
|
twig:
|
||||||
strict_variables: true
|
strict_variables: true
|
||||||
|
|||||||
@@ -22,3 +22,6 @@ services:
|
|||||||
|
|
||||||
# add more service definitions when explicit configuration is needed
|
# add more service definitions when explicit configuration is needed
|
||||||
# please note that last definitions always *replace* previous ones
|
# please note that last definitions always *replace* previous ones
|
||||||
|
App\Service\DatabaseTransferService:
|
||||||
|
arguments:
|
||||||
|
$projectDir: '%kernel.project_dir%'
|
||||||
@@ -1,22 +1,34 @@
|
|||||||
services:
|
services:
|
||||||
sermon-notes:
|
sermon-notes:
|
||||||
image: gitea.rkprather.com/ryan/sermon-notes:latest
|
image: gitea.rkprather.com/ryan/sermon-notes:1.3
|
||||||
container_name: sermon-notes
|
container_name: sermon-notes
|
||||||
hostname: sermon-notes
|
hostname: sermon-notes
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- ${HTTP_PORT}:80
|
- ${HTTP_PORT}:80
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/.env:/var/www/html/.env
|
- ${PWD}/.env:/var/www/html/.env
|
||||||
|
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: mariadb
|
image: mariadb:12.3
|
||||||
container_name: db
|
container_name: db
|
||||||
hostname: db
|
hostname: db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/db_data:/var/lib/mysql
|
- ${PWD}/db_data:/var/lib/mysql
|
||||||
|
|||||||
@@ -1,22 +1,34 @@
|
|||||||
services:
|
services:
|
||||||
sermon-notes:
|
sermon-notes:
|
||||||
image: gitea.rkprather.com/ryan/sermon-notes:latest
|
image: gitea.rkprather.com/ryan/sermon-notes:1.3
|
||||||
container_name: sermon-notes
|
container_name: sermon-notes
|
||||||
hostname: sermon-notes
|
hostname: sermon-notes
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- ${HTTP_PORT}:80
|
- ${HTTP_PORT}:80
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/.env:/var/www/html/.env
|
- ${PWD}/.env:/var/www/html/.env
|
||||||
|
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: mysql
|
image: mysql:9.6
|
||||||
container_name: db
|
container_name: db
|
||||||
hostname: db
|
hostname: db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/db-data:/var/lib/mysql
|
- ${PWD}/db-data:/var/lib/mysql
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
services:
|
services:
|
||||||
sermon-notes:
|
sermon-notes:
|
||||||
container_name: sermon-notes
|
container_name: sermon-notes
|
||||||
image: gitea.rkprather.com/ryan/sermon-notes:latest
|
image: gitea.rkprather.com/ryan/sermon-notes:1.3
|
||||||
hostname: sermon-notes
|
hostname: sermon-notes
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- ${HTTP_PORT}:80
|
- ${HTTP_PORT}:80
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/.env:/var/www/html/.env
|
- ${PWD}/.env:/var/www/html/.env
|
||||||
|
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
|
||||||
@@ -17,7 +27,9 @@ services:
|
|||||||
container_name: db
|
container_name: db
|
||||||
hostname: db
|
hostname: db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/db-data:/var/lib/postgresql/data
|
- ${PWD}/db-data:/var/lib/postgresql/data
|
||||||
|
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
services:
|
services:
|
||||||
sermon-notes:
|
sermon-notes:
|
||||||
image: gitea.rkprather.com/ryan/sermon-notes:latest
|
image: gitea.rkprather.com/ryan/sermon-notes:1.3
|
||||||
container_name: sermon-notes
|
container_name: sermon-notes
|
||||||
hostname: sermon-notes
|
hostname: sermon-notes
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- ${HTTP_PORT}:80
|
- ${HTTP_PORT}:80
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/.env:/var/www/html/.env
|
- ${PWD}/.env:/var/www/html/.env
|
||||||
|
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
services:
|
services:
|
||||||
sermon-notes:
|
sermon-notes:
|
||||||
image: gitea.rkprather.com/ryan/sermon-notes:latest
|
image: gitea.rkprather.com/ryan/sermon-notes:1.3
|
||||||
container_name: sermon-notes
|
container_name: sermon-notes
|
||||||
hostname: sermon-notes
|
hostname: sermon-notes
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- ${HTTP_PORT}:80
|
- ${HTTP_PORT}:80
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/data:/data
|
- ${PWD}/data:/data
|
||||||
- ${PWD}/.env:/var/www/html/.env
|
- ${PWD}/.env:/var/www/html/.env
|
||||||
|
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
@@ -34,4 +34,25 @@ return [
|
|||||||
'datatables' => [
|
'datatables' => [
|
||||||
'version' => '1.10.18',
|
'version' => '1.10.18',
|
||||||
],
|
],
|
||||||
|
'ref' => [
|
||||||
|
'path' => './assets/js/reference.js',
|
||||||
|
],
|
||||||
|
'home' => [
|
||||||
|
'path' => './assets/js/home.js',
|
||||||
|
],
|
||||||
|
'note' => [
|
||||||
|
'path' => './assets/js/note.js',
|
||||||
|
],
|
||||||
|
'state' => [
|
||||||
|
'path' => './assets/js/state.js',
|
||||||
|
],
|
||||||
|
'series' => [
|
||||||
|
'path' => './assets/js/series.js',
|
||||||
|
],
|
||||||
|
'speaker' => [
|
||||||
|
'path' => './assets/js/speaker.js',
|
||||||
|
],
|
||||||
|
'template' => [
|
||||||
|
'path' => './assets/js/template.js',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
+28
-28
@@ -1,28 +1,28 @@
|
|||||||
#!/usr/local/bin/php
|
#!/usr/bin/env php
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
print "Updating packages and compiling assets".PHP_EOL;
|
print "Updating packages and compiling assets".PHP_EOL;
|
||||||
`COMPOSE_ALLOW_SUPERUSER=1 composer update`;
|
shell_exec("COMPOSE_ALLOW_SUPERUSER=1 composer update");
|
||||||
`symfony console asset-map:compile`;
|
//`symfony console asset-map:compile");
|
||||||
|
|
||||||
print "Creating database schema".PHP_EOL;
|
print "Creating database schema".PHP_EOL;
|
||||||
`symfony console doctrine:database:create`;
|
shell_exec("symfony console doctrine:database:create --if-not-exists");
|
||||||
|
|
||||||
print "Updating migrations and setting permissions for data folder".PHP_EOL;
|
print "Updating migrations and setting permissions for data folder".PHP_EOL;
|
||||||
`symfony console doctrine:migrations:migrate --no-interaction`;
|
shell_exec("symfony console doctrine:schema:create");
|
||||||
|
|
||||||
`chown -R www-data:www-data /data`;
|
shell_exec("chown -R www-data:www-data /data /var/www/html/var/cache");
|
||||||
|
|
||||||
// import reference material
|
// import reference material
|
||||||
|
|
||||||
print "Importing Bible and Eccumenical Creeds".PHP_EOL;
|
print "Importing Bible and Eccumenical Creeds".PHP_EOL;
|
||||||
`symfony console app:ingest-bible /var/www/html/reference/esv-bible`;
|
shell_exec("symfony console app:ingest-bible /var/www/html/references/esv-bible");
|
||||||
`symfony console app:import-ref /var/www/html/references/creeds/Apostles 'Apostles Creed' creed apc`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/creeds/Apostles 'Apostles Creed' creed apc");
|
||||||
`symfony console app:import-ref /var/www/html/references/creeds/Athanasian 'Athanasian Creed' creed ath`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/creeds/Athanasian 'Athanasian Creed' creed ath");
|
||||||
`symfony console app:import-ref /var/www/html/references/creeds/Chalcedon 'Definition of Chalcedon' creed dc`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/creeds/Chalcedon 'Definition of Chalcedon' creed dc");
|
||||||
`symfony console app:import-ref /var/www/html/references/creeds/French 'French Confession' creed fc`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/creeds/French 'French Confession' creed fc");
|
||||||
`symfony console app:import-ref /var/www/html/references/creeds/Nicene 'Nicene Creed' creed nc`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/creeds/Nicene 'Nicene Creed' creed nc");
|
||||||
|
|
||||||
$dutchStandards = (
|
$dutchStandards = (
|
||||||
strtolower(
|
strtolower(
|
||||||
@@ -32,9 +32,9 @@ $dutchStandards = (
|
|||||||
|
|
||||||
if ($dutchStandards) {
|
if ($dutchStandards) {
|
||||||
print "Importing Dutch Standards".PHP_EOL;
|
print "Importing Dutch Standards".PHP_EOL;
|
||||||
`symfony console app:import-ref /var/www/html/references/bc Belgic belgic BC{\$ndx}`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/bc Belgic belgic BC{\$ndx}");
|
||||||
`symfony console app:import-heidelberg`;
|
shell_exec("symfony console app:import-heidelberg");
|
||||||
`symfony console app:import-canons-of-dort`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/cd Canons cd CD");
|
||||||
}
|
}
|
||||||
|
|
||||||
$westminsterStandards = (
|
$westminsterStandards = (
|
||||||
@@ -45,21 +45,21 @@ $westminsterStandards = (
|
|||||||
|
|
||||||
if ($westminsterStandards) {
|
if ($westminsterStandards) {
|
||||||
print "Importing Westminster Standards".PHP_EOL;
|
print "Importing Westminster Standards".PHP_EOL;
|
||||||
`symfony console app:import-ref /var/www/html/references/wcf 'Westminster Confession' wcf WCF{\$ndx}`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/wcf 'Westminster Confession' wcf WCF{\$ndx}");
|
||||||
`symfony console app:import-ref /var/www/html/references/wsc 'Westminster Shorter' wsc WSC{\$ndx}`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/wsc 'Westminster Shorter' wsc WSC{\$ndx}");
|
||||||
`symfony console app:import-wlc /var/www/html/references/wlc 'Westminster Larger' wlc WLC{\$ndx}`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/wlc 'Westminster Larger' wlc WLC{\$ndx}");
|
||||||
}
|
}
|
||||||
|
|
||||||
$helviticConfessions = (
|
$helveticConfessions = (
|
||||||
strtolower(
|
strtolower(
|
||||||
readline("Do you want to import the Helvetic Confessions (1st & 2nd) (y/n)? ")
|
readline("Do you want to import the Helvetic Confessions (1st & 2nd) (y/n)? ")
|
||||||
) == 'y'
|
) == 'y'
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($helviticConfessions) {
|
if ($helveticConfessions) {
|
||||||
print "Importing Helvitic standards".PHP_EOL;
|
print "Importing Helvitic standards".PHP_EOL;
|
||||||
`symfony console app:import-ref /var/www/html/references/fhc 'First Helvetic Confession' 1hc 1HC{\$ndx}`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/fhc 'First Helvetic Confession' 1hc 1HC{\$ndx}");
|
||||||
`symfony console app:import-ref /var/www/html/references/shc 'Second Helvetic Confession' 2hc 2HC{\$ndx}`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/shc 'Second Helvetic Confession' 2hc 2HC{\$ndx}");
|
||||||
}
|
}
|
||||||
|
|
||||||
$miscStandards = (
|
$miscStandards = (
|
||||||
@@ -70,12 +70,12 @@ $miscStandards = (
|
|||||||
|
|
||||||
if ($miscStandards) {
|
if ($miscStandards) {
|
||||||
print "Importing misc standards".PHP_EOL;
|
print "Importing misc standards".PHP_EOL;
|
||||||
`symfony console app:import-ref /var/www/html/references/39a 'Thirty-Nine Articles' 39a 39A{\$ndx}`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/39a 'Thirty-Nine Articles' 39a 39A{\$ndx}");
|
||||||
`symfony console app:import-ref /var/www/html/references/ac 'Augsberg Confession' agc AGC{\$ndx}`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/ac 'Augsberg Confession' agc AGC{\$ndx}");
|
||||||
`symfony console app:import-ref /var/www/html/references/lbc 'London Baptist Confession' lbc LBC{\$ndx}`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/lbc 'London Baptist Confession' lbc LBC{\$ndx}");
|
||||||
`symfony console app:import-ref /var/www/html/references/lsc 'Luther\'s Small Catechism' lsc LSC`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/lsc 'Luther\'s Small Catechism' lsc LSC");
|
||||||
`symfony console app:import-ref /var/www/html/references/llc 'Luther\'s Large Catechism' llc LLC`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/llc 'Luther\'s Large Catechism' llc LLC");
|
||||||
`symfony console app:import-ref /var/www/html/references/sd 'Savoy Declaration' sd SD{\$ndx}`;
|
shell_exec("symfony console app:import-ref /var/www/html/references/sd 'Savoy Declaration' sd SD{\$ndx}");
|
||||||
}
|
}
|
||||||
|
|
||||||
print "Sermon Notes Ready".PHP_EOL.PHP_EOL;
|
print "Sermon Notes Ready".PHP_EOL.PHP_EOL;
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240505234804 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE TABLE user (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, email VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
|
|
||||||
, password VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
|
||||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON user (email)');
|
|
||||||
$this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
|
||||||
, available_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
|
||||||
, delivered_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable)
|
|
||||||
)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP TABLE user');
|
|
||||||
$this->addSql('DROP TABLE messenger_messages');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240513011501 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE TABLE bible (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, book VARCHAR(255) NOT NULL, chapter INTEGER NOT NULL, verse INTEGER NOT NULL, content CLOB DEFAULT NULL, book_index INTEGER NOT NULL, label VARCHAR(20) DEFAULT NULL, PRIMARY KEY(id))');
|
|
||||||
$this->addSql('CREATE TABLE note (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, speaker_id BLOB DEFAULT NULL --(DC2Type:uuid)
|
|
||||||
, series_id BLOB DEFAULT NULL --(DC2Type:uuid)
|
|
||||||
, user_id BLOB DEFAULT NULL --(DC2Type:uuid)
|
|
||||||
, title VARCHAR(255) NOT NULL, date DATE NOT NULL, passage VARCHAR(255) NOT NULL, refs CLOB DEFAULT NULL --(DC2Type:json)
|
|
||||||
, text CLOB DEFAULT NULL, PRIMARY KEY(id), CONSTRAINT FK_CFBDFA14D04A0F27 FOREIGN KEY (speaker_id) REFERENCES speaker (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_CFBDFA145278319C FOREIGN KEY (series_id) REFERENCES series (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_CFBDFA14A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_CFBDFA14D04A0F27 ON note (speaker_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_CFBDFA145278319C ON note (series_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_CFBDFA14A76ED395 ON note (user_id)');
|
|
||||||
$this->addSql('CREATE TABLE reference (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, type VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, label VARCHAR(255) NOT NULL, ndx INTEGER NOT NULL, content CLOB DEFAULT NULL, PRIMARY KEY(id))');
|
|
||||||
$this->addSql('CREATE TABLE series (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, user_id BLOB DEFAULT NULL --(DC2Type:uuid)
|
|
||||||
, template_id BLOB DEFAULT NULL --(DC2Type:uuid)
|
|
||||||
, name VARCHAR(255) NOT NULL, PRIMARY KEY(id), CONSTRAINT FK_3A10012DA76ED395 FOREIGN KEY (user_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_3A10012D5DA0FB8 FOREIGN KEY (template_id) REFERENCES template (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_3A10012DA76ED395 ON series (user_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_3A10012D5DA0FB8 ON series (template_id)');
|
|
||||||
$this->addSql('CREATE TABLE speaker (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, user_id BLOB DEFAULT NULL --(DC2Type:uuid)
|
|
||||||
, name VARCHAR(255) NOT NULL, PRIMARY KEY(id), CONSTRAINT FK_7B85DB61A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_7B85DB61A76ED395 ON speaker (user_id)');
|
|
||||||
$this->addSql('CREATE TABLE template (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, user_id BLOB DEFAULT NULL --(DC2Type:uuid)
|
|
||||||
, name VARCHAR(255) NOT NULL, content CLOB NOT NULL, PRIMARY KEY(id), CONSTRAINT FK_97601F83A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_97601F83A76ED395 ON template (user_id)');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP TABLE bible');
|
|
||||||
$this->addSql('DROP TABLE note');
|
|
||||||
$this->addSql('DROP TABLE reference');
|
|
||||||
$this->addSql('DROP TABLE series');
|
|
||||||
$this->addSql('DROP TABLE speaker');
|
|
||||||
$this->addSql('DROP TABLE template');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240513161129 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE TEMPORARY TABLE __temp__reference AS SELECT id, type, name, label, ndx, content FROM reference');
|
|
||||||
$this->addSql('DROP TABLE reference');
|
|
||||||
$this->addSql('CREATE TABLE reference (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, type VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, label VARCHAR(255) NOT NULL, ndx INTEGER DEFAULT NULL, content CLOB DEFAULT NULL, PRIMARY KEY(id))');
|
|
||||||
$this->addSql('INSERT INTO reference (id, type, name, label, ndx, content) SELECT id, type, name, label, ndx, content FROM __temp__reference');
|
|
||||||
$this->addSql('DROP TABLE __temp__reference');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE TEMPORARY TABLE __temp__reference AS SELECT id, type, name, label, ndx, content FROM reference');
|
|
||||||
$this->addSql('DROP TABLE reference');
|
|
||||||
$this->addSql('CREATE TABLE reference (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, type VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, label VARCHAR(255) NOT NULL, ndx INTEGER NOT NULL, content CLOB DEFAULT NULL, PRIMARY KEY(id))');
|
|
||||||
$this->addSql('INSERT INTO reference (id, type, name, label, ndx, content) SELECT id, type, name, label, ndx, content FROM __temp__reference');
|
|
||||||
$this->addSql('DROP TABLE __temp__reference');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240527010736 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE note ADD COLUMN recording VARCHAR(255) DEFAULT NULL');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE TEMPORARY TABLE __temp__note AS SELECT id, speaker_id, series_id, user_id, title, date, passage, refs, text FROM note');
|
|
||||||
$this->addSql('DROP TABLE note');
|
|
||||||
$this->addSql('CREATE TABLE note (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, speaker_id BLOB DEFAULT NULL --(DC2Type:uuid)
|
|
||||||
, series_id BLOB DEFAULT NULL --(DC2Type:uuid)
|
|
||||||
, user_id BLOB DEFAULT NULL --(DC2Type:uuid)
|
|
||||||
, title VARCHAR(255) NOT NULL, date DATE NOT NULL, passage VARCHAR(255) NOT NULL, refs CLOB DEFAULT NULL --(DC2Type:json)
|
|
||||||
, text CLOB DEFAULT NULL, PRIMARY KEY(id), CONSTRAINT FK_CFBDFA14D04A0F27 FOREIGN KEY (speaker_id) REFERENCES speaker (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_CFBDFA145278319C FOREIGN KEY (series_id) REFERENCES series (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_CFBDFA14A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
|
||||||
$this->addSql('INSERT INTO note (id, speaker_id, series_id, user_id, title, date, passage, refs, text) SELECT id, speaker_id, series_id, user_id, title, date, passage, refs, text FROM __temp__note');
|
|
||||||
$this->addSql('DROP TABLE __temp__note');
|
|
||||||
$this->addSql('CREATE INDEX IDX_CFBDFA14D04A0F27 ON note (speaker_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_CFBDFA145278319C ON note (series_id)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_CFBDFA14A76ED395 ON note (user_id)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240622233923 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('ALTER TABLE user ADD COLUMN meta_data CLOB DEFAULT NULL');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, email, roles, password, name FROM user');
|
|
||||||
$this->addSql('DROP TABLE user');
|
|
||||||
$this->addSql('CREATE TABLE user (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, email VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
|
|
||||||
, password VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
|
||||||
$this->addSql('INSERT INTO user (id, email, roles, password, name) SELECT id, email, roles, password, name FROM __temp__user');
|
|
||||||
$this->addSql('DROP TABLE __temp__user');
|
|
||||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON user (email)');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20240717022017 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('CREATE TABLE shared_note (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, note_id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, owner_id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, shared_user_id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, PRIMARY KEY(id), CONSTRAINT FK_754B918C26ED0855 FOREIGN KEY (note_id) REFERENCES note (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_754B918C26ED0855 ON shared_note (note_id)');
|
|
||||||
$this->addSql('CREATE TABLE shared_series (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, series_id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, owner_id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, shared_user_id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, PRIMARY KEY(id), CONSTRAINT FK_59E803195278319C FOREIGN KEY (series_id) REFERENCES series (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_59E803195278319C ON shared_series (series_id)');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP TABLE shared_note');
|
|
||||||
$this->addSql('DROP TABLE shared_series');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
Options -MultiViews
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||||
|
</IfModule>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "Sermon Notes",
|
||||||
|
"short_name": "Sermon Notes",
|
||||||
|
"description": "A personal note-taking app for sermons with reference material",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"orientation": "landscape",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/images/Notes-icon-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/images/Notes-icon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "/images/Notes-icon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/images/Notes-icon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"form_factor": "wide"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// public/sw.js
|
||||||
|
const CACHE_NAME = 'app-cache-v1';
|
||||||
|
const ASSETS_TO_CACHE = [
|
||||||
|
'/',
|
||||||
|
'/manifest.json',
|
||||||
|
// Add paths to your main CSS and JS files here
|
||||||
|
// e.g., '/build/app.css', '/build/app.js'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Install Event: Cache core static assets
|
||||||
|
self.addEventListener('install', event => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then(cache => {
|
||||||
|
return cache.addAll(ASSETS_TO_CACHE);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
self.skipWaiting();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Activate Event: Clean up old caches if you change the CACHE_NAME version
|
||||||
|
self.addEventListener('activate', event => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then(cacheNames => {
|
||||||
|
return Promise.all(
|
||||||
|
cacheNames.map(cache => {
|
||||||
|
if (cache !== CACHE_NAME) {
|
||||||
|
return caches.delete(cache);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
self.clients.claim();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch Event: Network First, Fallback to Cache
|
||||||
|
self.addEventListener('fetch', event => {
|
||||||
|
// Only cache GET requests
|
||||||
|
if (event.request.method !== 'GET') return;
|
||||||
|
|
||||||
|
event.respondWith(
|
||||||
|
fetch(event.request)
|
||||||
|
.then(networkResponse => {
|
||||||
|
// If the network request succeeds, clone it and put it in the cache
|
||||||
|
return caches.open(CACHE_NAME).then(cache => {
|
||||||
|
cache.put(event.request, networkResponse.clone());
|
||||||
|
return networkResponse;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// If the network fails (offline), try to serve from cache
|
||||||
|
return caches.match(event.request);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/local/bin/php
|
#!/usr/bin/env php
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ if (!file_exists('/var/www/html/.env')) {
|
|||||||
|
|
||||||
$cmd = getopt("", ["sqlite", "mysql", "mariadb", "pgsql", "shared"]);
|
$cmd = getopt("", ["sqlite", "mysql", "mariadb", "pgsql", "shared"]);
|
||||||
|
|
||||||
$key = `openssl rand -base64 32 | tr -d '=' | tr -d '+' | tr -d '/' | tr -d ' '`;
|
$key = shell_exec("openssl rand -base64 32 | tr -d '=' | tr -d '+' | tr -d '/' | tr -d ' '");
|
||||||
$key = substr($key, 0, 32);
|
$key = substr($key, 0, 32);
|
||||||
$database_url = null;
|
$database_url = null;
|
||||||
$getCreds = true;
|
$getCreds = true;
|
||||||
@@ -53,7 +53,7 @@ if ($getCreds) {
|
|||||||
$db_port = (isset($cmd['pgsql']) ? 5432 : 3306);
|
$db_port = (isset($cmd['pgsql']) ? 5432 : 3306);
|
||||||
$db_name = 'sermon_notes';
|
$db_name = 'sermon_notes';
|
||||||
$db_user = 'root';
|
$db_user = 'root';
|
||||||
$pwd = `openssl rand -base64 32 | tr -d '=' | tr -d '+' | tr -d '/' | tr -d ' '`;
|
$pwd = shell_exec("openssl rand -base64 32 | tr -d '=' | tr -d '+' | tr -d '/' | tr -d ' '");
|
||||||
$db_password = substr($pwd, 0, 32);
|
$db_password = substr($pwd, 0, 32);
|
||||||
|
|
||||||
if (isset($cmd['pgsql'])) {
|
if (isset($cmd['pgsql'])) {
|
||||||
@@ -85,7 +85,7 @@ $output = <<<EOF
|
|||||||
APP_ENV=prod
|
APP_ENV=prod
|
||||||
APP_DEBUG=0
|
APP_DEBUG=0
|
||||||
APP_SECRET=$key
|
APP_SECRET=$key
|
||||||
MESSAGENER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||||
HTTP_PORT=$http_port
|
HTTP_PORT=$http_port
|
||||||
$creds$database_url
|
$creds$database_url
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,282 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Entity\Note;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:get-audio',
|
||||||
|
description: 'Finds Notes with missing recordings and matches them to RSS feed by Date and Title.',
|
||||||
|
)]
|
||||||
|
class GetAudioCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private EntityManagerInterface $entityManager,
|
||||||
|
private HttpClientInterface $httpClient
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->addOption('dry-run', null, InputOption::VALUE_NONE, 'No DB changes.');
|
||||||
|
// No specific --debug flag needed, we will output verbose logs by default for now
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$isDryRun = $input->getOption('dry-run');
|
||||||
|
$noteRepository = $this->entityManager->getRepository(Note::class);
|
||||||
|
|
||||||
|
$io->title("Starting Audio Matcher");
|
||||||
|
|
||||||
|
// 1. Fetch Notes
|
||||||
|
$qb = $noteRepository->createQueryBuilder('n')
|
||||||
|
->leftJoin('n.user', 'u')
|
||||||
|
->addSelect('u')
|
||||||
|
->where('n.recording IS NULL OR n.recording = :empty')
|
||||||
|
->andWhere('u.homeChurchRSS IS NOT NULL')
|
||||||
|
->orderBy('n.date', 'DESC') // <--- Added Sort Here
|
||||||
|
->setParameter('empty', '');
|
||||||
|
//$query = $qb->getQuery();
|
||||||
|
|
||||||
|
//print ($query->getSql());
|
||||||
|
|
||||||
|
$notesMissingAudio = $qb->getQuery()->getResult();
|
||||||
|
$count = count($notesMissingAudio);
|
||||||
|
$io->text("Found $count notes in database missing audio.");
|
||||||
|
|
||||||
|
if ($count === 0) {
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Group by User
|
||||||
|
$notesByUser = [];
|
||||||
|
foreach ($notesMissingAudio as $note) {
|
||||||
|
$userId = (string) $note->getUser()->getId();
|
||||||
|
$notesByUser[$userId]['user'] = $note->getUser();
|
||||||
|
$notesByUser[$userId]['notes'][] = $note;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Process Per User
|
||||||
|
foreach ($notesByUser as $userId => $data) {
|
||||||
|
$user = $data['user'];
|
||||||
|
$userNotes = $data['notes'];
|
||||||
|
$rssUrl = $user->getHomeChurchRSS();
|
||||||
|
|
||||||
|
$io->section("User: {$user->getEmail()} (Notes: " . count($userNotes) . ")");
|
||||||
|
$io->text("Fetching RSS: $rssUrl");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Pass $io to helper for debug output
|
||||||
|
$rssItems = $this->fetchRssItems($rssUrl, $io);
|
||||||
|
|
||||||
|
if (empty($rssItems)) {
|
||||||
|
$io->warning("RSS feed was empty or failed to parse.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$matchCount = 0;
|
||||||
|
|
||||||
|
foreach ($userNotes as $note) {
|
||||||
|
if (!$note->getDate()) {
|
||||||
|
$io->text(" > Note ID {$note->getId()} skipped (No Date)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$noteDateString = $note->getDate()->format('Y-m-d');
|
||||||
|
$noteTitle = $note->getTitle();
|
||||||
|
$io->text("---------------------------------------------------");
|
||||||
|
$io->text("Checking Note: [$noteDateString] '$noteTitle'");
|
||||||
|
|
||||||
|
$bestMatch = null;
|
||||||
|
$highestConfidence = 0;
|
||||||
|
|
||||||
|
foreach ($rssItems as $item) {
|
||||||
|
// DEBUG: Show Date Comparison
|
||||||
|
if ($item['date_string'] !== $noteDateString) {
|
||||||
|
// Uncomment the line below if you want to see EVERY failed date comparison (can be noisy)
|
||||||
|
// $io->text(" - REJECTED: Date mismatch (RSS: {$item['date_string']})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG: Show Score Calculation
|
||||||
|
$confidence = $this->calculateConfidence($note, $item);
|
||||||
|
$io->text(sprintf(
|
||||||
|
" - DATE MATCHED. Score: %d%%. RSS Title: '%s'",
|
||||||
|
$confidence,
|
||||||
|
$item['title']
|
||||||
|
));
|
||||||
|
|
||||||
|
if ($confidence >= 80 && $confidence > $highestConfidence) {
|
||||||
|
$highestConfidence = $confidence;
|
||||||
|
$bestMatch = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($bestMatch) {
|
||||||
|
$matchCount++;
|
||||||
|
$io->success("Match Found! ($highestConfidence%)");
|
||||||
|
if (!$isDryRun) {
|
||||||
|
$note->setRecording($bestMatch['url']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$io->text(" > No match found for this note.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$isDryRun) {
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($matchCount > 0) {
|
||||||
|
$io->success("Found $matchCount matches");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$io->error("Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively fetches RSS items if pagination links are present.
|
||||||
|
*/
|
||||||
|
private function fetchRssItems(string $startUrl, SymfonyStyle $io): array
|
||||||
|
{
|
||||||
|
$items = [];
|
||||||
|
$nextUrl = $startUrl;
|
||||||
|
$pageCount = 0;
|
||||||
|
$maxPages = 20; // Safety brake to prevent infinite loops
|
||||||
|
|
||||||
|
do {
|
||||||
|
$pageCount++;
|
||||||
|
$io->text(" > Fetching Feed Page $pageCount: $nextUrl");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $this->httpClient->request('GET', $nextUrl);
|
||||||
|
$content = $response->getContent();
|
||||||
|
|
||||||
|
// Suppress warnings for malformed XML
|
||||||
|
$xml = @simplexml_load_string($content);
|
||||||
|
|
||||||
|
if ($xml === false) {
|
||||||
|
$io->warning("XML Parsing Failed on page $pageCount");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$io->warning("HTTP Request Failed on page $pageCount: " . $e->getMessage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Parse Items on this page
|
||||||
|
$pageItemsCount = 0;
|
||||||
|
foreach ($xml->channel->item as $item) {
|
||||||
|
$namespaces = $item->getNamespaces(true);
|
||||||
|
$speaker = '';
|
||||||
|
|
||||||
|
// Speaker Logic
|
||||||
|
if (isset($namespaces['itunes'])) {
|
||||||
|
$itunes = $item->children($namespaces['itunes']);
|
||||||
|
$speaker = (string) ($itunes->author ?? '');
|
||||||
|
}
|
||||||
|
if (empty($speaker) && isset($namespaces['dc'])) {
|
||||||
|
$dc = $item->children($namespaces['dc']);
|
||||||
|
$speaker = (string) ($dc->creator ?? '');
|
||||||
|
}
|
||||||
|
if (empty($speaker)) {
|
||||||
|
$speaker = (string) ($item->author ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date Parsing
|
||||||
|
$dateString = null;
|
||||||
|
if (isset($item->pubDate)) {
|
||||||
|
try {
|
||||||
|
$dt = new \DateTimeImmutable((string)$item->pubDate);
|
||||||
|
$dateString = $dt->format('Y-m-d');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// ignore bad date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$items[] = [
|
||||||
|
'title' => (string) $item->title,
|
||||||
|
'speaker' => $speaker,
|
||||||
|
'url' => (string) ($item->enclosure['url'] ?? ''),
|
||||||
|
'date_string' => $dateString,
|
||||||
|
];
|
||||||
|
$pageItemsCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->text(" Found $pageItemsCount items on this page.");
|
||||||
|
|
||||||
|
// 2. Look for "Next Page" link (RFC 5005 / Atom)
|
||||||
|
$nextUrl = null;
|
||||||
|
|
||||||
|
// Get namespaces on the <channel> element
|
||||||
|
$namespaces = $xml->channel->getNamespaces(true);
|
||||||
|
|
||||||
|
if (isset($namespaces['atom'])) {
|
||||||
|
$atom = $xml->channel->children($namespaces['atom']);
|
||||||
|
foreach ($atom->link as $link) {
|
||||||
|
// We are looking for <atom:link rel="next" href="..." />
|
||||||
|
$attributes = $link->attributes();
|
||||||
|
if (isset($attributes['rel']) && (string)$attributes['rel'] === 'next') {
|
||||||
|
$nextUrl = (string)$attributes['href'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Check for raw <link rel="next"> if atom ns missing (rare but happens)
|
||||||
|
if (!$nextUrl && property_exists($xml->channel, 'link')) {
|
||||||
|
foreach ($xml->channel->link as $link) {
|
||||||
|
$attributes = $link->attributes();
|
||||||
|
if (isset($attributes['rel']) && (string)$attributes['rel'] === 'next') {
|
||||||
|
$nextUrl = (string)$attributes['href'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} while ($nextUrl && $pageCount < $maxPages);
|
||||||
|
|
||||||
|
$io->success(sprintf("Finished fetching. Total items: %d (across %d pages)", count($items), $pageCount));
|
||||||
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calculateConfidence(Note $note, array $rssItem): float
|
||||||
|
{
|
||||||
|
$noteTitle = $this->normalize($note->getTitle());
|
||||||
|
$rssTitle = $this->normalize($rssItem['title']);
|
||||||
|
|
||||||
|
$noteSpeaker = $this->normalize($note->getSpeaker()->getName() ?? '');
|
||||||
|
$rssSpeaker = $this->normalize($rssItem['speaker']);
|
||||||
|
|
||||||
|
similar_text($noteTitle, $rssTitle, $titlePercent);
|
||||||
|
|
||||||
|
if (!empty($noteSpeaker) && !empty($rssSpeaker)) {
|
||||||
|
similar_text($noteSpeaker, $rssSpeaker, $speakerPercent);
|
||||||
|
return ($titlePercent + $speakerPercent) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $titlePercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalize(string $input): string
|
||||||
|
{
|
||||||
|
return strtolower(trim($input));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -144,6 +144,10 @@ class IngestReferenceCommand extends Command
|
|||||||
$ref->setContent($md);
|
$ref->setContent($md);
|
||||||
$ref->setName($this->name);
|
$ref->setName($this->name);
|
||||||
$ref->setType($this->type);
|
$ref->setType($this->type);
|
||||||
|
|
||||||
|
if ($this->type == 'cd') {
|
||||||
|
$label = substr(basename($file), 0, -3);
|
||||||
|
}
|
||||||
$ref->setLabel($label);
|
$ref->setLabel($label);
|
||||||
|
|
||||||
$this->io->success("Ingested {$this->name} as {$this->type}:{$label}");
|
$this->io->success("Ingested {$this->name} as {$this->type}:{$label}");
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use Exception;
|
|||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\Mailer\MailerInterface;
|
use Symfony\Component\Mailer\MailerInterface;
|
||||||
use Symfony\Component\Mime\Address;
|
use Symfony\Component\Mime\Address;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
@@ -453,6 +454,47 @@ class AjaxController extends AbstractController
|
|||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/save-profile', name: 'app_save_profile', methods: ['POST'])]
|
||||||
|
public function saveProfile(Request $req, EntityManagerInterface $emi): Response
|
||||||
|
{
|
||||||
|
$data = json_decode($req->getContent());
|
||||||
|
/** @var App\Entity\User $user */
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return new JsonResponse(['msg' => 'No User']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data->passChange) {
|
||||||
|
if(!$data->password) {
|
||||||
|
return new JsonResponse(['msg' => 'Blank password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo check that password matches current password
|
||||||
|
if ($data->password != $user->getPassword()) {
|
||||||
|
return new JsonResponse(['msg' => 'Invalid password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data->newPassword != $data->confPassword) {
|
||||||
|
return new JsonResponse(['msg' => 'Passwords don\'t match']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->setName($data->name);
|
||||||
|
$user->setEmail($data->email);
|
||||||
|
$user->setHomeChurchRSS($data->homeChurch);
|
||||||
|
|
||||||
|
$emi->persist($user);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$emi->flush();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return new JsonResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse(['msg' => 'Updated']);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/save-settings', name: 'app_save_settings', methods: ['POST'])]
|
#[Route('/save-settings', name: 'app_save_settings', methods: ['POST'])]
|
||||||
public function saveSettings(Request $req, EntityManagerInterface $emi): Response
|
public function saveSettings(Request $req, EntityManagerInterface $emi): Response
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Entity\User;
|
|||||||
use App\Entity\Speaker;
|
use App\Entity\Speaker;
|
||||||
use App\Entity\Series;
|
use App\Entity\Series;
|
||||||
use App\Entity\SharedNote;
|
use App\Entity\SharedNote;
|
||||||
|
use App\Service\DatabaseTransferService;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@@ -40,6 +41,7 @@ class DefaultController extends AbstractController
|
|||||||
'last4Notes' => $last4Notes,
|
'last4Notes' => $last4Notes,
|
||||||
'reverseNoteSort' => $openNotes,
|
'reverseNoteSort' => $openNotes,
|
||||||
'isAdmin' => $this->isGranted('ROLE_ADMIN'),
|
'isAdmin' => $this->isGranted('ROLE_ADMIN'),
|
||||||
|
'xferDB' => DatabaseTransferService::isTransferEnabled(),
|
||||||
'meta' => $meta,
|
'meta' => $meta,
|
||||||
'speakers' => $speakers,
|
'speakers' => $speakers,
|
||||||
'series' => $series,
|
'series' => $series,
|
||||||
@@ -89,7 +91,7 @@ class DefaultController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/reference-editor', name: 'app_reference_editor')]
|
#[Route('/reference-editor', 'app_reference_editor')]
|
||||||
public function referenceEditor(EntityManagerInterface $emi): Response
|
public function referenceEditor(EntityManagerInterface $emi): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||||
@@ -97,10 +99,64 @@ class DefaultController extends AbstractController
|
|||||||
return $this->render('editors/reference-editor.html.twig');
|
return $this->render('editors/reference-editor.html.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/template-editor', name: 'app_template_editor')]
|
#[Route('/template-editor', 'app_template_editor')]
|
||||||
public function templateEditor(): Response
|
public function templateEditor(): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||||
return $this->render('editors/template-editor.html.twig');
|
return $this->render('editors/template-editor.html.twig');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/xfer-database', name: 'app_admin_transfer_db')]
|
||||||
|
public function transfer(DatabaseTransferService $service, Request $request): Response
|
||||||
|
{
|
||||||
|
$step = $request->query->get('step', 'init');
|
||||||
|
$session = $request->getSession();
|
||||||
|
|
||||||
|
if ($step === 'init') {
|
||||||
|
$service->createSchema();
|
||||||
|
$session->remove('transfer_logs');
|
||||||
|
return $this->redirectToRoute('app_admin_transfer_db', ['step' => 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($step === 'summary') {
|
||||||
|
// Finalize
|
||||||
|
$service->finalizeEnvSwap();
|
||||||
|
|
||||||
|
return $this->render('default/transfer_summary.html.twig', [
|
||||||
|
'logs' => $session->get('transfer_logs', [])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes = $service->getEntityClasses();
|
||||||
|
$totalClasses = count($classes);
|
||||||
|
|
||||||
|
if (isset($classes[$step])) {
|
||||||
|
$class = explode("\\", $classes[$step]);
|
||||||
|
$className = end($class);
|
||||||
|
|
||||||
|
$func = "transfer{$className}Table";
|
||||||
|
|
||||||
|
if (method_exists($service, $func)) {
|
||||||
|
$skippedCount = $service->{$func}();
|
||||||
|
|
||||||
|
if ($skippedCount > 0) {
|
||||||
|
$logs = $session->get('transfer_logs', []);
|
||||||
|
$logs[] = "Skipped $skippedCount orphaned records in the $className table.";
|
||||||
|
$session->set('transfer_logs', $logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$progress = round((($step+1) / $totalClasses) * 100);
|
||||||
|
$nextStep = (($step + 1) < $totalClasses) ? ($step + 1) : 'summary';
|
||||||
|
|
||||||
|
return $this->render('default/transfer_progress.html.twig', [
|
||||||
|
'current' => (isset($classes[$step+1]) ? end(explode("\\", $classes[$step+1])) : 'summary'),
|
||||||
|
'progress' => $progress,
|
||||||
|
'next_step' => $nextStep,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 7: Logout and redirect
|
||||||
|
return $this->redirectToRoute('app_logout');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use Symfony\Component\Uid\Uuid;
|
|||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||||
|
#[ORM\Table(name: 'app_user')]
|
||||||
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
|
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
|
||||||
class User implements UserInterface, PasswordAuthenticatedUserInterface, JsonSerializable
|
class User implements UserInterface, PasswordAuthenticatedUserInterface, JsonSerializable
|
||||||
{
|
{
|
||||||
@@ -71,6 +72,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, JsonSer
|
|||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private ?array $metaData = null;
|
private ?array $metaData = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $homeChurchRSS = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->series = new ArrayCollection();
|
$this->series = new ArrayCollection();
|
||||||
@@ -323,4 +327,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, JsonSer
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHomeChurchRSS(): ?string
|
||||||
|
{
|
||||||
|
return $this->homeChurchRSS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHomeChurchRSS(?string $homeChurchRSS): static
|
||||||
|
{
|
||||||
|
$this->homeChurchRSS = $homeChurchRSS;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,501 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Entity\Bible;
|
||||||
|
use App\Entity\Note;
|
||||||
|
use App\Entity\Reference;
|
||||||
|
use App\Entity\Series;
|
||||||
|
use App\Entity\Speaker;
|
||||||
|
use App\Entity\Template;
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\ORM\Tools\SchemaTool;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
use PDO;
|
||||||
|
use PDOException;
|
||||||
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
|
class DatabaseTransferService
|
||||||
|
{
|
||||||
|
private PDO $srcDB;
|
||||||
|
private PDO $destDB;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private string $projectDir,
|
||||||
|
private ManagerRegistry $doctrine
|
||||||
|
) {
|
||||||
|
// connect the source database
|
||||||
|
switch (explode(":", $_ENV['DATABASE_URL'])[0]) {
|
||||||
|
case 'pdo-sqlite';
|
||||||
|
list($driverName, $dbFile) = explode(":/", $_ENV['DATABASE_URL']);
|
||||||
|
$this->srcDB = new PDO('sqlite:'.$dbFile);
|
||||||
|
break;
|
||||||
|
case 'pdo-mysql';
|
||||||
|
case 'pdo-pgsql';
|
||||||
|
$dsn = $this->convertDBURLString($_ENV['DATABASE_URL']);
|
||||||
|
$this->srcDB = new PDO($dsn['dsn'], $dsn['username'], $dsn['password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect the destination database
|
||||||
|
switch(explode(':', $_ENV['XFER_DATABASE_URL'])[0]) {
|
||||||
|
case 'pdo-sqlite':
|
||||||
|
list($driverName, $dbFile) = explode(":/", $_ENV['XFER_DATABASE']);
|
||||||
|
$this->destDB = new PDO('sqlite:'.$dbFile);
|
||||||
|
break;
|
||||||
|
case 'pdo-mysql':
|
||||||
|
case 'pdo-pgsql':
|
||||||
|
$dsn = $this->convertDBURLString($_ENV['XFER_DATABASE_URL']);
|
||||||
|
$this->destDB = new PDO($dsn['dsn'], $dsn['username'], $dsn['password']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createSchema(): void
|
||||||
|
{
|
||||||
|
$destEm = $this->doctrine->getManager('transfer');
|
||||||
|
$metadata = $destEm->getMetadataFactory()->getAllMetadata();
|
||||||
|
$tool = new SchemaTool($destEm);
|
||||||
|
|
||||||
|
$tool->dropSchema($metadata);
|
||||||
|
$tool->createSchema($metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityClasses(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
User::class,
|
||||||
|
Bible::class,
|
||||||
|
Reference::class,
|
||||||
|
Template::class,
|
||||||
|
Series::class,
|
||||||
|
Speaker::class,
|
||||||
|
Note::class
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferUserTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM user";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO user (id, email, roles, password, name, meta_data, home_church_rss) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :email, :roles, :password, :name, :meta_data, :home_church)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'email' => $row['email'],
|
||||||
|
'roles' => $row['roles'],
|
||||||
|
'password' => $row['password'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'meta_data' => $row['meta_data'],
|
||||||
|
'home_church' => $row['home_church_rss'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferBibleTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM bible";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO bible (id, book, chapter, verse, content, book_index, label) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :book, :chapter, :verse, :content, :book_index, :label)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$uuid = Uuid::fromString($row['id']);
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'id' => $uuid->toBinary(),
|
||||||
|
'book' => $row['book'],
|
||||||
|
'chapter' => $row['chapter'],
|
||||||
|
'verse' => $row['verse'],
|
||||||
|
'content' => $row['content'],
|
||||||
|
'book_index' => $row['book_index'],
|
||||||
|
'label' => $row['label'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferReferenceTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM reference";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO reference (id, type, name, label, ndx, content) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :type, :name, :label, :ndx, :content)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'type' => $row['type'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'label' => $row['label'],
|
||||||
|
'ndx' => $row['ndx'],
|
||||||
|
'content' => $row['content'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferTemplateTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM template";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO template (id, user_id, name, content) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :user_id, :name, :content)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'user_id' => $row['user_id'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'content' => $row['content'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferSpeakerTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM speaker";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO speaker (id, name, user_id) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :name, :user_id)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'user_id' => $row['user_id'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferSeriesTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM series";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO series (id, name, user_id, template_id) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :name, :user_id, :template_id)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'user_id' => $row['user_id'],
|
||||||
|
'template_id' => $row['template_id'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferNoteTable(): int
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM note";
|
||||||
|
$stmt = $this->srcDB->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$insQuery = "INSERT INTO note (id, title, date, passage, refs, text, speaker_id, series_id, user_id, recording) ".
|
||||||
|
"VALUES ".
|
||||||
|
"(:id, :title, :date, :passage, :refs, :text, :speaker_id, :series_id, :user_id, :recording)";
|
||||||
|
$destStmt = $this->destDB->prepare($insQuery);
|
||||||
|
$skippedCount = 0;
|
||||||
|
|
||||||
|
$this->destDB->beginTransaction();
|
||||||
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$params = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
'speaker_id' => $row['speaker_id'],
|
||||||
|
'series_id' => $row['series_id'],
|
||||||
|
'user_id' => $row['user_id'],
|
||||||
|
'title' => $row['title'],
|
||||||
|
'date' => $row['date'],
|
||||||
|
'passage' => $row['passage'],
|
||||||
|
'refs' => $row['refs'],
|
||||||
|
'text' => $row['text'],
|
||||||
|
'recording' => $row['recording'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->destDB->exec("SAVEPOINT row_insert");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$destStmt->execute($params);
|
||||||
|
|
||||||
|
$this->destDB->exec("RELEASE SAVEPOINT row_insert");
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$skipCodes = [
|
||||||
|
'23000',
|
||||||
|
'23503',
|
||||||
|
'23505',
|
||||||
|
];
|
||||||
|
if (in_array($e->getCode(), $skipCodes, true)) {
|
||||||
|
$this->destDB->exec("ROLLBACK TO SAVEPOINT row_insert");
|
||||||
|
$skippedCount++; // 4. Increment the counter
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->rollBack();
|
||||||
|
print_r($params);
|
||||||
|
die("Critical Error: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->destDB->commit();
|
||||||
|
|
||||||
|
return $skippedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function finalizeEnvSwap(): void
|
||||||
|
{
|
||||||
|
$envPath = $this->projectDir . '/.env';
|
||||||
|
|
||||||
|
if (!file_exists($envPath)) {
|
||||||
|
throw new \Exception(".env file not found at $envPath");
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = file($envPath);
|
||||||
|
$newLines = [];
|
||||||
|
$xferUrlValue = null;
|
||||||
|
|
||||||
|
// 1. First pass: Find the value of XFER_DATABASE_URL
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if (preg_match('/^XFER_DATABASE_URL=(.*)/', trim($line), $matches)) {
|
||||||
|
$xferUrlValue = $matches[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$xferUrlValue) {
|
||||||
|
throw new \Exception("XFER_DATABASE_URL not found in .env file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Second pass: Perform the swap
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$trimmed = trim($line);
|
||||||
|
|
||||||
|
// Prepend #OLD_ to the existing DATABASE_URL
|
||||||
|
if (preg_match('/^DATABASE_URL=/', $trimmed)) {
|
||||||
|
$newLines[] = "# OLD_" . $line;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename XFER_DATABASE_URL to DATABASE_URL
|
||||||
|
if (preg_match('/^XFER_DATABASE_URL=/', $trimmed)) {
|
||||||
|
$newLines[] = "DATABASE_URL=" . $xferUrlValue . PHP_EOL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newLines[] = $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($envPath, implode('', $newLines));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isTransferEnabled(): bool
|
||||||
|
{
|
||||||
|
return (isset($_ENV['XFER_DATABASE_URL']) && !empty($_ENV['XFER_DATABASE_URL']));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertDBURLString(string $DBURL): array
|
||||||
|
{
|
||||||
|
$dsn = preg_split("/[:|\/|\@]+/", $DBURL);
|
||||||
|
if (count($dsn) > 6) {
|
||||||
|
die('You cannot have special characters in your password for the database entry during this process. Change the database user password to just use alpha-numeric characters, then run again. You can change it back to more secure after this transfer is complete');
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = [
|
||||||
|
'dsn' => substr($dsn[0], 4).':'.
|
||||||
|
'host='.$dsn[3].';'.
|
||||||
|
'port='.$dsn[4].';'.
|
||||||
|
'dbname='.$dsn[5],
|
||||||
|
'username' => $dsn[1],
|
||||||
|
'password' => $dsn[2]
|
||||||
|
];
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,4 +32,58 @@ class Utils
|
|||||||
// error message or try to resend the message
|
// error message or try to resend the message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function filePerms($file): string
|
||||||
|
{
|
||||||
|
$perms = fileperms($file);
|
||||||
|
|
||||||
|
switch ($perms & 0xF000) {
|
||||||
|
case 0xC000: // socket
|
||||||
|
$info = 's';
|
||||||
|
break;
|
||||||
|
case 0xA000: // symbolic link
|
||||||
|
$info = 'l';
|
||||||
|
break;
|
||||||
|
case 0x8000: // regular
|
||||||
|
$info = 'r';
|
||||||
|
break;
|
||||||
|
case 0x6000: // block special
|
||||||
|
$info = 'b';
|
||||||
|
break;
|
||||||
|
case 0x4000: // directory
|
||||||
|
$info = 'd';
|
||||||
|
break;
|
||||||
|
case 0x2000: // character special
|
||||||
|
$info = 'c';
|
||||||
|
break;
|
||||||
|
case 0x1000: // FIFO pipe
|
||||||
|
$info = 'p';
|
||||||
|
break;
|
||||||
|
default: // unknown
|
||||||
|
$info = 'u';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owner
|
||||||
|
$info .= (($perms & 0x0100) ? 'r' : '-');
|
||||||
|
$info .= (($perms & 0x0080) ? 'w' : '-');
|
||||||
|
$info .= (($perms & 0x0040) ?
|
||||||
|
(($perms & 0x0800) ? 's' : 'x' ) :
|
||||||
|
(($perms & 0x0800) ? 'S' : '-'));
|
||||||
|
|
||||||
|
// Group
|
||||||
|
$info .= (($perms & 0x0020) ? 'r' : '-');
|
||||||
|
$info .= (($perms & 0x0010) ? 'w' : '-');
|
||||||
|
$info .= (($perms & 0x0008) ?
|
||||||
|
(($perms & 0x0400) ? 's' : 'x' ) :
|
||||||
|
(($perms & 0x0400) ? 'S' : '-'));
|
||||||
|
|
||||||
|
// World
|
||||||
|
$info .= (($perms & 0x0004) ? 'r' : '-');
|
||||||
|
$info .= (($perms & 0x0002) ? 'w' : '-');
|
||||||
|
$info .= (($perms & 0x0001) ?
|
||||||
|
(($perms & 0x0200) ? 't' : 'x' ) :
|
||||||
|
(($perms & 0x0200) ? 'T' : '-'));
|
||||||
|
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"doctrine/deprecations": {
|
||||||
|
"version": "1.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"doctrine/doctrine-bundle": {
|
"doctrine/doctrine-bundle": {
|
||||||
"version": "2.12",
|
"version": "2.12",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
@@ -91,6 +100,18 @@
|
|||||||
".env"
|
".env"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/form": {
|
||||||
|
"version": "8.0",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.2",
|
||||||
|
"ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/csrf.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/framework-bundle": {
|
"symfony/framework-bundle": {
|
||||||
"version": "7.0",
|
"version": "7.0",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
@@ -182,6 +203,18 @@
|
|||||||
"tests/bootstrap.php"
|
"tests/bootstrap.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/property-info": {
|
||||||
|
"version": "8.0",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.3",
|
||||||
|
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/property_info.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/routing": {
|
"symfony/routing": {
|
||||||
"version": "7.0",
|
"version": "7.0",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||||
|
<link rel='manifest' href='{{ asset('manifest.json') }}'>
|
||||||
|
|
||||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||||
{% block stylesheets %}{% endblock %}
|
{% block stylesheets %}{% endblock %}
|
||||||
@@ -15,5 +16,19 @@
|
|||||||
<body class='is-preload' onload='{% if onLoad is defined %}{{ onLoad }}{% endif %}'>
|
<body class='is-preload' onload='{% if onLoad is defined %}{{ onLoad }}{% endif %}'>
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
{% block javascripts %}{% endblock %}
|
{% block javascripts %}{% endblock %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
navigator.serviceWorker.register('/sw.js')
|
||||||
|
.then(registration => {
|
||||||
|
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log('ServiceWorker registration failed: ', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ let saveFailureCount = {{ meta.saveFailureCount }};
|
|||||||
<!-- The modal body -->
|
<!-- The modal body -->
|
||||||
<form id="emailForm" class="modal-body">
|
<form id="emailForm" class="modal-body">
|
||||||
<label for="shareEmail">Enter Friends Email:</label>
|
<label for="shareEmail">Enter Friends Email:</label>
|
||||||
<input type="email" id="shareEmail" name="email" required />
|
<input type="email" id="shareEmail" name="email" autocomplete=false required />
|
||||||
<button type='button' id="submit" class="btn btn-primary" onclick='shareNote()'>Submit</button>
|
<button type='button' id="submit" class="btn btn-primary" onclick='shareNote()'>Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Sermon Notes{% endblock %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
<link href="{{ asset('css/jquery-ui.theme.css') }}" rel="stylesheet" />
|
||||||
|
<link href="{{ asset('css/jquery-ui.structure.css') }}" rel="stylesheet" />
|
||||||
|
<link href="{{ asset('css/light-mode.css') }}" rel="stylesheet" data-css="mode" data-light="{{ asset('css/light-mode.css') }}" data-dark="{{ asset('css/dark-mode.css') }}" />
|
||||||
|
<link href="{{ asset('css/styles.css') }}" rel="stylesheet" />
|
||||||
|
|
||||||
|
<link href="//cdn.datatables.net/2.0.8/css/dataTables.dataTables.min.css" rel="stylesheet" />
|
||||||
|
<style>
|
||||||
|
#notes,
|
||||||
|
#notePreview {
|
||||||
|
font-size: {{ meta.noteTextSize }}pt;
|
||||||
|
}
|
||||||
|
.input-error {
|
||||||
|
/* border: solid 2px red !important; */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
<script src="{{ asset('js/jquery.min.js') }}"></script>
|
||||||
|
<script src="{{ asset('js/jquery-ui.js') }}"></script>
|
||||||
|
<script src="{{ asset('js/browser.min.js') }}"></script>
|
||||||
|
<script src="{{ asset('js/breakpoints.min.js') }}"></script>
|
||||||
|
<script src="{{ asset('js/util.js') }}"></script>
|
||||||
|
{#<script src="{{ asset('js/main.js') }}"></script>#}
|
||||||
|
<script src="//momentjs.com/downloads/moment-with-locales.js"></script>
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.2/markdown-it.min.js" integrity="sha512-ohlWmsCxOu0bph1om5eDL0jm/83eH09fvqLDhiEdiqfDeJbEvz4FSbeY0gLJSVJwQAp0laRhTXbUQG+ZUuifUQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script src="//cdn.datatables.net/2.0.8/js/dataTables.min.js"></script>
|
||||||
|
|
||||||
|
{{ importmap('app') }}
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import * as ref from "{{ asset('js/reference.js') }}";
|
||||||
|
import * as home from "{{ asset('js/home.js') }}";
|
||||||
|
import * as note from "{{ asset('js/note.js') }}";
|
||||||
|
import { state } from "{{ asset('js/state.js') }}";
|
||||||
|
import * as series from "{{ asset('js/series.js') }}";
|
||||||
|
import * as speaker from "{{ asset('js/speaker.js') }}";
|
||||||
|
import * as template from "{{ asset('js/template.js') }}";
|
||||||
|
window.ref = ref;
|
||||||
|
window.home = home;
|
||||||
|
window.note = note;
|
||||||
|
window.state = state;
|
||||||
|
window.series = series;
|
||||||
|
window.speaker = speaker;
|
||||||
|
window.template = template;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
home.initHome();
|
||||||
|
|
||||||
|
state.saveInterval = ({{ meta.saveInterval }} * 1000);
|
||||||
|
state.saveTimeout = ({{ meta.saveTimeout }} * 1000);
|
||||||
|
const SAVE_FAILURE_LIMIT = {{ meta.saveFailureCount }};
|
||||||
|
state.saveFailureCount = {{ meta.saveFailureCount }};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{% include('default/sidebar.html.twig') %}
|
||||||
|
|
||||||
|
<div id="query"></div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="ref-btn-header"> </div>
|
||||||
|
<div class="ref-btn bottom-row">
|
||||||
|
<ul id="ref-list" class="ref-list"></ul>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-note-header">
|
||||||
|
<h2>Notes</h2>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="mobile-dark-mode-checkbox"/>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<button id="mobile-open-ref" class="button">
|
||||||
|
<i class="fa fa-book"></i>
|
||||||
|
</button>
|
||||||
|
<button id="mobile-show-hide-btn" class="button">
|
||||||
|
<i class="fa fa-table"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="ref-header">
|
||||||
|
<h2>Reference</h2>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="dark-mode-checkbox"/>
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<div class="ref-header-buttons">
|
||||||
|
<button id="increaseFont"><i class="fa fa-plus"></i></button>
|
||||||
|
<button id="decreaseFont"><i class="fa fa-minus"></i></button>
|
||||||
|
<button id="open-ref" class="button"><i class="fa fa-book"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="ref-text" class="ref-text bottom-row" style="font-size:{{ meta.noteTextSize }}pt;"></div>
|
||||||
|
<div id="note-list">
|
||||||
|
<table id="note-table" data-order='[[ 3, "desc" ]]'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Speaker</th>
|
||||||
|
<th>Passage</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="note-header">
|
||||||
|
<div class="note-header-column">
|
||||||
|
<h2>Notes</h2>
|
||||||
|
<i id="save-check" class="fa fa-save"></i>
|
||||||
|
</div>
|
||||||
|
<div class="note-header-column">
|
||||||
|
<button id="previewBtn" class="button">
|
||||||
|
<i class="fa fa-eye"></i>
|
||||||
|
</button>
|
||||||
|
<button id="show-hide-btn" class="button">
|
||||||
|
<i class="fa fa-table"></i>
|
||||||
|
</button>
|
||||||
|
<button id="shareBtn" class="button">
|
||||||
|
<i class="fas fa-share-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="note-header-column">
|
||||||
|
<div class="dropdown" id="templateDropdown">
|
||||||
|
<select id="template" onchange="template.retrieveTemplate('template', 'notes')" >
|
||||||
|
<option value="0">-- Template --</option>
|
||||||
|
{% for t in app.user.templates %}
|
||||||
|
<option value="{{ t.id }}">{{ t.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="notes bottom-row">
|
||||||
|
<div class="fields-container">
|
||||||
|
<input type="hidden" id="noteId" value="">
|
||||||
|
<input type="text" id="noteTitle" placeholder="Title..." onkeyup="state.textDirty=true;state.saved=false;">
|
||||||
|
<input type="text" id="noteDate" placeholder="Date..." onchange="state.textDirty=true;state.saved=false;">
|
||||||
|
<input type="text" id="passage" placeholder="Passage..." onchange="state.textDirty=true;state.saved=false;">
|
||||||
|
<input type="text" id="recording" placeholder="Recording link...">
|
||||||
|
<input type="text" id="newSpeaker" placeholder="Name..." onkeyup="speaker.saveSpeaker(event)" style="display:none;">
|
||||||
|
<div class="dropdown" id="speakerDropdown">
|
||||||
|
<select id="speaker" onchange="speaker.newSpeaker()">
|
||||||
|
<option value="0">-- Speaker --</option>
|
||||||
|
<option value="new">New Speaker</option>
|
||||||
|
{% for s in speakers %}
|
||||||
|
<option value="{{ s.id }}">{{ s.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<input type="text" id="newSeries" placeholder="Series..." onkeyup="series.saveSeries(event)" style="display:none;">
|
||||||
|
<div class="dropdown" id="seriesDropdown">
|
||||||
|
<select id="series" onchange="series.newSeries()">
|
||||||
|
<option value="0">-- Series --</option>
|
||||||
|
<option value="new">New Series</option>
|
||||||
|
{% for s in series %}
|
||||||
|
<option value="{{ s.id }}">{{ s.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="note-text">
|
||||||
|
<textarea id="notes" wrap="hard" style="font-size: {{ meta.noteTextSize }}pt"></textarea>
|
||||||
|
</div>
|
||||||
|
<div id="notePreview"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="refQuery">
|
||||||
|
<select id="referenceType" onchange="ref.retrieveBooks()">
|
||||||
|
<option>-- Select --</option>
|
||||||
|
<option value="bible">Bible</option>
|
||||||
|
<option value="creed">Various Creed</option>
|
||||||
|
<option value="bc">Belgic Confession</option>
|
||||||
|
<option value="hc">Heidelberg Catechism</option>
|
||||||
|
<option value="cd">Canons of Dort</option>
|
||||||
|
<option value="wcf">Westminster Confession of Faith</option>
|
||||||
|
<option value="wsc">Westminster Shorter Catechism</option>
|
||||||
|
<option value="wlc">Westminster Larger Catechism</option>
|
||||||
|
<option value="lbc">London Baptist Confession</option>
|
||||||
|
<option value="39a">Thirty-Nine Articles</option>
|
||||||
|
<option value="1hc">First Helvetic Confession</option>
|
||||||
|
<option value="2hc">Second Helvetic Confession</option>
|
||||||
|
<option value="sd">Savoy Declaration</option>
|
||||||
|
<option value="agc">Augsburg Confession</option>
|
||||||
|
</select>
|
||||||
|
<select id="referenceBook" onchange="ref.filterBooks()" style="display:none;"></select>
|
||||||
|
<span id="chapter-range"></span>
|
||||||
|
<input type="text" id="referenceSearch" placeholder="Search" onkeyup="ref.filterVerse()" style="display:none;">
|
||||||
|
<span id="verse-range"></span>
|
||||||
|
<br />
|
||||||
|
<button id="searchBtn">Search</button>
|
||||||
|
<button id="closeSearch">Close</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="passage-popup"></div>
|
||||||
|
|
||||||
|
<div id="modal-backdrop" class="modal-backdrop fade in" style="display:none;"></div>
|
||||||
|
|
||||||
|
<div id="modal-container" class="modal-container" style="display:none;">
|
||||||
|
<header class="modal-header">Share Note</header>
|
||||||
|
<form id="emailForm" class="modal-body">
|
||||||
|
<label for="shareEmail">Enter Friends Email:</label>
|
||||||
|
<input type="email" id="shareEmail" name="email" required />
|
||||||
|
<button type="button" id="submit" class="btn btn-primary" onclick="note.shareNote()">Submit</button>
|
||||||
|
</form>
|
||||||
|
<footer class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal" onclick="note.closeShareNote()">Close</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<link href="{{ asset('css/main.css') }}" rel="stylesheet" />
|
<link href="{{ asset('css/main.css') }}" rel="stylesheet" />
|
||||||
<link href="{{ asset('css/jquery-ui.theme.css') }}" rel='stylesheet' />
|
<link href="{{ asset('css/jquery-ui.theme.css') }}" rel='stylesheet' />
|
||||||
<link href="{{ asset('css/jquery-ui.structure.css') }}" rel='stylesheet' />
|
<link href="{{ asset('css/jquery-ui.structure.css') }}" rel='stylesheet' />
|
||||||
<link href="{{ asset('css/style.css') }}" rel='stylesheet' />
|
<link href="{{ asset('styles/style.css') }}" rel='stylesheet' />
|
||||||
<link href='//cdn.datatables.net/2.0.8/css/dataTables.dataTables.min.css' rel='stylesheet' />
|
<link href='//cdn.datatables.net/2.0.8/css/dataTables.dataTables.min.css' rel='stylesheet' />
|
||||||
<style>
|
<style>
|
||||||
.flex-container {
|
.flex-container {
|
||||||
@@ -80,6 +80,49 @@ $(function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function saveProfile() {
|
||||||
|
const name = $('#name');
|
||||||
|
const email = $('#email');
|
||||||
|
const homeChurch = $('#home-church');
|
||||||
|
const password = $('#password');
|
||||||
|
const newPassword = $('#new-password');
|
||||||
|
const confPassword = $('#conf-password');
|
||||||
|
let passChange = false;
|
||||||
|
|
||||||
|
if (newPassword.val().length > 0) {
|
||||||
|
if (password.val().length == 0) {
|
||||||
|
alert('If you want to change your password you need to put in the current password as well');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword.val() != confPassword.val()) {
|
||||||
|
alert('New password and confirm passwords do not match');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
passChange = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('/save-profile', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
'name': name.val(),
|
||||||
|
'email': email.val(),
|
||||||
|
'homeChurch': homeChurch.val(),
|
||||||
|
'passChange': passChange,
|
||||||
|
'password': password.val(),
|
||||||
|
'newPassword': newPassword.val(),
|
||||||
|
'confPassword': confPassword.val()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(results => {
|
||||||
|
alert(results.msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
var saveInterval = $('#save-interval');
|
var saveInterval = $('#save-interval');
|
||||||
var saveReferences = $('#save-references');
|
var saveReferences = $('#save-references');
|
||||||
@@ -128,6 +171,9 @@ function rollUp(cont) {
|
|||||||
<label for='email'>Email: </label>
|
<label for='email'>Email: </label>
|
||||||
<input type='email' id='email' name='email' value='{{ app.user.email }}' /><br />
|
<input type='email' id='email' name='email' value='{{ app.user.email }}' /><br />
|
||||||
|
|
||||||
|
<label for='home-church'>Home Church RSS Feed: </label>
|
||||||
|
<input type='text' id='home-church' name='home-church' value='{{ app.user.homeChurchRSS }}' /><br />
|
||||||
|
|
||||||
<label for='password'>Password: </label>
|
<label for='password'>Password: </label>
|
||||||
<input type='password' id='password' name='password' /><br/>
|
<input type='password' id='password' name='password' /><br/>
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,9 @@
|
|||||||
<li><a href="#" onclick="saveNote()">Save Note</a></li>
|
<li><a href="#" onclick="saveNote()">Save Note</a></li>
|
||||||
{% if isAdmin is defined and isAdmin %}
|
{% if isAdmin is defined and isAdmin %}
|
||||||
<li><a href='/reference-editor'>Reference Editor</a></li>
|
<li><a href='/reference-editor'>Reference Editor</a></li>
|
||||||
|
{% if xferDB is defined and xferDB %}
|
||||||
|
<li><a href='/xfer-database'>Transfer Database</a></li>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><a href='#' onclick="openRef()">Open Reference</a></li>
|
<li><a href='#' onclick="openRef()">Open Reference</a></li>
|
||||||
<li><a href='/template-editor'>Template Editor</a></li>
|
<li><a href='/template-editor'>Template Editor</a></li>
|
||||||
@@ -67,4 +70,5 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
<a class='toggle' href='#sidebar' id='sidebar-link'>Toggle</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mt-5 text-center">
|
||||||
|
<h2>Transferring Database...</h2>
|
||||||
|
<p>Processing: <strong>{{ current }}</strong></p>
|
||||||
|
|
||||||
|
<div class="progress mb-3" style="height: 30px;">
|
||||||
|
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||||||
|
role="progressbar" style="width: {{ progress }}%;">
|
||||||
|
{{ progress }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Please do not close this window.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Automatically move to the next step after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "{{ path('app_admin_transfer_db', {step: next_step}) }}";
|
||||||
|
}, 500);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
<link href="{{ asset('css/main.css') }}" rel="stylesheet" />
|
||||||
|
<link href='{{ asset('css/jquery-ui.theme.css') }}' rel='stylesheet' />
|
||||||
|
<link href='{{ asset('css/jquery-ui.structure.css') }}' rel='stylesheet' />
|
||||||
|
<link href='{{ asset('styles/style.css') }}' rel='stylesheet' />
|
||||||
|
<link href='//cdn.datatables.net/2.0.8/css/dataTables.dataTables.min.css' rel='stylesheet' />
|
||||||
|
<style>
|
||||||
|
button.button i {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
.input-error {
|
||||||
|
border: solid 2px red !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
<script src="{{ asset('js/jquery.min.js') }}"></script>
|
||||||
|
<script src='{{ asset('js/jquery-ui.js') }}'></script>
|
||||||
|
<script src="{{ asset('js/browser.min.js') }}"></script>
|
||||||
|
<script src="{{ asset('js/breakpoints.min.js') }}"></script>
|
||||||
|
<script src="{{ asset('js/util.js') }}"></script>
|
||||||
|
<script src="{{ asset('js/main.js') }}"></script>
|
||||||
|
<script src='//momentjs.com/downloads/moment-with-locales.js'></script>
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.2/markdown-it.min.js" integrity="sha512-ohlWmsCxOu0bph1om5eDL0jm/83eH09fvqLDhiEdiqfDeJbEvz4FSbeY0gLJSVJwQAp0laRhTXbUQG+ZUuifUQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<script src='//cdn.datatables.net/2.0.8/js/dataTables.min.js'></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mt-5 text-center">
|
||||||
|
<h2 class="text-success">Database Transfer Complete!</h2>
|
||||||
|
<p>Your data has been successfully moved to the new database, and the environment variables have been updated.</p>
|
||||||
|
|
||||||
|
{% if logs|length > 0 %}
|
||||||
|
<div class="alert alert-warning text-start mx-auto" style="max-width: 600px;">
|
||||||
|
<h5 class="alert-heading">Data Clean-up Notice</h5>
|
||||||
|
<p>During the transfer, the following orphaned records were safely ignored to maintain database integrity:</p>
|
||||||
|
<ul>
|
||||||
|
{% for log in logs %}
|
||||||
|
<li>{{ log }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-success mx-auto" style="max-width: 600px;">
|
||||||
|
Perfect transfer! No orphaned records were found.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<p class="text-muted">You must log in again to establish a connection with the new database.</p>
|
||||||
|
<a href="{{ path('app_logout') }}" class="btn btn-primary btn-lg">Acknowledge & Relogin</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% include('default/sidebar.html.twig') %}
|
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
@@ -44,6 +43,7 @@
|
|||||||
value="{{ csrf_token('authenticate') }}"
|
value="{{ csrf_token('authenticate') }}"
|
||||||
>
|
>
|
||||||
<button class="btn btn-lg btn-primary" type="submit">Sign in</button>
|
<button class="btn btn-lg btn-primary" type="submit">Sign in</button>
|
||||||
|
<button class="btn btn-lg btn-primary" id='back'>Back</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user