diff --git a/composer.json b/composer.json index bc6d6bc..ec0fbf0 100644 --- a/composer.json +++ b/composer.json @@ -42,6 +42,7 @@ "symfony/validator": "6.4.*", "symfony/web-link": "6.4.*", "symfony/yaml": "6.4.*", + "symfonycasts/verify-email-bundle": "^1.17", "twig/extra-bundle": "^2.12|^3.0", "twig/twig": "^2.12|^3.0" }, @@ -95,11 +96,12 @@ } }, "require-dev": { + "doctrine/doctrine-fixtures-bundle": "^3.5", "phpunit/phpunit": "^9.5", "symfony/browser-kit": "6.4.*", "symfony/css-selector": "6.4.*", "symfony/debug-bundle": "6.4.*", - "symfony/maker-bundle": "^1.0", + "symfony/maker-bundle": "^1.58", "symfony/phpunit-bridge": "^7.0", "symfony/stopwatch": "6.4.*", "symfony/web-profiler-bundle": "6.4.*" diff --git a/config/bundles.php b/config/bundles.php index d5e1567..3584c3f 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -14,4 +14,5 @@ return [ Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], + SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true], ]; diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 367af25..25e429e 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -12,6 +12,19 @@ security: main: lazy: true provider: users_in_memory + custom_authenticator: App\Security\AppAuthenticator + logout: + path: app_logout + # where to redirect after logout + # target: app_any_route + + remember_me: + secret: '%kernel.secret%' + lifetime: 604800 + path: / + # by default, the feature is enabled by checking a checkbox in the + # login form, uncomment the following line to always enable it. + #always_remember_me: true # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall diff --git a/config/services.yaml b/config/services.yaml index 2d6a76f..9026b0d 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -6,19 +6,25 @@ parameters: services: - # default configuration for services in *this* file - _defaults: - autowire: true # Automatically injects dependencies in your services. - autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. + app.user_provider: + class: App\Security\AppAuthenticator + arguments: [ + "%kernel.container_params.user_entity_manager", + "%kernel.container_params.password_hasher", + ] # default configuration for services in *this* file - # makes classes in src/ available to be used as services - # this creates a service per class whose id is the fully-qualified class name - App\: - resource: '../src/' - exclude: - - '../src/DependencyInjection/' - - '../src/Entity/' - - '../src/Kernel.php' + _defaults: + autowire: true # Automatically injects dependencies in your services. + autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. - # add more service definitions when explicit configuration is needed - # please note that last definitions always *replace* previous ones + # makes classes in src/ available to be used as services + # this creates a service per class whose id is the fully-qualified class name + App\: + resource: "../src/" + exclude: + - "../src/DependencyInjection/" + - "../src/Entity/" + - "../src/Kernel.php" + + # add more service definitions when explicit configuration is needed + # please note that last definitions always *replace* previous ones diff --git a/public/css/style.css b/public/css/style.css index 1d424c9..181180a 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -91,6 +91,72 @@ ul.menu-open { box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); } +#passage { + width: 100px; +} + +#newSpeaker { + display: none; + width: 110px; +} + +#newSeries { + display: none; + width: 110px; +} + +#noteSearch { + display: none; +} + +#fields-container { + display: none; +} + +#show-hide-btn { + position: absolute; + top: 10px; + right: 20px; + font-size: 16px; + cursor: pointer; + background-color: #4CAF50; + /* green */ + color: #fff; + /* white */ + border: none; + border-radius: 5px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.6); + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + transition: all 0.3s ease-out; +} +#show-hide-btn:hover { + background-color: #3e8e41; + /* darker green */ +} +#show-hide-btn::before { + font-size: 16px; + cursor: pointer; +} + +#show-hide-btn::before { + font-size: 16px; + cursor: pointer; + transition: transform 0.5s ease-in-out; +} + +#show-hide-btn:hover::before { + transform: translateY(-10px); +} + +#fields-container.show { + display: block; +} + .notes { width: 55%; } @@ -100,6 +166,22 @@ textarea#notes { height: 100%; } +#notePreview { + display: none; + overflow-x: scroll; +} + +#note-header-left, +#note-header-right { + display: inline-flex; + flex-direction: row; + width: 49%; +} + +#note-header-right { + flex-direction: row-reverse !important; +} + div#refQuery { display: none; position: absolute; diff --git a/public/css/style.css.map b/public/css/style.css.map index ccb2786..ed7a6e2 100644 --- a/public/css/style.css.map +++ b/public/css/style.css.map @@ -1 +1 @@ -{"version":3,"sources":["style.scss","style.css"],"names":[],"mappings":"AAAA,kCAAA;AACA;EACI,SAAA;ACCJ;;ADEA;EACI,aAAA;EACA,mBAAA;EACA,eAAA;EACA,6BAAA;EACA,oBAAA;EACA,yBAAA;EACA,iBAAA;EACA,iCAAA;EACA,cAAA;ACCJ;;ADEA;EACI,YAAA;EACA,WAAA;ACCJ;;ADEA,6BAAA;AAEA;EACI,aAAA;EACA,8BAAA;EACA,mBAAA;EACA,wBAAA;EACA,uBAAA;EACA,eAAA;EACA,kBAAA;EACA,WAAA;EACA,YAAA;EACA,YAAA;EACA,wCAAA;ACAJ;;ADGA;EACI,YAAA;ACAJ;;ADGA;;EAEI,YAAA;ACAJ;;ADIA;EACI,yBAAA;EACA,yBAAA;EACA,aAAA;EACA,YAAA;EACA,gBAAA;EACA,qBAAA;EACA,eAAA;ACDJ;;ADWA;EACI,WAAA;ACRJ;;ADWA;EACI,aAAA;EACA,2BAAA;EACA,mBAAA;EACA,aAAA;EACA,WAAA;EACA,eAAA;EACA,kBAAA;ACRJ;;ADWA;EACI,SAAA;EACA,UAAA;ACRJ;;ADWA;EACI,gBAAA;EACA,qBAAA;ACRJ;;ADWA;EACI,UAAA;ACRJ;;ADWA;EACI,kBAAA;EACA,WAAA;EACA,YAAA;EACA,uBAAA;EACA,kBAAA;EACA,wCAAA;ACRJ;;ADWA;EACI,UAAA;ACRJ;;ADWA;EACI,WAAA;EACA,YAAA;ACRJ;;ADWA;EACI,aAAA;EACA,kBAAA;EACA,YAAA;EACA,oCAAA;ACRJ;;ADWA;EACI,YAAA;EACA,kBAAA;EACA,kBAAA;EACA,eAAA;EACA,gBAAA;EACA,WAAA;EACA,yBAAA;EACA,YAAA;EACA,YAAA;EACA,eAAA;ACRJ","file":"style.css"} \ No newline at end of file +{"version":3,"sources":["style.scss","style.css"],"names":[],"mappings":"AAAA,kCAAA;AACA;EACI,SAAA;ACCJ;;ADEA;EACI,aAAA;EACA,mBAAA;EACA,eAAA;EACA,6BAAA;EACA,oBAAA;EACA,yBAAA;EACA,iBAAA;EACA,iCAAA;EACA,cAAA;ACCJ;;ADEA;EACI,YAAA;EACA,WAAA;ACCJ;;ADEA,6BAAA;AAEA;EACI,aAAA;EACA,8BAAA;EACA,mBAAA;EACA,wBAAA;EACA,uBAAA;EACA,eAAA;EACA,kBAAA;EACA,WAAA;EACA,YAAA;EACA,YAAA;EACA,wCAAA;ACAJ;;ADGA;EACI,YAAA;ACAJ;;ADGA;;EAEI,YAAA;ACAJ;;ADIA;EACI,yBAAA;EACA,yBAAA;EACA,aAAA;EACA,YAAA;EACA,gBAAA;EACA,qBAAA;EACA,eAAA;ACDJ;;ADIA;EACI,WAAA;ACDJ;;ADIA;EACI,aAAA;EACA,2BAAA;EACA,mBAAA;EACA,aAAA;EACA,WAAA;EACA,eAAA;EACA,kBAAA;ACDJ;;ADIA;EACI,SAAA;EACA,UAAA;ACDJ;;ADIA;EACI,gBAAA;EACA,qBAAA;ACDJ;;ADIA;EACI,UAAA;ACDJ;;ADIA;EACI,kBAAA;EACA,WAAA;EACA,YAAA;EACA,uBAAA;EACA,kBAAA;EACA,wCAAA;ACDJ;;ADIA;EACI,YAAA;ACDJ;;ADIA;EACI,aAAA;EACA,YAAA;ACDJ;;ADIA;EACI,aAAA;EACA,YAAA;ACDJ;;ADIA;EACI,aAAA;ACDJ;;ADIA;EACI,aAAA;ACDJ;;ADIA;EACI,kBAAA;EACA,SAAA;EACA,WAAA;EACA,eAAA;EACA,eAAA;EACA,yBAAA;EACA,UAAA;EACA,WAAA;EACA,UAAA;EACA,YAAA;EACA,kBAAA;EACA,wCAAA;EACA,kBAAA;EACA,kBAAA;EACA,qBAAA;EACA,qBAAA;EACA,eAAA;EACA,eAAA;EAGA,6BAAA;ACDJ;ADGI;EACI,yBAAA;EACA,iBAAA;ACDR;ADII;EACI,eAAA;EACA,eAAA;ACFR;;ADMA;EACI,eAAA;EACA,eAAA;EACA,sCAAA;ACHJ;;ADMA;EACI,4BAAA;ACHJ;;ADMA;EACI,cAAA;ACHJ;;ADMA;EACI,UAAA;ACHJ;;ADMA;EACI,WAAA;EACA,YAAA;ACHJ;;ADMA;EACI,aAAA;EACA,kBAAA;ACHJ;;ADMA;;EAEI,oBAAA;EACA,mBAAA;EACA,UAAA;ACHJ;;ADMA;EACI,sCAAA;ACHJ;;ADMA;EACI,aAAA;EACA,kBAAA;EACA,YAAA;EACA,oCAAA;ACHJ;;ADMA;EACI,YAAA;EACA,kBAAA;EACA,kBAAA;EACA,eAAA;EACA,gBAAA;EACA,WAAA;EACA,yBAAA;EACA,YAAA;EACA,YAAA;EACA,eAAA;ACHJ","file":"style.css"} \ No newline at end of file diff --git a/public/css/style.min.css b/public/css/style.min.css index 27acf7e..db67d14 100644 --- a/public/css/style.min.css +++ b/public/css/style.min.css @@ -1 +1 @@ -body{margin:0}.container{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-evenly;align-items:stretch;align-content:flex-start;max-width:1020px;margin:0 auto}.top-tab{height:50px;width:100%}.hamburger{display:flex;justify-content:space-between;align-items:center;margin:15px 0 15px 15px;border:#000 solid 1px;cursor:pointer;border-radius:5px;width:20px;height:20px;padding:5px;box-shadow:0 2px 5px rgba(0,0,0,.3)}.fa-bars:before,.fa-navicon:before{padding:3px}ul.menu-open{display:block !important;background-color:#f9c74d;padding:10px;z-index:100;list-style:none;list-style-type:none;position:fixed}.ref-tab{width:60px}.tab button{display:flex;justify-content:flex-start;align-items:center;height:100px;width:100%;cursor:pointer;text-align:center}.ref-tab ul{margin:0;padding:0}.ref-tab ul li{list-style:none;list-style-type:none}.ref{width:35%}.ref>div#ref{overflow-y:scroll;width:100%;height:100%;border:#000 solid 1px;border-radius:3px;box-shadow:0 2px 5px rgba(0,0,0,.3)}.notes{width:55%}textarea#notes{width:100%;height:100%}div#refQuery{display:none;position:absolute;z-index:100;background-color:rgba(0,0,0,.5)}div#refQuery #search{border:none;border-radius:5px;padding:10px 20px;font-size:16px;line-height:1.5;color:#333;background-color:#f4f4f4;width:150px;height:25px;cursor:pointer}/*# sourceMappingURL=style.min.css.map */ \ No newline at end of file +body{margin:0}.container{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-evenly;align-items:stretch;align-content:flex-start;max-width:1020px;margin:0 auto}.top-tab{height:50px;width:100%}.hamburger{display:flex;justify-content:space-between;align-items:center;margin:15px 0 15px 15px;border:#000 solid 1px;cursor:pointer;border-radius:5px;width:20px;height:20px;padding:5px;box-shadow:0 2px 5px rgba(0,0,0,.3)}.fa-check{color:green}.fa-bars:before,.fa-navicon:before{padding:3px}ul.menu-open{display:block !important;background-color:#f9c74d;padding:10px;z-index:100;list-style:none;list-style-type:none;position:fixed}.ref-tab{width:60px}.tab button{display:flex;justify-content:flex-start;align-items:center;height:100px;width:100%;cursor:pointer;text-align:center}.ref-tab ul{margin:0;padding:0}.ref-tab ul li{list-style:none;list-style-type:none}.ref{width:35%}.ref>div#ref{overflow-y:scroll;width:100%;height:100%;border:#000 solid 1px;border-radius:3px;box-shadow:0 2px 5px rgba(0,0,0,.3)}#passage{width:100px}#newSpeaker{display:none;width:110px}#newSeries{display:none;width:110px}#noteSearch{display:none}#fields-container{display:none}#show-hide-btn{position:absolute;top:10px;right:20px;font-size:16px;cursor:pointer;background-color:#4caf50;color:#fff;border:none;border-radius:5px;box-shadow:0 2px 5px rgba(0,0,0,.6);padding:15px 32px;text-align:center;text-decoration:none;display:inline-block;font-size:16px;margin:4px 2px;transition:all .3s ease-out}#show-hide-btn:hover{background-color:#3e8e41}#show-hide-btn::before{font-size:16px;cursor:pointer}#show-hide-btn::before{font-size:16px;cursor:pointer;transition:transform .5s ease-in-out}#show-hide-btn:hover::before{transform:translateY(-10px)}#fields-container.show{display:block}.notes{width:55%}textarea#notes{width:100%;height:100%}#notePreview{display:none;overflow-x:scroll}#note-header-left,#note-header-right{display:inline-flex;flex-direction:row;width:49%}#note-header-right{flex-direction:row-reverse !important}div#refQuery{display:none;position:absolute;z-index:100;background-color:rgba(0,0,0,.5)}div#refQuery #search{border:none;border-radius:5px;padding:10px 20px;font-size:16px;line-height:1.5;color:#333;background-color:#f4f4f4;width:150px;height:25px;cursor:pointer}/*# sourceMappingURL=style.min.css.map */ \ No newline at end of file diff --git a/public/css/style.min.css.map b/public/css/style.min.css.map index 0748a04..5830926 100644 --- a/public/css/style.min.css.map +++ b/public/css/style.min.css.map @@ -1 +1 @@ -{"version":3,"sources":["style.scss"],"names":[],"mappings":"AACA,KACI,QAAA,CAGJ,WACI,YAAA,CACA,kBAAA,CACA,cAAA,CACA,4BAAA,CACA,mBAAA,CACA,wBAAA,CACA,gBAAA,CAEA,aAAA,CAGJ,SACI,WAAA,CACA,UAAA,CAKJ,WACI,YAAA,CACA,6BAAA,CACA,kBAAA,CACA,uBAAA,CACA,qBAAA,CACA,cAAA,CACA,iBAAA,CACA,UAAA,CACA,WAAA,CACA,WAAA,CACA,mCAAA,CAGJ,mCAEI,WAAA,CAIJ,aACI,wBAAA,CACA,wBAAA,CACA,YAAA,CACA,WAAA,CACA,eAAA,CACA,oBAAA,CACA,cAAA,CAUJ,SACI,UAAA,CAGJ,YACI,YAAA,CACA,0BAAA,CACA,kBAAA,CACA,YAAA,CACA,UAAA,CACA,cAAA,CACA,iBAAA,CAGJ,YACI,QAAA,CACA,SAAA,CAGJ,eACI,eAAA,CACA,oBAAA,CAGJ,KACI,SAAA,CAGJ,aACI,iBAAA,CACA,UAAA,CACA,WAAA,CACA,qBAAA,CACA,iBAAA,CACA,mCAAA,CAGJ,OACI,SAAA,CAGJ,eACI,UAAA,CACA,WAAA,CAGJ,aACI,YAAA,CACA,iBAAA,CACA,WAAA,CACA,+BAAA,CAGJ,qBACI,WAAA,CACA,iBAAA,CACA,iBAAA,CACA,cAAA,CACA,eAAA,CACA,UAAA,CACA,wBAAA,CACA,WAAA,CACA,WAAA,CACA,cAAA","file":"style.min.css"} \ No newline at end of file +{"version":3,"sources":["style.scss"],"names":[],"mappings":"AACA,KACI,QAAA,CAGJ,WACI,YAAA,CACA,kBAAA,CACA,cAAA,CACA,4BAAA,CACA,mBAAA,CACA,wBAAA,CACA,gBAAA,CAEA,aAAA,CAGJ,SACI,WAAA,CACA,UAAA,CAKJ,WACI,YAAA,CACA,6BAAA,CACA,kBAAA,CACA,uBAAA,CACA,qBAAA,CACA,cAAA,CACA,iBAAA,CACA,UAAA,CACA,WAAA,CACA,WAAA,CACA,mCAAA,CAGJ,UACI,WAAA,CAGJ,mCAEI,WAAA,CAIJ,aACI,wBAAA,CACA,wBAAA,CACA,YAAA,CACA,WAAA,CACA,eAAA,CACA,oBAAA,CACA,cAAA,CAGJ,SACI,UAAA,CAGJ,YACI,YAAA,CACA,0BAAA,CACA,kBAAA,CACA,YAAA,CACA,UAAA,CACA,cAAA,CACA,iBAAA,CAGJ,YACI,QAAA,CACA,SAAA,CAGJ,eACI,eAAA,CACA,oBAAA,CAGJ,KACI,SAAA,CAGJ,aACI,iBAAA,CACA,UAAA,CACA,WAAA,CACA,qBAAA,CACA,iBAAA,CACA,mCAAA,CAGJ,SACI,WAAA,CAGJ,YACI,YAAA,CACA,WAAA,CAGJ,WACI,YAAA,CACA,WAAA,CAGJ,YACI,YAAA,CAGJ,kBACI,YAAA,CAGJ,eACI,iBAAA,CACA,QAAA,CACA,UAAA,CACA,cAAA,CACA,cAAA,CACA,wBAAA,CAEA,UAAA,CAEA,WAAA,CACA,iBAAA,CACA,mCAAA,CACA,iBAAA,CACA,iBAAA,CACA,oBAAA,CACA,oBAAA,CACA,cAAA,CACA,cAAA,CAGA,2BAAA,CAEA,qBACI,wBAAA,CAIJ,uBACI,cAAA,CACA,cAAA,CAIR,uBACI,cAAA,CACA,cAAA,CACA,oCAAA,CAGJ,6BACI,2BAAA,CAGJ,uBACI,aAAA,CAGJ,OACI,SAAA,CAGJ,eACI,UAAA,CACA,WAAA,CAGJ,aACI,YAAA,CACA,iBAAA,CAGJ,qCAEI,mBAAA,CACA,kBAAA,CACA,SAAA,CAGJ,mBACI,qCAAA,CAGJ,aACI,YAAA,CACA,iBAAA,CACA,WAAA,CACA,+BAAA,CAGJ,qBACI,WAAA,CACA,iBAAA,CACA,iBAAA,CACA,cAAA,CACA,eAAA,CACA,UAAA,CACA,wBAAA,CACA,WAAA,CACA,WAAA,CACA,cAAA","file":"style.min.css"} \ No newline at end of file diff --git a/public/css/style.scss b/public/css/style.scss index b6721ee..06069e4 100644 --- a/public/css/style.scss +++ b/public/css/style.scss @@ -56,13 +56,6 @@ ul.menu-open { position: fixed; } -.ref-tab, -.ref, -.notes { - // float: left; - // padding: 20px; -} - .ref-tab { width: 60px; } @@ -100,6 +93,76 @@ ul.menu-open { box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); } +#passage { + width: 100px; +} + +#newSpeaker { + display: none; + width: 110px; +} + +#newSeries { + display: none; + width: 110px; +} + +#noteSearch { + display: none; +} + +#fields-container { + display: none; +} + +#show-hide-btn { + position: absolute; + top: 10px; + right: 20px; + font-size: 16px; + cursor: pointer; + background-color: #4CAF50; + /* green */ + color: #fff; + /* white */ + border: none; + border-radius: 5px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.6); + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; + + &:hover { + background-color: #3e8e41; + /* darker green */ + } + + &::before { + font-size: 16px; + cursor: pointer; + } +} + +#show-hide-btn::before { + font-size: 16px; + cursor: pointer; + transition: transform 0.5s ease-in-out; +} + +#show-hide-btn:hover::before { + transform: translateY(-10px); +} + +#fields-container.show { + display: block; +} + .notes { width: 55% } @@ -109,6 +172,22 @@ textarea#notes { height: 100%; } +#notePreview { + display: none; + overflow-x: scroll; +} + +#note-header-left, +#note-header-right { + display: inline-flex; + flex-direction: row; + width: 49%; +} + +#note-header-right { + flex-direction: row-reverse !important; +} + div#refQuery { display: none; position: absolute; diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..0daf85f Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/js/data.js b/public/js/data.js new file mode 100644 index 0000000..5b9ca57 --- /dev/null +++ b/public/js/data.js @@ -0,0 +1,95 @@ +var BOOKS = { + "bible": [ + "Genesis", + "Exodus", + "Leviticus", + "Numbers", + "Deuteronomy", + "Joshua", + "Judges", + "Ruth", + "1 Samuel", + "2 Samuel", + "1 Kings", + "2 Kings", + "1 Chronicles", + "2 Chronicles", + "Ezra", + "Nehemiah", + "Esther", + "Job", + "Psalms", + "Proverbs", + "Ecclesiastes", + "Song of Solomon", + "Isaiah", + "Jeremiah", + "Lamentations", + "Ezekiel", + "Daniel", + "Hosea", + "Joel", + "Amos", + "Obadiah", + "Jonah", + "Micah", + "Nahum", + "Habakkuk", + "Zephaniah", + "Haggai", + "Zechariah", + "Malachi", + "Matthew", + "Mark", + "Luke", + "John", + "Acts", + "Romans", + "1 Corinthians", + "2 Corinthians", + "Galatians", + "Ephesians", + "Philippians", + "Colossians", + "1 Thessalonians", + "2 Thessalonians", + "1 Timothy", + "2 Timothy", + "Titus", + "Philemon", + "Hebrews", + "James", + "1 Peter", + "2 Peter", + "1 John", + "2 John", + "3 John", + "Jude", + "Revelation" + ], + "creed": { + "apc": "Apostle's Creed", + "nc": "Nicene Creed", + "atc": "Athanasian Creed", + "dc": "Definition of Chalcedon", + "fc": "French Confession" + }, + "bc": [ + 1, 37 + ], + "hc": [ + 1, 52 + ], + "cd": [ + "1", "2", "3", "5", "Conclusion" + ], + "wcf": [ + 1, 33 + ], + "wsc": [ + 1, 107 + ], + "wlc": [ + 1, 196 + ] +}; diff --git a/public/js/script.js b/public/js/script.js index c96df8c..bfcab29 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -1,7 +1,9 @@ // Get the link element const link = document.querySelector('.hamburger'); var converter = null; +var markdownit = null; var references = {}; +let saved = false; // Add an event listener to the link if (link) { @@ -13,7 +15,13 @@ if (link) { function setHeight() { converter = new showdown.Converter(); - converter.setFlavor('github'); + //converter.setFlavor(); + + markdownit = new markdownit({ + html: true, + linkify: true, + breaks: true + }); body = document.querySelector('body'); body.style.height = window.innerHeight + 'px'; @@ -28,9 +36,16 @@ function setHeight() { ref.style.height = (window.innerHeight - 125) + 'px'; notes = document.querySelector('.notes'); - notes.style.height = (window.innerHeight - 150) + 'px'; + notes.style.height = (window.innerHeight - 130) + 'px'; - setTimeout(saveNote, 5000); + notePreview = document.querySelector('#notePreview'); + notePreview.style.height = (window.innerHeight - 150) + 'px'; + + date = document.querySelector('#noteDate'); + dt = new Date(); + date.value = dt.getFullYear() + '-' + ((dt.getMonth() < 9) ? '0' + (dt.getMonth() + 1) : (dt.getMonth() + 1)) + '-' + dt.getDate(); + + setTimeout(saveNote, 10000); } function newNote() { @@ -38,68 +53,92 @@ function newNote() { notes.textContent = ''; } -function openNote() { - -} - +/** + * 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. + */ function saveNote(event) { - event.preventDefault(); + if (event) { + event.preventDefault(); + } - if(!validateNote()) { + if (!validateNote()) { setTimeout(saveNote, 5000); return; } + + 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 + }; fetch('/index.php/save-note', { method: 'POST', headers: { - "Content-Type": 'application/x-www-form-urlencoded' + "Content-Type": 'application/json' }, - body: { - title: document.querySelector('#noteTitle').value, - speaker: document.querySelector('#speaker').value, - series: document.querySelector('#series').value, - note: document.querySelector('#notes').textContent - } - .then(response => response.text) - .then(results => { - results = JSON.parse(results); - alert(results); - showSave(); + body: JSON.stringify(note) + }) + .then(response => response.json()) + .then(data => { + if (data.msg == 'saved' && !saved) { + showSave(); + saved = true; + } }) - }); - setTimeout(saveNote, 5000); + .catch(error => console.log(error)); + setTimeout(saveNote, 10000); } 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 id = document.querySelector('#noteId'); + const psg = document.querySelector('#passage'); - if(!title.value.length) {return false;} - - if(!speaker.value) {return false;} - - if(!series.value) {return false;} - - if(!note.textContent.length) {return false;} + if (!parseInt(id.value)) { return false; } + if (!title.value.length) { return false; } + if (!date.value) { return false; } + if (!parseInt(speaker.value)) { return false; } + if (!parseInt(series.value)) { return false; } + if (!psg.value) { return false; } + if (!note.value.length) { return false; } return true; } -function showSave() {// Get the element that will display the checkmark +/** + * 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. + */ +function showSave() { + if (saved) { return; } + var checkmark = document.getElementById("save-check"); - + // Schedule the animation to run every 1 second (which is equivalent to a 1-second delay between each iteration) - setInterval(function() { - // Increment the opacity of the checkmark by 0.01 each time - checkmark.style.opacity += 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 >= 1) { - checkmark.style.opacity = 0; - clearInterval(setInterval); - } + 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 >= 1) { + checkmark.style.opacity = 0; + clearInterval(si); + saved = false; + } }, 100); } @@ -111,11 +150,98 @@ function discardNote() { document.querySelector('#speaker').value = 0; document.querySelector('#series').value = 0; document.querySelector('#template').value = 0; + document.querySelector('#passage').value = ''; document.querySelector('#notes').value = ''; + fetch('/index.php/discard-note', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + 'id': document.querySelector('#noteId').value + }) + .then(response => response.json()) + .then(data => { + if (data.msg == 'deleted') { + alert('Note deleted.'); + } + }) + }); + openRef(); } +function newSpeaker() { + if (document.querySelector('#speaker').value == 'new') { + document.querySelector('#newSpeaker').style.display = 'block'; + document.querySelector('#speaker').style.display = 'none'; + } +} + +function saveSpeaker(event) { + if (event.keyCode == 13) { + fetch('/index.php/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 = 'block'; + + document.querySelector('#newSpeaker').value = ''; + document.querySelector('#speaker').value = results.id; + }); + } +} + +function newSeries() { + if (document.querySelector('#series').value == 'new') { + document.querySelector('#newSeries').style.display = 'block'; + document.querySelector('#series').style.display = 'none'; + } +} + +function saveSeries(event) { + if (event.keyCode == 13) { + fetch('/index.php/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 = 'block'; + + document.querySelector('#newSeries').value = ''; + document.querySelector('#series').value = results.id; + }); + } +} + function openRef() { refQuery = document.querySelector('#refQuery'); if (refQuery.style.display === 'block') { @@ -130,13 +256,17 @@ function openRef() { } function queryRef() { - const input = document.querySelector('#refQuery #search'); + var input = document.querySelector('#refQuery #search'); + var type = document.querySelector('#referenceType'); + var book = document.querySelector('#referenceBook'); fetch('/index.php/retrieve-reference', { method: 'POST', headers: { 'Content-Type': 'plain/text' }, body: JSON.stringify({ + 'type': type.value, + 'book': book.value, 'reference': input.value }) }) @@ -167,6 +297,8 @@ function queryRef() { references[results.title] = results.text; input.value = ''; + document.querySelector('#referenceType').value = 0; + document.querySelector('#referenceBook').value = 0; openRef(); link.click(); }); @@ -215,21 +347,177 @@ function saveTemplate() { }); } -function retrieveSeries() { +function toggleFields() { + const fieldsContainer = document.getElementById('fields-container'); + const showHideBtn = document.getElementById('show-hide-btn'); + if (fieldsContainer.classList.contains('show')) { + // Hide the fields when the button says "Show Fields" + fieldsContainer.classList.remove('show'); + fieldsContainer.style.display = 'none'; + showHideBtn.textContent = 'Show'; + } else { + // Show the fields when the button says "Hide Fields" + fieldsContainer.classList.add('show'); + fieldsContainer.style.display = 'block'; + showHideBtn.textContent = 'Hide'; + } + + setHeight(); } -function saveSeries() { +function retrieveBooks() { + const selectedType = document.querySelector('#referenceType').value; + if (!selectedType) { return; } + var bookList = document.querySelector('#referenceBook'); + bookList.style.display = "block"; + bookList.innerHTML = ''; + if (selectedType == 'bible') { + var none = document.createElement("option"); + none.value = ''; + none.text = '-- Select --'; + bookList.appendChild(none); + for (var x in BOOKS.bible) { + var newBook = document.createElement("option"); + newBook.text = BOOKS.bible[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 BOOKS.creed) { + var newBook = document.createElement('option'); + newBook.value = x; + newBook.text = 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 BOOKS.cd) { + var newBook = document.createElement("option"); + newBook.text = BOOKS.cd[x]; + bookList.appendChild(newBook); + } + } else { + var min = BOOKS[selectedType][0]; + var max = 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); + } + } } -function retrieveSpeaker() { - +function retrieveReference(el) { + fetch('/index.php/get-reference', { + method: "POST", + header: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + file: el.value, + type: el.options[el.selectedIndex].getAttribute('type') + }) + }) + .then(response => response.json()) + .then(results => { + document.querySelector('#reference').value = results.text; + }); } -function saveSpeaker() { +function saveReference() { + var select = document.querySelector('#references'); + fetch('/index.php/save-reference', { + method: 'POST', + header: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + type: select.options[select.selectedIndex].getAttribute('type'), + file: select.value, + text: document.querySelector('#reference').value + }) + }) + .then(response => response.json()) + .then(results => { + alert(results.msg); + document.querySelector('#reference').value = ''; + document.querySelector('#references').value = ''; + }); } -function referenceEditor() { +function previewNote() { + var noteText = document.querySelector('#notes'); + var notePreview = document.querySelector('#notePreview'); + var previewButton = document.querySelector('#previewBtn'); + + //notePreview.innerHTML = converter.makeHtml(noteText.value); + notePreview.innerHTML = markdownit.render(noteText.value); + + if (previewButton.value == 'Preview') { + previewButton.value = 'Hide Preview'; + noteText.style.display = 'none'; + notePreview.style.display = 'block'; + } else { + previewButton.value = 'Preview'; + noteText.style.display = 'block'; + notePreview.style.display = 'none'; + } + + findLinks(); } + +function findLinks() { + var links = document.querySelector('#notePreview').querySelectorAll('a'); + alert(links.length); +} + +function showSearchNote(event) { + event.preventDefault(); + var searchNote = document.querySelector('#noteSearch'); + if (searchNote.style.display == 'none') { + searchNote.style.display = 'block'; + } else { + searchNote.style.display = 'none'; + } +} + +function searchNote() { + const search = document.querySelector('#noteSearchQuery'); + + fetch('/index.php/search-note', { + method: 'POST', + header: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + "search": search.value + }) + }) + .then(response => response.json()) + .then(result => { + 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 = result.date; + document.querySelector('#noteId').value = result.id; + + document.querySelector('#noteSearch').style.display = 'none'; + link.click(); + }); +} \ No newline at end of file diff --git a/src/Command/IngestReferenceCommand.php b/src/Command/IngestReferenceCommand.php new file mode 100644 index 0000000..02e08a8 --- /dev/null +++ b/src/Command/IngestReferenceCommand.php @@ -0,0 +1,153 @@ +emi = $emi; + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this + ->addArgument('directory', InputArgument::REQUIRED, 'Directory to crawl') + ->addArgument('name', InputArgument::REQUIRED, 'Name of the reference') + ->addArgument('type', InputArgument::REQUIRED, 'Type of the reference') + ->addArgument('label', InputArgument::REQUIRED, 'Label of the reference') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->io = new SymfonyStyle($input, $output); + $this->dir = $input->getArgument('directory'); + $this->name = $input->getArgument('name'); + $this->type = $input->getArgument('type'); + $this->label = $input->getArgument('label'); + + if (!$this->dir || !file_exists($this->dir) ||!is_dir($this->dir)) { + $this->io->error('Directory not specific or does not exist'); + return Command::FAILURE; + } + + if (!$this->name) { + $this->io->error('No name specified'); + return Command::FAILURE; + } + + if (!$this->type) { + $this->io->error('No type specified'); + return Command::FAILURE; + } + + if (!$this->label) { + $this->io->error('No label specified'); + return Command::FAILURE; + } + + $this->io->note("Crawling {$this->dir} and ingesting {$this->name} as {$this->type}:{$this->label}"); + + foreach ($this->getFiles() as $file) { + $ref = $this->processFile($file); + + $this->emi->persist($ref); + } + + $this->emi->flush(); + + return Command::SUCCESS; + } + + public function getFiles(): array + { + $this->files = glob($this->dir . '/*.md'); + + if(!$this->files || count($this->files) === 0) { + $this->io->warning("No files found in this directory\n{$this->dir}"); + $this->files = []; + } + + return $this->files; + } + + public function processFile(string $file): Reference|bool + { + $this->io->info("Processing {$file}"); + $md = trim(file_get_contents($file)); + $ref = new Reference(); + + if (!$md) { + $this->io->warning("File is empty\n{$file}"); + return false; + } + + $match = []; + $label = str_replace("{\$ndx}", "", $this->label); + if(preg_match("/([\d]+)/", $file, $match)) { + $ndx = ltrim($match[1], "0"); + $label = str_replace("{\$ndx}", $ndx, $this->label); + $ref->setNdx($ndx); + } elseif (preg_match("/\(([^\)]+)\)/", $file, $match)) { + $label = $match[1]; + } + + $ref->setContent($md); + $ref->setName($this->name); + $ref->setType($this->type); + $ref->setLabel($label); + + $this->io->success("Ingested {$this->name} as {$this->type}:{$label}"); + + return $ref; + } +} diff --git a/src/Controller/AjaxController.php b/src/Controller/AjaxController.php index ace3027..a7fb6cd 100644 --- a/src/Controller/AjaxController.php +++ b/src/Controller/AjaxController.php @@ -2,11 +2,15 @@ namespace App\Controller; +use App\Entity\Bible; +use DateTime; +use DateTimeZone; + use App\Entity\Series; use App\Entity\Speaker; use App\Entity\Template; use App\Entity\Notes; - +use App\Entity\Reference; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -85,23 +89,16 @@ class AjaxController extends AbstractController public function saveSpeaker(Request $req, EntityManagerInterface $emi): Response { $ret = new Response(); - $req = json_decode($req->getContent()); - $speaker_id = $req->speaker_id; - $speaker_name = $req->speaker_name; + $data = json_decode($req->getContent()); - if ($speaker_id) { - $speaker = $emi->getRepository(Speaker::class)->find($speaker_id); - $speaker->setName($speaker_name); - $emi->persist($speaker); - $emi->flush(); - $ret->setContent('updated'); - } else { - $speaker = new Speaker(); - $speaker->setName($speaker_name); - $emi->persist($speaker); - $emi->flush(); - $ret->setContent('added'); - } + $speaker = new Speaker(); + $speaker->setName($data->speakerName); + $emi->persist($speaker); + $emi->flush(); + $ret->setContent(json_encode([ + 'id' => $speaker->getId(), + 'msg' => $speaker->getName().' added' + ])); return $ret; } @@ -129,89 +126,186 @@ class AjaxController extends AbstractController public function saveSeries(Request $req, EntityManagerInterface $emi): Response { $ret = new Response(); - $req = json_decode($req->getContent()); - $series_id = $req->series_id; - $series_name = $req->series_name; + $data = json_decode($req->getContent()); - if ($series_id) { - $series = $emi->getRepository(Series::class)->find($series_id); - $series->setName($series_name); - $emi->persist($series); - $emi->flush(); - $ret->setContent('updated'); - } else { - $series = new Series(); - $series->setName($series_name); - $emi->persist($series); - $emi->flush(); - $ret->setContent('added'); - } + $series = new Series(); + $series->setName($data->seriesName); + $emi->persist($series); + $emi->flush(); + + $ret->setContent(json_encode([ + 'id' => $series->getId(), + 'msg' => $series->getName().' added' + ])); return $ret; } - #[Route('/autocomplete-reference', name: 'app_autocomplete_reference')] - public function autocompleteReference(Request $req): Response - { - $res = new Response(); - - return $res; - } - #[Route('/retrieve-reference', name: 'app_retrive_reference')] - public function retrieveReference(Request $req): Response + public function retrieveReference(Request $req, EntityManagerInterface $emi): Response { - $type = null; - $search = null; - $passage = null; - $res = new Response(); - $ref = json_decode($req->getContent())->reference; - if (count(explode(':', $ref)) > 2) { - list($type, $search, $passage) = explode(':', $ref); - } else { - list($type, $search) = explode(':', $ref); + $data = json_decode($req->getContent()); + $search = $data->reference; + + $ref = new Reference(); + $ref->setType($data->type); + if((int) $data->book) { + $ref->setNdx($data->book); } - $ret = match(strtolower($type)) { - 'bible' => ReferenceController::retrieveBible("{$search}:{$passage}"), - 'hc' => ReferenceController::retrieveHC($search), - 'bc' => ReferenceController::retrieveBC($search), - 'dc' => ReferenceController::retrieveCD($search), - 'wcf' => ReferenceController::retrieveWCF($search), - 'wsc' => ReferenceController::retrieveWSC($search), - 'wlc' => ReferenceController::retrieveWLC($search), - 'creed' => ReferenceController::retrieveCreed($search) + ReferenceController::$emi = $emi; + + $ret = match(strtolower($data->type)) { + 'bible' => ReferenceController::retrieveBible("{$data->book} {$search}"), + 'hc' => ReferenceController::retrieveHC($ref), + 'bc' => ReferenceController::retrieveBC($ref), + 'cd' => ReferenceController::retrieveCD($ref), + 'wcf' => ReferenceController::retrieveWCF($ref), + 'wsc' => ReferenceController::retrieveWSC($ref), + 'wlc' => ReferenceController::retrieveWLC($ref), + 'creed' => ReferenceController::retrieveCreed($data->book) }; - $res->setContent(json_encode(['text' => $ret, 'title' => "{$search}"])); + $res->setContent(json_encode(['text' => $ret->getContent(), 'title' => "{$ret->getLabel()}"])); return $res; } - #[Route('/open-note', name: 'app_open_note')] + #[Route('/get-reference', name: 'app_get_reference')] + public function getReference(Request $req, EntityManagerInterface $emi): Response + { + $res = new Response(); + $data = json_decode($req->getContent()); + $ret = match ($data->type) { + 'creed' => '/Creeds/', + 'bc' => '/Belgic/', + 'hc' => '/Heidelberg/', + 'cd' => '/Dort/', + 'wcf' => '/Westminster/Confessions/', + 'wsc' => '/Westminster/Shorter Catechism/', + 'wlc' => '/Westminster/Larger Catechism/' + }; + + $fc = file_get_contents(dirname(dirname(__DIR__))."/references{$ret}{$data->file}"); + + $res->setContent(json_encode(['text' => $fc])); + + return $res; + } + + #[Route('/save-reference', name: 'app_save_reference')] + public function saveReference(Request $req, EntityManagerInterface $emi): Response + { + $res = new Response(); + $data = json_decode($req->getContent()); + $path = match($data->type) { + 'creed' => 'Creeds', + 'bc' => 'Belgic', + 'hc' => 'Heidelberg', + 'cd' => 'Dort', + 'wcf' => 'Westminster/Confessions', + 'wsc' => 'Westminster/Shorter Catechism', + 'wlc' => 'Westminster/Larger Catechism' + }; + + $ret = file_put_contents(dirname(dirname(__DIR__))."/references/{$path}/{$data->file}", $data->text); + + if($ret !== false) { + $res->setContent(json_encode(['msg' => 'File Saved'])); + } else { + $res->setContent(json_encode(['msg' => 'Failed to save file'])); + } + + return $res; + } + + #[Route('/search-note', name: 'app_open_note')] public function openNote(Request $req, EntityManagerInterface $emi): Response { $res = new Response(); - - return $res; - } - - #[Route('/save-note', name: 'app_save_note')] - public function saveNote(Request $req, EntityManagerInterface $emi): Response - { - $res = new Response(); - $note = new Notes(); - $note->setTitle($req->get('title')); - - $series = $emi->getRepository(Series::class)->find($req->get('series')); - $speaker = $emi->getRepository(Speaker::class)->find($req->get('speaker')); - $note->setSeries($series); - $note->setSpeaker($speaker); - $note->setText($req->get('note')); - + $data = json_decode($req->getContent()); + $note = $emi->getRepository(Notes::class)->findNote($data->search); $res->setContent(json_encode($note)); return $res; } + + #[Route('/save-note', name: 'app_save_note', methods: ['POST'])] + public function saveNote(Request $req, EntityManagerInterface $emi): Response + { + $data = json_decode($req->getContent()); + $note = $emi->getRepository(Notes::class)->find($data->id); + + if (!$note) { + $note = new Notes(); + $note->setId($data->id); + } + + $note->setTitle($data->title); + $note->setDate(new DateTime($data->date)); + + $series = $emi->getRepository(Series::class)->find($data->series); + $speaker = $emi->getRepository(Speaker::class)->find($data->speaker); + $note->setSeries($series); + $note->setSpeaker($speaker); + $note->setText($data->note); + $note->setPassage($data->passage); + + $emi->persist($note); + $emi->flush(); + + $res = new Response(); + $res->setContent(json_encode([ + 'msg' => 'saved', + 'id' => $note->getId() + ])); + + return $res; + } + + #[Route('/discard-note', name: 'app_discard_note', methods: ['POST'])] + public function discardNote(Request $req, EntityManagerInterface $emi): Response + { + $data = json_decode($req->getContent()); + $note = $emi->getRepository(Notes::class)->find($data->id); + $emi->remove($note); + $emi->flush(); + $res = new Response(); + $res->setContent(json_encode([ + 'msg' => 'deleted' + ])); + + return $res; + } + + #[Route('/get-passage/{passage}', name: 'app_get_passage')] + public function getPassage($passage, EntityManagerInterface $emi): Response + { + $passage = str_replace('+', ' ', $passage); + $book = Bible::findBook($passage); + $chapter = Bible::findChapter($passage); + + $bible = new Bible(); + $bible->setBook($book); + $bible->setChapter($chapter); + $verse = Bible::findVerse($passage); + + $ret = $emi->getRepository(Bible::class)->findRange($bible, $verse); + + if (is_array($ret)) { + $text = null; + foreach($ret as $b) { + $text .= "{$b->getVerse()}. {$b->getContent()}".PHP_EOL; + } + $bible->setContent($text); + } elseif (is_a($ret, Bible::class)) { + $bible->setContent($ret->getContent()); + } + + $res = new Response(); + $res->setContent($bible->getContent()); + + return $res; + } } diff --git a/src/Controller/DefaultController.php b/src/Controller/DefaultController.php index 1e14b60..e4a29df 100644 --- a/src/Controller/DefaultController.php +++ b/src/Controller/DefaultController.php @@ -5,6 +5,7 @@ namespace App\Controller; use App\Entity\Speaker; use App\Entity\Template; use App\Entity\Series; +use App\Entity\Notes; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -19,12 +20,14 @@ class DefaultController extends AbstractController $speakers = $emi->getRepository(Speaker::class)->findAll(); $series = $emi->getRepository(Series::class)->findAll(); $templates = $emi->getRepository(Template::class)->findAll(); + $note = $emi->getRepository(Notes::class)->findLastNote(); return $this->render('default/index.html.twig', [ 'controller_name' => 'DefaultController', 'speakers' => $speakers, 'series' => $series, - 'templates' => $templates + 'templates' => $templates, + 'noteId' => ($note->getId() + 1) ]); } @@ -40,6 +43,32 @@ class DefaultController extends AbstractController #[Route('/reference-editor', name: 'app_reference_editor')] public function referenceEditor(EntityManagerInterface $emi): Response { - return $this->render('default/reference-editor.html.twig', []); + $ref_dir = dirname(dirname(__DIR__)).'/references/'; + $creeds = $this->stripPath(glob($ref_dir.'Creeds/*.md')); + $belgic = $this->stripPath(glob($ref_dir.'Belgic/*.md')); + $dort = $this->stripPath(glob($ref_dir.'Dort/*.md')); + $heidelberg = $this->stripPath(glob($ref_dir.'Heidelberg/*.md')); + $wcf = $this->stripPath(glob($ref_dir.'Westminster/Confessions/*.md')); + $wsc = $this->stripPath(glob($ref_dir.'Westminster/Shorter Catechism/*.md')); + $wlc = $this->stripPath(glob($ref_dir.'Westminster/Longer Catechism/*.md')); + + return $this->render('default/reference-editor.html.twig', [ + 'creeds' => $creeds, + 'belgic' => $belgic, + 'heidelberg' => $heidelberg, + 'dort' => $dort, + 'wcf' => $wcf, + 'wsc' => $wsc, + 'wlc' => $wlc, + ]); + } + + private function stripPath(array $files): array + { + $ret = []; + foreach($files as $f) { + $ret[] = basename($f); + } + return $ret; } } diff --git a/src/Controller/ReferenceController.php b/src/Controller/ReferenceController.php index fc0836f..56022c7 100644 --- a/src/Controller/ReferenceController.php +++ b/src/Controller/ReferenceController.php @@ -2,6 +2,9 @@ namespace App\Controller; +use App\Entity\Bible; +use App\Entity\Reference; +use Doctrine\ORM\EntityManagerInterface; use \Symfony\Bundle\FrameworkBundle\Controller\AbstractController; /** @@ -9,26 +12,63 @@ use \Symfony\Bundle\FrameworkBundle\Controller\AbstractController; */ class ReferenceController extends AbstractController { + + /** + * @var EntityManagerInterface + */ + public static EntityManagerInterface $emi; + /** * Method to retrieve a Bible reference * * @param string $ref * - * @return string + * @return Reference */ - public static function retrieveBible($ref): string + public static function retrieveBible($ref): Reference { - if(count(explode(' ', $ref)) > 2) { - list($index, $book, $chapter, $passage) = preg_split("/:| /", $ref); - $book = "{$index}{$book}"; - } else { - list($book, $chapter, $passage) = preg_split("/:| /", $ref); + $book = Bible::findBook($ref); + $chapter = Bible::findChapter($ref); + $passage = Bible::findVerse($ref); + $bible = new Bible(); + $bible->setBook($book); + $bible->setChapter($chapter); + $label = null; + + if (is_array($passage)) { + $passage_start = $passage[0]; + $passage_end = $passage[1]; + + $bible = self::$emi->getRepository(Bible::class)->findRange($bible, [$passage_start, $passage_end]); + $passage = "{$passage_start}-{$passage_end}"; + $label = "{$bible[0]->getLabel()} {$bible[0]->getChapter()}:{$passage}"; + } elseif (is_int($passage)) { + $bible = self::$emi->getRepository(Bible::class)->findBy(['book' => $book, 'chapter' => $chapter, 'verse' => $passage]); + $label = "{$bible[0]->getLabel()} {$bible[0]->getChapter()}:{$passage}"; + } elseif ($passage === false) { + $bible = self::$emi->getRepository(Bible::class)->findBy(['book' => $book, 'chapter' => $chapter]); + $label = "{$bible[0]->getLabel()} {$bible[0]->getChapter()}"; + + $passage = null; } - $file = glob(dirname(dirname(__DIR__))."/references/Bible/* - {$book}/{$book}{$chapter}.md"); - $res = implode("\n", file(current($file))); + if(is_array($bible)) { + $text = []; + foreach($bible as $b) { + $text[] = "{$b->getVerse()}. {$b->getContent()}"; + } + } else { + $text[] = "{$bible->getVerse()}. {$bible->getContent()}"; + } - return $res; + $reference = "{$book} {$chapter}".($passage === null ? '' : ":{$passage}"); + $ref = new Reference(); + $ref->setType('bible'); + $ref->setName($reference); + $ref->setLabel($label); + $ref->setContent("# {$reference}\n\n".implode("\n", $text)); + + return $ref; } /** @@ -36,9 +76,9 @@ class ReferenceController extends AbstractController * * @param string * - * @return string + * @return Reference */ - public static function retrieveCreed($ref) + public static function retrieveCreed($ref): Reference { $file = match($ref) { 'apc' => "Apostle's Creed.md", @@ -48,58 +88,86 @@ class ReferenceController extends AbstractController 'nc' => 'Nicene Creed.md' }; - return implode("\n", file(dirname(dirname(__DIR__))."/references/Creeds/{$file}")); + $r = self::$emi->getRepository(Reference::class)->findBy(['label' => $ref]); + $ref = $r[0]; + return $ref; } /** * Method to retrieve a Heidelberg Catechism reference * - * @param string $ref + * @param Reference $ref * - * @return string + * @return Reference */ - public static function retrieveHC($ref) + public static function retrieveHC($ref): Reference { - $ref = strtoupper($ref); - return implode("\n", file(dirname(dirname(__DIR__))."/references/Heidelberg/{$ref}.md")); + $r = self::$emi->getRepository(Reference::class)->findBy(['type' => $ref->getType(), 'ndx' => $ref->getNdx()]); + return $r[0]; } /** * Method to retrieve a Belgian Catechism reference * - * @param string $ref + * @param Reference $ref * - * @return string + * @return Reference */ - public static function retrieveBC($ref) + public static function retrieveBC($ref): Reference { - $ref = ucfirst($ref); - return implode("\n", file(dirname(dirname(__DIR__))."/references/Belgic/{$ref}.md")); + $r = self::$emi->getRepository(Reference::class)->findBy(['type' => $ref->getType(), 'ndx' => $ref->getNdx()]); + return $r[0]; } - public static function retrieveCD($ref) + /** + * Method to retrieve the Canon of Dort reference + * + * @param Reference $ref + * + * @return Reference + */ + public static function retrieveCD($ref): Reference { - $ref = strtoupper($ref); - return implode("\n", file(dirname(dirname(__DIR__))."/references/Dort/{$ref}.md")); + $r = self::$emi->getRepository(Reference::class)->findBy(['type' => $ref->getType(), 'ndx' => $ref->getNdx()]); + return $r[0]; } - public static function retrieveWSC($ref) + /** + * Method to retrieve a WSC reference + * + * @param Reference $ref + * + * @return Reference + */ + public static function retrieveWSC($ref): Reference { - $art = str_pad($ref, 3, '0', STR_PAD_LEFT); - $files = glob(dirname(dirname(__DIR__))."/references/Westminster/Shorter Catechism/WSC{$art}.md"); - return implode("\n", file(current($files))); + $r = self::$emi->getRepository(Reference::class)->findBy(['type' => $ref->getType(), 'ndx' => $ref->getNdx()]); + return $r[0]; } - public static function retrieveWLC($ref) + /** + * Method to retrieve a WLC reference + * + * @param Reference $ref + * + * @return Reference + */ + public static function retrieveWLC($ref): Reference { - $art = str_pad($ref, 3, '0', STR_PAD_LEFT); - $files = glob(dirname(dirname(__DIR__))."/references/Westminster/Larger Catechism/WLC{$art}.md"); - return implode("\n", file(current($files))); + $r = self::$emi->getRepository(Reference::class)->findBy(['type' => $ref->getType(), 'ndx' => $ref->getNdx()]); + return $r[0]; } - public static function retrieveWCF($ref) + /** + * Method to retrieve a WCF reference + * + * @param Reference $ref + * + * @return Reference + */ + public static function retrieveWCF($ref): Reference { - $files = glob(dirname(dirname(__DIR__))."/references/Westminster/Confessions/Chapter {$ref}.md"); - return implode("\n", file(current($files))); + $r = self::$emi->getRepository(Reference::class)->findBy(['type' => $ref->getType(), 'ndx' => $ref->getNdx()]); + return $r[0]; } } diff --git a/src/Entity/Bible.php b/src/Entity/Bible.php new file mode 100644 index 0000000..ea9fb05 --- /dev/null +++ b/src/Entity/Bible.php @@ -0,0 +1,159 @@ +id; + } + + public function getBook(): ?string + { + return $this->book; + } + + public function setBook(string $book): static + { + $this->book = $book; + + return $this; + } + + public function getChapter(): ?int + { + return $this->chapter; + } + + public function setChapter(int $chapter): static + { + $this->chapter = $chapter; + + return $this; + } + + public function getVerse(): ?int + { + return $this->verse; + } + + public function setVerse(int $verse): static + { + $this->verse = $verse; + + return $this; + } + + public function getContent(): ?string + { + return $this->content; + } + + public function setContent(string $content): static + { + $this->content = $content; + + return $this; + } + + public function getBookIndex(): ?int + { + return $this->book_index; + } + + public function setBookIndex(int $book_index): static + { + $this->book_index = $book_index; + + return $this; + } + + public function getLabel(): ?string + { + return $this->label; + } + + public function setLabel(?string $label): static + { + $this->label = $label; + + return $this; + } + + public static function findBook(string $reference): string + { + $book = ''; + $tmp = explode(' ', $reference); + $book = "{$tmp[0]}"; + + if(count($tmp) > 2) { + $book = "{$tmp[0]}{$tmp[1]}"; + } + + return $book; + } + + public static function findChapter(string $reference): string + { + $chapter = ''; + $tmp = explode(' ', $reference); + + if(count($tmp) > 2) { + $passage = $tmp[2]; + } else { + $passage = $tmp[1]; + } + + $tmp = explode(':', $passage); + $chapter = $tmp[0]; + + return $chapter; + } + + public static function findVerse(string $reference): int|array|bool + { + $verses = null; + $semicolon = strpos($reference, ':'); + if ($semicolon === false) { + return false; + } + + $tmp = substr($reference, $semicolon + 1); + + if(strpos($tmp, '-') !== false) { + $verses = explode('-', $tmp); + } else { + $verses = (int) $tmp; + } + + return $verses; + } +} diff --git a/src/Entity/Notes.php b/src/Entity/Notes.php index ce3ad0b..e42d9ed 100644 --- a/src/Entity/Notes.php +++ b/src/Entity/Notes.php @@ -10,8 +10,8 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: NotesRepository::class)] class Notes implements JsonSerializable { - #[ORM\Id] - #[ORM\Column] + #[ORM\Id()] + #[ORM\Column(type: Types::INTEGER)] private ?int $id = null; #[ORM\Column(length: 255)] @@ -30,11 +30,25 @@ class Notes implements JsonSerializable #[ORM\Column(type: Types::DATE_MUTABLE)] private ?\DateTimeInterface $date = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $passage = null; + + /** + * Retrieves the ID of the object. + * + * @return int|null The ID of the object, or null if it is not set. + */ public function getId(): ?int { return $this->id; } + /** + * Sets the ID of the object. + * + * @param int $id The ID to set. + * @return static + */ public function setId(int $id): static { $this->id = $id; @@ -42,11 +56,22 @@ class Notes implements JsonSerializable return $this; } + /** + * Retrieves the title of the object. + * + * @return string|null The title of the object, or null if it is not set. + */ public function getTitle(): ?string { return $this->title; } + /** + * Sets the title of the object. + * + * @param string $title The title to set. + * @return static + */ public function setTitle(string $title): static { $this->title = $title; @@ -54,6 +79,18 @@ class Notes implements JsonSerializable return $this; } + public function getDate(): ?\DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): static + { + $this->date = $date; + + return $this; + } + public function getSpeaker(): ?Speaker { return $this->speaker; @@ -90,6 +127,18 @@ class Notes implements JsonSerializable return $this; } + public function getPassage(): ?string + { + return $this->passage; + } + + public function setPassage(?string $passage): static + { + $this->passage = $passage; + + return $this; + } + public function jsonSerialize(): array { return [ @@ -98,18 +147,8 @@ class Notes implements JsonSerializable 'speaker' => $this->speaker, 'series' => $this->series, 'text' => $this->text, + 'passage' => $this->passage, + 'date' => $this->date->format('Y-m-d'), ]; } - - public function getDate(): ?\DateTimeInterface - { - return $this->date; - } - - public function setDate(\DateTimeInterface $date): static - { - $this->date = $date; - - return $this; - } } diff --git a/src/Entity/Reference.php b/src/Entity/Reference.php new file mode 100644 index 0000000..9a455a6 --- /dev/null +++ b/src/Entity/Reference.php @@ -0,0 +1,180 @@ +id; + } + + /** + * Getter method for type. + * + * @return string + */ + public function getType(): ?string + { + return $this->type; + } + + /** + * Setter method for type. + * + * @param string $type + * + * @return Reference + */ + public function setType(string $type): static + { + $this->type = $type; + + return $this; + } + + /** + * Getter method for name. + * + * @return string + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * Setter method for name. + * + * @param string $name + * + * @return Reference + */ + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + /** + * Getter method for label. + * + * @return string + */ + public function getLabel(): ?string + { + return $this->label; + } + + /** + * Setter method for label. + * + * @param string $label + * + * @return Reference + */ + public function setLabel(string $label): static + { + $this->label = $label; + + return $this; + } + + /** + * Getter method for ndx. + * + * @return int + */ + public function getNdx(): ?int + { + return $this->ndx; + } + + /** + * Setter method for ndx. + * + * @param int $ndx + * + * @return Reference + */ + public function setNdx(int $ndx): static + { + $this->ndx = $ndx; + + return $this; + } + + /** + * Getter method for content. + * + * @return string + */ + public function getContent(): ?string + { + return $this->content; + } + + + /** + * Setter method for content. + * + * @param string $content + * + * @return Reference + */ + public function setContent(string $content): static + { + $this->content = $content; + + return $this; + } + + /** + * @inheritDoc + * + * @codeCoverageIgnore + */ + public function jsonSerialize(): array + { + return [ + 'id' => $this->id, + 'type' => $this->type, + 'name' => $this->name, + 'label' => $this->label, + 'ndx' => $this->ndx, + 'text' => $this->content, + ]; + } +} diff --git a/src/Repository/BibleRepository.php b/src/Repository/BibleRepository.php new file mode 100644 index 0000000..8c767eb --- /dev/null +++ b/src/Repository/BibleRepository.php @@ -0,0 +1,63 @@ + + * + * @method Bible|null find($id, $lockMode = null, $lockVersion = null) + * @method Bible|null findOneBy(array $criteria, array $orderBy = null) + * @method Bible[] findAll() + * @method Bible[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class BibleRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Bible::class); + } + + public function findRange(Bible $bible, array $passage): array + { + return $this->createQueryBuilder('b') + ->andWhere('b.book = :book') + ->andWhere('b.chapter = :chapter') + ->andWhere('b.verse BETWEEN :verseStart AND :verseEnd') + ->setParameter('book', $bible->getBook()) + ->setParameter('chapter', $bible->getChapter()) + ->setParameter('verseStart', $passage[0]) + ->setParameter('verseEnd', $passage[1]) + ->getQuery() + ->getResult(); + } + + // /** + // * @return Bible[] Returns an array of Bible objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('b') + // ->andWhere('b.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('b.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Bible + // { + // return $this->createQueryBuilder('b') + // ->andWhere('b.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Repository/NotesRepository.php b/src/Repository/NotesRepository.php index 4219df3..4ceed1e 100644 --- a/src/Repository/NotesRepository.php +++ b/src/Repository/NotesRepository.php @@ -19,6 +19,43 @@ class NotesRepository extends ServiceEntityRepository public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Notes::class); + + } + + /** + * @return Notes + */ + public function findLastNote(): Notes + { + $qb = $this->createQueryBuilder('n') + ->orderBy('n.id', 'DESC') + ->setMaxResults(1); + $query = $qb->getQuery(); + $note = $query->getOneOrNullResult(); + + if (!$note) { + $note = new Notes(); + $note->setId(0); + } + + return $note; + } + + public function findNote($data): Notes + { + $qb = $this->createQueryBuilder('notes'); + $qb->select('n') + ->from('\App\Entity\Notes', 'n') + ->where("n.title LIKE ?1") + ->orWhere('n.passage LIKE ?1') + ->orWhere('n.text LIKE ?1') + ->setParameter(1, "%{$data}%") + ->setMaxResults(1); + + $query = $qb->getQuery(); + $res = $query->getSingleResult(); + + return $res; } // /** diff --git a/src/Repository/ReferenceRepository.php b/src/Repository/ReferenceRepository.php new file mode 100644 index 0000000..1ba46c0 --- /dev/null +++ b/src/Repository/ReferenceRepository.php @@ -0,0 +1,48 @@ + + * + * @method Reference|null find($id, $lockMode = null, $lockVersion = null) + * @method Reference|null findOneBy(array $criteria, array $orderBy = null) + * @method Reference[] findAll() + * @method Reference[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class ReferenceRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Reference::class); + } + + // /** + // * @return Reference[] Returns an array of Reference objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('r') + // ->andWhere('r.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('r.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Reference + // { + // return $this->createQueryBuilder('r') + // ->andWhere('r.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Repository/SeriesRepository.php b/src/Repository/SeriesRepository.php index d79102b..101fd0a 100644 --- a/src/Repository/SeriesRepository.php +++ b/src/Repository/SeriesRepository.php @@ -21,6 +21,11 @@ class SeriesRepository extends ServiceEntityRepository parent::__construct($registry, Series::class); } + public function findAll(): array + { + return $this->findBy([], ['name' => 'ASC']); + } + // /** // * @return Series[] Returns an array of Series objects // */ diff --git a/src/Repository/SpeakerRepository.php b/src/Repository/SpeakerRepository.php index 546a9b5..a720859 100644 --- a/src/Repository/SpeakerRepository.php +++ b/src/Repository/SpeakerRepository.php @@ -21,6 +21,11 @@ class SpeakerRepository extends ServiceEntityRepository parent::__construct($registry, Speaker::class); } + public function findAll(): array + { + return $this->findBy([], ['name' => 'ASC']); + } + // /** // * @return Speaker[] Returns an array of Speaker objects // */ diff --git a/templates/base.html.twig b/templates/base.html.twig index 5c8cda3..cca7ef7 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -3,7 +3,7 @@