From d3fa1733f4789f95dfd0c0735c735f2e05227eae Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Mon, 26 May 2025 13:00:12 -0600 Subject: [PATCH] implemented spaces, fixed login and signup, thought creation and space creation. --- components/core/foot.php | 75 +-- components/core/head.php | 1 + components/core/nav.php | 2 - components/user/login.php | 31 -- components/user/signup.php | 50 -- css/base.css | 351 +------------ css/dark.css | 85 +-- css/light.css | 80 +-- css/rewrite.css | 490 ++++++++++++++++-- director.php | 227 +++++++- functions/core.php | 6 +- functions/init.php | 9 +- functions/post.php | 81 +-- functions/space.php | 134 +++-- functions/user.php | 185 ++++--- index.php | 5 +- js/actionBar.js | 104 ++++ js/actionButton.js | 118 ----- js/dropdown.js | 47 ++ js/login.js | 93 ---- js/nav.js | 43 +- js/{ => old}/clickable_rows.js | 0 js/{ => old}/md_preview.js | 0 js/{ => old}/profile_picture_cropper_modal.js | 0 js/{ => old}/todo.js | 0 js/{ => old}/upload.js | 0 js/{ => old}/user_actions_modal.js | 0 js/override_browser_styles.js | 0 js/theme.js | 34 ++ js/user.js | 245 +++++++++ js/util.js | 21 + spaces.php | 114 ++-- 32 files changed, 1499 insertions(+), 1132 deletions(-) delete mode 100644 components/user/login.php delete mode 100644 components/user/signup.php create mode 100644 js/actionBar.js delete mode 100644 js/actionButton.js create mode 100644 js/dropdown.js delete mode 100644 js/login.js rename js/{ => old}/clickable_rows.js (100%) rename js/{ => old}/md_preview.js (100%) rename js/{ => old}/profile_picture_cropper_modal.js (100%) rename js/{ => old}/todo.js (100%) rename js/{ => old}/upload.js (100%) rename js/{ => old}/user_actions_modal.js (100%) delete mode 100644 js/override_browser_styles.js create mode 100644 js/theme.js create mode 100644 js/user.js create mode 100644 js/util.js diff --git a/components/core/foot.php b/components/core/foot.php index 704fa04..7f57b77 100644 --- a/components/core/foot.php +++ b/components/core/foot.php @@ -1,74 +1,17 @@ diff --git a/components/core/head.php b/components/core/head.php index 73ac6b6..af1e0b2 100644 --- a/components/core/head.php +++ b/components/core/head.php @@ -6,6 +6,7 @@ + '; diff --git a/components/core/nav.php b/components/core/nav.php index 94af34e..a0e43fd 100644 --- a/components/core/nav.php +++ b/components/core/nav.php @@ -55,8 +55,6 @@ $nav .= ' - -
diff --git a/components/user/login.php b/components/user/login.php deleted file mode 100644 index 5681f61..0000000 --- a/components/user/login.php +++ /dev/null @@ -1,31 +0,0 @@ - -

Log In

-
- - - - - - - - - - - - - ' . $error_msg . ' -
- - - - '; - - return $form; -} diff --git a/components/user/signup.php b/components/user/signup.php deleted file mode 100644 index 420466b..0000000 --- a/components/user/signup.php +++ /dev/null @@ -1,50 +0,0 @@ - -

Sign Up

-
- - - - - - - - - - - - - - - - - - - - - No file selected. - - - - - ' . $error_msg . ' -
- - - -
- -
- - '; - - return $form; -} diff --git a/css/base.css b/css/base.css index dcc366a..a6e3f6a 100644 --- a/css/base.css +++ b/css/base.css @@ -14,167 +14,11 @@ body { padding-bottom: 50px; } -h1, -h2, -h3, -h4, -h5, -h6, -p, -small, -label, -input, -input:-webkit-autofill, -input:-webkit-autofill:hover, -input:-webkit-autofill:focus, -textarea, -a, -.modal_button, -th, -td, -li, -select, -option, -span { - font-family: "Hack Nerd Font Mono", Hack, sans-serif; - padding: 0; - margin: 0; -} - -h1 { - font-size: 3vw; -} - -h2 { - font-size: 2.5vw; -} - -h3, -h3 a { - font-size: 2vw; -} - -h4, -h4 a { - font-size: 1.75vw; -} - -h5, -h5 a { - font-size: 1.5vw; -} - -h6, -h6 a { - font-size: 1.25vw; -} - -p, -a, -label, -li, -.modal_button, -.button, -span { - font-size: 1vw; -} - -p { - margin-bottom: 15px; -} - -small { - font-size: 0.75vw; -} - -a, -.modal_button { - color: orange; - transition-duration: 1s; -} - -a { - -webkit-tap-highlight-color: transparent !important; - outline: none !important; -} - -a.fill_div { - display: block; - height: 100%; - width: 100%; - text-decoration: none; -} - -nav { - display: flex; - flex-direction: row; - position: sticky; - width: 100%; - padding: 0; - margin: 0; - backdrop-filter: blur(50px); - z-index: 100; -} - -nav ul { - padding: 0; - margin: 0; - width: 100%; -} - -nav ul li { - display: inline-block; -} - -nav ul li a { - font-size: 2vw; - font-weight: 800; - - text-decoration: none; - display: block; - height: 100%; - width: 100%; - padding: 15px; - - transition-duration: 500ms; -} - -nav ul li a:hover { - color: orange; -} - -nav form { - padding: 15px; - - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -.nav_right_hand { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; -} - -.nav_right_hand * { - margin: 0px 15px 0px 15px; -} - code { padding: 1px; border-radius: 4px; } -.line { - height: 1px; - width: 100%; - margin-top: 5px; - margin-bottom: 5px; -} - .center-h { margin: 0 auto; } @@ -238,7 +82,7 @@ tbody tr { } tbody tr:hover { - outline: solid 3px orange; + outline: solid 3px red; } tbody tr td { @@ -271,15 +115,6 @@ table tr td label { justify-content: center; } -.button { - background-color: transparent; - padding: 15px; - text-decoration: none; - border-radius: 12px; - transition-duration: 1s; - border: 3px solid orange; -} - .narrow { width: 20%; } @@ -288,7 +123,7 @@ table tr td label { margin: 15px; padding: 10px; border-radius: 18px; - border: 2px solid orange; + border: 2px solid red; text-decoration: none; } @@ -307,11 +142,6 @@ table tr td label { border-radius: 50%; } -.link { - color: orange; - cursor: pointer; -} - .divLink { text-decoration: none; display: block; @@ -319,28 +149,6 @@ table tr td label { width: 100%; } -.underline { - text-decoration: underline; - text-decoration-thickness: 3px; - text-decoration-color: orange; -} - -i { - border: none; - background: transparent; - font-size: 1.5vw; - transition-duration: 500ms; -} - -#theme_toggle { - background: none; - border: none; -} - -i:hover { - color: orange; -} - .article { width: 60%; margin: 0 auto; @@ -359,115 +167,14 @@ i:hover { justify-content: left; } -input, -.autofilled, -input:-webkit-autofill, -textarea:-webkit-autofill, -input:-webkit-autofill:hover, -textarea:-webkit-autofill:hover, -input:-webkit-autofill:focus, -select, -option, -textarea { - /* Try this first */ - border-radius: 12px; - width: 100%; - padding: 15px; - margin: 15px 15px 15px 0px; - box-sizing: border-box; - transition-duration: 500ms; - resize: none; - background-color: transparent; -} - input[type="datetime_local"] { color: white; } -input:focus, -textarea:focus, -input:hover, -textarea:hover { - border: 3px solid orange; - outline: none; -} - -textarea { - border-radius: 18px; - height: 70%; -} - .checkboxes { height: max-content; } -input[type="radio"] { - display: none; -} - -.form_checkbox_container { - display: flex; - align-items: center; - justify-content: center; - position: relative; - padding-left: 35px; - margin: 15px 15px 15px 0px; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - - height: 29px; - width: max-content; -} - -.checkmark { - position: absolute; - top: 0; - left: 0; - height: 25px; - width: 25px; -} - -.form_checkbox_container input { - position: absolute; - opacity: 0; - cursor: pointer; - height: 0; - width: 0; -} - -/* Create the checkmark/indicator (hidden when not checked) */ -.checkmark:after { - content: ""; - position: absolute; - display: none; -} - -/* Show the checkmark when checked */ -.form_checkbox_container input:checked~.checkmark:after { - display: block; -} - -/* When the checkbox is checked, add a blue background */ -.form_checkbox_container input:checked~.checkmark { - background-color: orange; -} - -/* Style the checkmark/indicator */ -.form_checkbox_container .checkmark:after { - left: 9px; - top: 5px; - width: 5px; - height: 10px; - border: solid white; - border-width: 0 3px 3px 0; - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); -} - input[type="file"] { display: none; } @@ -567,7 +274,7 @@ input[type="file"] { .modal_close:hover, .modal_close:focus { - color: orange; + color: red; cursor: pointer; } @@ -591,7 +298,7 @@ input[type="file"] { height: 128px; width: 128px; - outline: dashed 4px orange; + outline: dashed 4px red; position: fixed; left: 50%; @@ -603,7 +310,7 @@ input[type="file"] { position: absolute; width: 10px; height: 10px; - background-color: orange; + background-color: red; border-radius: 50%; cursor: nwse-resize; /* Default cursor for resizing */ @@ -683,7 +390,7 @@ input[type="file"] { ::selection { color: black; - background: orange; + background: red; } .grid-2x1 { @@ -718,52 +425,6 @@ hr { margin: 15px 0px 15px 0px; } -::-webkit-scrollbar { - background-color: transparent; - border-radius: 18px; -} - -::-webkit-scrollbar-button { - display: none; -} - -::-webkit-scrollbar-track { - display: none; -} - -::-webkit-scrollbar-thumb { - background-color: orange; - border-radius: 9px; -} - -.dropdown { - position: relative; - display: block; - margin-right: 15px; - padding: 15px; - border-radius: 9px; -} - -.dropdown_content { - display: none; - position: absolute; - min-width: 50px; - padding: 15px; - z-index: 2; -} - -.dropdown:hover .dropdown_content { - display: block; - margin-left: -15px; - margin-top: 15px; - border-radius: 12px; -} - -.dropdown_content form * { - margin-top: 5px; - margin-bottom: 5px; -} - @media screen and (max-width: 992px) { body { padding-bottom: 160px; diff --git a/css/dark.css b/css/dark.css index 339e7aa..ad7264d 100644 --- a/css/dark.css +++ b/css/dark.css @@ -11,10 +11,12 @@ h2, h3, h4, h5, +h6, p, small, label, -span { +span, +i { color: white; } @@ -31,24 +33,9 @@ nav ul li a { color: white; } -code { - background-color: #2f2f2f; -} - .line { background-color: white; - border-top: 1px solid white; -} - -.article_card, -.form_card, -.view_card { - border: 5px solid white; - background-color: black; -} - -table tr { - outline: solid 3px white; + border-top: 2px solid white; } .button { @@ -57,21 +44,13 @@ table tr { .button:hover, .button:hover i { - color: orange; -} - -.tag { - color: white; + color: red; } #theme_toggle { color: white; } -.article ul li { - color: white; -} - input, textarea { background-color: transparent; @@ -95,60 +74,6 @@ select:-webkit-autofill { background-color: white; } -.upload_button { - border: 3px solid white; -} - -/* .form_card:hover, */ -/* .form_card:focus, */ -/* .article_card:hover, */ -/* .article_card:focus { */ -/* box-shadow: 0 2px 4px 0 white, 0 4px 8px 0 white; */ -/* } */ - -.modal { - background-color: black; - background-color: rgba(0, 0, 0, 0.9); -} - -.modal_close { - color: white; -} - -.user_actions { - border: 3px solid white; -} - -#floating_compose { - border: 2px solid white; -} - -#floating_compose, -#floating_compose * { - background-color: white; - color: black; - transition-duration: 500ms; -} - -#floating_compose:hover, -#floating_compose:hover * { - background-color: black; - color: orange; -} - -#floating_compose:hover { - border: 2px solid orange; -} - -.dropdown { - border: 2px solid white; -} - -.dropdown_content { - background-color: black; - border: 2px solid white; -} - .std_border, .med_border, .sm_border { diff --git a/css/light.css b/css/light.css index 2c2ffd9..e7fa57d 100644 --- a/css/light.css +++ b/css/light.css @@ -14,7 +14,8 @@ h5, p, small, label, -span { +span, +i { color: black; } @@ -37,18 +38,7 @@ code { .line { background-color: black; - border-top: 1px solid black; -} - -.article_card, -.form_card, -.view_card { - border: 5px solid black; - background-color: white; -} - -table tr { - outline: solid 3px black; + border-top: 2px solid black; } .button { @@ -57,21 +47,13 @@ table tr { .button:hover, .button:hover * { - color: orange; -} - -.tag { - color: black; + color: red; } #theme_toggle { color: black; } -.article ul li { - color: black; -} - input, textarea { background-color: transparent; @@ -95,60 +77,6 @@ select:-webkit-autofill { background-color: black; } -.upload_button { - border: 3px solid black; -} - -/* .form_card:hover, */ -/* .form_card:focus, */ -/* .article_card:hover, */ -/* .article_card:focus { */ -/* box-shadow: 0 2px 4px 0 black, 0 4px 8px 0 black; */ -/* } */ - -.modal { - background-color: white; - background-color: rgba(256, 256, 256, 0.9); -} - -.modal_close { - color: black; -} - -.user_actions { - border: 3px solid black; -} - -#floating_compose { - border: 2px solid black; -} - -#floating_compose, -#floating_compose * { - background-color: black; - color: white; - transition-duration: 500ms; -} - -#floating_compose:hover, -#floating_compose:hover * { - background-color: white; - color: orange; -} - -#floating_compose:hover { - border: 2px solid orange; -} - -.dropdown { - border: 2px solid black; -} - -.dropdown_content { - background-color: white; - border: 2px solid black; -} - .std_border, .med_border, .sm_border { diff --git a/css/rewrite.css b/css/rewrite.css index 9bce7cc..419a332 100644 --- a/css/rewrite.css +++ b/css/rewrite.css @@ -10,6 +10,232 @@ body { box-sizing: border-box; } +h1, +h2, +h3, +h4, +h5, +h6, +p, +small, +label, +input, +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +textarea, +a, +.modal_button, +th, +td, +li, +select, +option, +span { + font-family: "Hack Nerd Font Mono", Hack, sans-serif; + padding: 0; + margin: 0; +} + +h1 { + font-size: 3vw; +} + +h2 { + font-size: 2.5vw; +} + +h3, +h3 a { + font-size: 2vw; +} + +h4, +h4 a { + font-size: 1.75vw; +} + +h5, +h5 a { + font-size: 1.5vw; +} + +h6, +h6 a { + font-size: 1.25vw; +} + +p, +a, +label, +li, +.modal_button, +.button, +span { + font-size: 1vw; +} + +small { + font-size: 0.75vw; +} + +a, +.modal_button { + color: red; + transition-duration: 1s; +} + +a { + -webkit-tap-highlight-color: transparent !important; + outline: none !important; +} + +a.fillDiv { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; +} + +.button { + background-color: transparent; + padding: 15px; + text-decoration: none; + border-radius: 12px; + transition-duration: 1s; + border: 3px solid red; +} + +.link { + cursor: pointer; +} + +.underline { + text-decoration: underline; + text-decoration-thickness: 3px; + text-decoration-color: red; +} + +i { + border: none; + background: transparent; + font-size: 1.5vw; + transition-duration: 500ms; +} + +#theme_toggle { + background: none; + border: none; +} + +i:hover { + color: red; +} + +/********** Forms ************/ + +input, +.autofilled, +input:-webkit-autofill, +textarea:-webkit-autofill, +input:-webkit-autofill:hover, +textarea:-webkit-autofill:hover, +input:-webkit-autofill:focus, +select, +option, +textarea { + /* Try this first */ + border-radius: 12px; + width: 100%; + padding: 15px; + margin: 15px 15px 15px 0px; + box-sizing: border-box; + transition-duration: 500ms; + resize: none; + background-color: transparent; +} + +input:focus, +textarea:focus, +input:hover, +textarea:hover { + border: 3px solid red; + outline: none; +} + +textarea { + border-radius: 18px; + height: 70%; +} + +input[type="radio"] { + display: none; +} + +.form_checkbox_container { + display: flex; + align-items: center; + justify-content: center; + position: relative; + padding-left: 35px; + margin: 15px 15px 15px 0px; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + height: 29px; + width: max-content; +} + +.checkmark { + position: absolute; + top: 0; + left: 0; + height: 25px; + width: 25px; +} + +.form_checkbox_container input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} + +/* Create the checkmark/indicator (hidden when not checked) */ +.checkmark:after { + content: ""; + position: absolute; + display: none; +} + +/* Show the checkmark when checked */ +.form_checkbox_container input:checked ~ .checkmark:after { + display: block; +} + +/* When the checkbox is checked, add a blue background */ +.form_checkbox_container input:checked ~ .checkmark { + background-color: red; +} + +/* Style the checkmark/indicator */ +.form_checkbox_container .checkmark:after { + left: 9px; + top: 5px; + width: 5px; + height: 10px; + border: solid white; + border-width: 0 3px 3px 0; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} + /********** Width & Sizing **********/ .full_width { width: 100%; @@ -50,6 +276,11 @@ body { border: 1px solid red; } +.line { + margin-top: 5px; + margin-bottom: 5px; +} + /********** Rows & Columns **********/ .row { display: flex; @@ -71,6 +302,10 @@ body { justify-content: center; } +.rel { + position: relative; +} + /********** Margins & Padding **********/ .margin { margin: 15px; @@ -124,6 +359,83 @@ body { padding-left: 15px; } +/*********** Nav **********/ +nav { + display: flex; + flex-direction: row; + position: sticky; + width: 100%; + padding: 0; + margin: 0; + backdrop-filter: blur(50px); + z-index: 100; +} + +nav ul { + padding: 0; + margin: 0; + width: 100%; +} + +nav ul li { + display: inline-block; +} + +nav ul li a { + font-size: 2vw; + font-weight: 800; + + text-decoration: none; + display: block; + height: 100%; + width: 100%; + padding: 15px; + + transition-duration: 500ms; +} + +nav ul li a:hover { + color: red; +} + +nav form { + padding: 15px; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.nav_right_hand { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.nav_right_hand * { + margin: 0px 15px 0px 15px; +} + +/*********** Dropdowns **********/ +.dropdown { + position: relative; + display: inline-block; + z-index: 0; +} + +.dropdownContent { + display: none; + position: absolute; + right: 0; +} + +/******* Space Selector *********/ +#postSpaces { + overflow: hidden; +} + /************ Modals ************/ #stdModal { position: absolute; @@ -136,55 +448,25 @@ body { justify-content: center; align-items: center; + + z-index: 10; } -#stdModal>* {} - -/******** Action Buttons ********/ -.circleLayout { - width: 350px; - /* Adjust as needed */ - height: 350px; - border-radius: 50%; - /* Important for positioning children */ - position: fixed; - bottom: 50px; - right: 50px; - overflow: hidden; +#stdModal > * { } -.actionButton { +.glass { + background-color: transparent; + backdrop-filter: blur(5px); +} + +#actionBar { position: absolute; - - border-radius: 50%; - width: 75px; - height: 75px; - background-color: orange; - color: black; -} - -#actionButton { - top: 50%; + bottom: 3.5%; left: 50%; - transform: translate(-50%, -50%); -} + transform: translate(-50%, -5%); -#newPost { - top: 50%; - left: 0%; - transform: translate(0%, -50%); -} - -#newSpace { - top: 15%; - left: 15%; - transform: translate(-15%, -15%); -} - -#filterSort { - top: 0%; - left: 50%; - transform: translate(-50%, 0%); + z-index: 10; } /********** Animations **********/ @@ -194,11 +476,11 @@ body { } ::view-transition-old(root) { - animation: fade-out 750ms forwards; + animation: fade-out 1s forwards; } ::view-transition-new(root) { - animation: fade-in 750ms forwards; + animation: fade-in 1s forwards; } @keyframes fade-in { @@ -220,3 +502,123 @@ body { opacity: 0; } } + +/********** Misc. **********/ +::-webkit-scrollbar { + background-color: transparent; + border-radius: 18px; + width: 10px; +} + +::-webkit-scrollbar-button { + display: none; +} + +::-webkit-scrollbar-track { + display: none; +} + +::-webkit-scrollbar-thumb { + background-color: red; + border-radius: 9px; +} + +#backButton { + position: absolute; + top: 11%; + left: 8%; + width: 100px; + height: 100px; +} + +#backButton:hover > i { + color: red; +} + +/********** Mobile **********/ +@media screen and (max-width: 1200px) { + h1 { + font-size: 6vw; + } + + h2 { + font-size: 5vw; + } + + h3 { + font-size: 4vw; + } + + h4, + h5, + h6 { + font-size: 3.5vw; + } + + p, + a, + nav ul li a, + li, + i, + span, + input, + label, + input[type="submit"] { + font-size: 2.5vw; + } + + .std_width, + .stdWidth { + width: 95%; + } + + .med_width, + .medWidth { + width: 70%; + } + + nav { + position: fixed; + bottom: -2px; + width: 100%; + margin: 0; + padding: 25px 0px 25px 0px; + backdrop-filter: blur(50px); + } + + nav ul li a { + font-size: 3vw; + } + + .site_navigation ul { + display: flex; + flex-direction: row-reverse; + } + + nav .row_space_between .no_margin { + flex-direction: row-reverse; + } + + .nav_right_hand { + flex-direction: row-reverse; + align-items: center; + justify-content: center; + } + + .nav_right_hand * { + margin: 0px 0px 0px 15px; + } + + #backButton { + display: none; + } + + #actionBar { + bottom: 125px; + } + + .modalContent { + max-height: 600px; + overflow-y: auto; + } +} diff --git a/director.php b/director.php index fb2d517..d5aee47 100644 --- a/director.php +++ b/director.php @@ -7,41 +7,218 @@ if (isset($_POST['ajaxId'])) { case 'login': handle_login(); break; + case 'loginHTML': + handleLoginHTML(); + break; + case 'signupHTML': + handleSignupHTML(); + break; + case 'usernameCheck': + handleUsernameCheck(); + break; case 'logout': User::logout(); break; case 'newPostHTML': handleNewPostHTML(); break; - case 'newThought': - handleNewThought(); + case 'newSpaceHTML': + handleNewSpaceHTML(); + break; + case 'deletePost': + handleDeletePost(); + break; + case 'deleteSpace': + handleDeleteSpace(); + break; + } +} else if (isset($_POST['formID'])) { + switch ($_POST['formID']) { + case 'spaceNew': + break; + case 'signup': + handleSignup(); + break; + case 'newPost': + handleNewPost(); + break; + case 'newSpace': + handleNewSpace(); break; } -} else { - $response = [ - 'error' => pretty_dump($_POST), - ]; - echo json_encode($response); + if (!isset($_POST['redirect'])) + header('Location: .'); + else + header('Location: ' . $_POST['redirect']); } -function handleNewThought() +function handleDeleteSpace() { - $thought = new Thought(Space::retrieveFromDB($_POST['space']), null, unserialize($_SESSION['user']), 3, 2, urldecode($_POST['thought'])); - echo json_encode(['log' => 'success']); + if (!isset($_POST['id'])) { + echo json_encode(['error' => 'Missing required Space ID.']); + return; + } + + $stmt = 'DELETE FROM spaceMembers where space = ?'; + exec_stmt($stmt, 'i', $_POST['id']); + + $stmt = 'DELETE FROM space WHERE id = ?'; + exec_stmt($stmt, 'i', $_POST['id']); +} + +function handleNewSpace() +{ + if (!isset($_POST['name']) || !isset($_POST['description']) || !isset($_POST['visibility'])) { + error_log('ruh roh'); + return; + } + + $id = randomId(2) * -1; + /* $name = htmlspecialchars($_POST['name'], ENT_QUOTES, 'UTF-8'); */ + /* $description = htmlspecialchars($_POST['description'], ENT_QUOTES, 'UTF-8'); */ + $space = new Space($id, $_POST['name'], $_POST['description'], $_POST['visibility'], null, null, isset($_POST['space']) ? Space::retrieveFromDB($_POST['space']) : null, true); + error_log(pretty_dump($space)); + Space::addUserToSpace(unserialize($_SESSION['user'])->getId(), $id * -1, 1, true); +} + +function handleNewPost() +{ + if (!isset($_POST['postType']) || !isset($_POST['space'])) + return; + + switch ($_POST['postType']) { + case 'Thought': + if (!isset($_POST['thought'])) + return; + $id = randomId(1) * -1; + new Thought(Space::retrieveFromDB($_POST['space']), $id, unserialize($_SESSION['user']), 3, 2, urldecode($_POST['thought'])); + break; + case 'Moment': + break; + case 'Essay': + new Essay(Space::retrieveFromDB($_POST['space']), null, unserialize($_SESSION['user']), 3, 2, $_POST['title'], $_POST['markdown'], $_POST['excerpt']); + break; + } +} + +function handleSignup() +{ + if (!isset($_POST['username']) || !isset($_POST['password']) || !isset($_POST['vPassword'])) + return; + + if ($_POST['password'] != $_POST['vPassword']) + return; + + $_SESSION['user'] = serialize(new User(randomId(0) * -1, null, $_POST['username'], null, null, null, 3, password_hash($_POST['password'], PASSWORD_DEFAULT))); + header('Location: .'); +} + +function handleUsernameCheck() +{ + if (!isset($_POST['username'])) + return; + + echo json_encode(['exists' => User::exists($_POST['username'])]); +} + +function handleSignupHTML() +{ + if (isset($_SESSION['user'])) + header('Location: .'); + + echo json_encode(['html' => User::getSignupHTML()]); +} + +function handleLoginHTML() +{ + if (isset($_SESSION['user'])) + header('Location: .'); + + echo json_encode(['html' => User::getLoginHTML()]); +} + +function handleDeletePost() +{ + if (!isset($_POST['id'])) { + echo json_encode(['error' => 'Missing required Post ID.']); + return; + } + + $stmt = 'DELETE FROM post WHERE id = ?'; + exec_stmt($stmt, 'i', $_POST['id']); +} + +function handleNewSpaceHTML() +{ + $user = unserialize($_SESSION['user']); + + $html = ' +
+

New Space

+
+ + + + + + '; + + $spaces = $user->getAllSpaces(); + foreach ($spaces as $s) { + $html .= ' + + '; + } + + $html .= ' + +
+
+ '; + + echo json_encode(['html' => $html]); +} + +function getSpaceIcon($space) +{ + switch ($space->getVisibility()) { + case 1: + return 'nf-cod-globe'; + case 2: + return 'nf-fa-group'; + case 3: + case 4: + return 'nf-fa-user_group'; + case 5: + return 'nf-fa-lock'; + } } function handleNewPostHTML() { + $user = unserialize($_SESSION['user']); + // TODO: Need to plumb this into the DB via AJAX. // TODO: Need to store the current space in SESSION and use that to auto-select a Space // TODO: List the user's spaces or let them search for a space or quick select a favorited space $html = ' -
+

New Thought

-
- + + + +
@@ -57,6 +234,22 @@ function handleNewPostHTML()
+
+ '; + + $spaces = $user->getAllSpaces(); + foreach ($spaces as $s) { + $html .= ' + + '; + } + + $html .= ' +
+
@@ -93,20 +286,18 @@ function handle_login() // Serialize the user object and store it in the session $_SESSION['user'] = serialize($user); - // You might want to store specific user data instead of the whole serialized object, - // e.g., $_SESSION['user_id'] = $user->getId(); $_SESSION['username'] = $user->getUsername(); + $spaces = $user->getSpaces(); + foreach ($spaces as $s) + $_SESSION['spaces'][] = serialize($s); } else { $response['status'] = 'error'; $response['message'] = 'Incorrect username and/or password.'; } } catch (Exception $e) { - // Catch potential exceptions from LoginCredentials or User::login() - // Log the detailed error for server-side debugging error_log('Login Error: ' . $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine()); $response['status'] = 'error'; $response['message'] = 'An internal error occurred. Please try again later.'; - // Optionally, for development, you could include $e->getMessage() but not in production. } } else { $response['status'] = 'error'; diff --git a/functions/core.php b/functions/core.php index 0c89a4c..8742eb1 100644 --- a/functions/core.php +++ b/functions/core.php @@ -23,9 +23,11 @@ function randomId(int $mode) return randomId($mode); else return $id; - break; case 2: - break; + if (Space::exists($id)) + return randomId($mode); + else + return $id; } } diff --git a/functions/init.php b/functions/init.php index 67ee350..b89ed64 100644 --- a/functions/init.php +++ b/functions/init.php @@ -12,14 +12,13 @@ require_once 'functions/user.php'; require_once 'functions/post.php'; require_once 'functions/space.php'; -/**/ // Set SESSION variable. if (!isset($_SESSION['initialized'])) { - $_SESSION['initialized'] = true; - $_SESSION['theme'] = 'dark'; + $_SESSION['initialized'] = true; + $_SESSION['theme'] = 'dark'; } else if (isset($_POST['theme'])) { - $_SESSION['theme'] = $_POST['theme']; - unset($_POST['theme']); + $_SESSION['theme'] = $_POST['theme']; + unset($_POST['theme']); } define('owner', 1); diff --git a/functions/post.php b/functions/post.php index 229157b..8519d43 100644 --- a/functions/post.php +++ b/functions/post.php @@ -60,10 +60,6 @@ class Post $result = exec_stmt($stmt, 'i', $id)->fetch_assoc(); if ($result['count(id)'] == 1) return true; - else if ($result['count(id)'] > 1) { - // TODO: Add additional logging here, since that means multiple posts have the same ID. - return true; - } return false; } @@ -80,11 +76,10 @@ class Post return new Essay(null, $id, $creator, 1, $post['status'], $post['title'], $post['markdown'], $post['excerpt']); case 3: return new Thought(null, $post['id'], $creator, 3, $post['status'], $post['thought']); - default: - return null; } - } else - return null; + } + + return null; } public function setId(int $id): void @@ -92,11 +87,6 @@ class Post $this->id = $id; } - public function authUser(User $user): bool - { - return true; - } - public function getId(): int { return $this->id; @@ -149,6 +139,10 @@ class Post public function getSpace(): ?Space { + if (!isset($this->space)) { + $stmt = 'SELECT space from spacePosts WHERE post = ?'; + $rawSpace = exec_stmt($stmt, 'i', $this->id)->fetch_assoc(); + } return $this->space; } } @@ -156,21 +150,17 @@ class Post class Essay extends Post { private string $title; - private string $html; + private string $markdown; private string $excerpt; /* private array $files; */ public function __construct(?Space $space = null, ?int $id = null, User $creator, int $postType, int $status, string $title, string $markdown, string $excerpt) { parent::__construct($space, $id, $creator, 1, $postType, $status); - require_once 'vendor/autoload.php'; - - $parsedown = new Parsedown(); - $parsedown->setSafeMode(true); $this->title = $title; $this->excerpt = $excerpt; - $this->html = $parsedown->text($markdown); + $this->markdown = $markdown; } public function getCardHTML(): ?string @@ -184,8 +174,8 @@ class Essay extends Post

' . $this->title . '

-
- Something +
+
@@ -198,14 +188,19 @@ class Essay extends Post return $html; } + public function allowUser(User $user): bool + { + return parent::getSpace()->allowUser($user); + } + public function getTitle(): ?string { return $this->title; } - public function getHTML(): ?string + public function getMarkdown(): ?string { - return $this->html; + return $this->markdown; } public function getExcerpt(): ?string @@ -215,16 +210,18 @@ class Essay extends Post public function setMarkdown(?string $markdown): void { - require_once 'vendor/autoload.php'; - $parsedown = new Parsedown(); - $parsedown->setSafeMode(true); - $this->html = $parsedown->text($markdown); + $this->markdown = $markdown; } public function setExcerpt(?string $excerpt): void { $this->excerpt = $excerpt; } + + public function getSpace(): ?Space + { + return parent::getSpace(); + } } class Moment extends Post @@ -269,27 +266,41 @@ class Thought extends Post parent::__construct($space, $id, $creator, $postType, $status); $this->thought = $thought; - if ($id == null) { + if ($id < 0) { $stmt = 'INSERT INTO post (id, parentSpace, creator, postType, status, thought) VALUES (?, ?, ?, ?, ?, ?)'; - $newID = randomId(1); - parent::setId($newID); + parent::setId($id * -1); $space = isset($space) ? $space->getId() : $creator->getHomeSpace()->getId(); - exec_stmt($stmt, 'iiiiis', $newID, $space, $creator->getId(), 3, 2, $this->thought); + exec_stmt($stmt, 'iiiiis', $id * -1, $space, $creator->getId(), 3, 2, $this->thought); $stmt = 'INSERT INTO spacePosts (post, space) VALUES (?, ?)'; - exec_stmt($stmt, 'ii', $newID, $space); + exec_stmt($stmt, 'ii', $id * -1, $space); } } public function getCardHTML(): ?string { $html = ' -
+
@' . parent::getCreator()->getUsername() . ' -
- Something + + '; + + if (isset($_SESSION['user'])) { + $user = unserialize($_SESSION['user']); + if ($user->getId() == parent::getCreator()->getId()) { + $html .= ' + + '; + } + } + + $html .= '
diff --git a/functions/space.php b/functions/space.php index e9731dd..630072b 100644 --- a/functions/space.php +++ b/functions/space.php @@ -10,13 +10,12 @@ class Space private string $name; private string $description; private int $visibility; - private array $members; - private array $posts; + private ?array $members; + private ?array $posts; private ?Space $parent; - public function __construct(?int $id, string $name, string $description, int $visibility, array $members, array $posts, ?Space $parent = null) + public function __construct(int $id, string $name, string $description, int $visibility, ?array $members, ?array $posts, ?Space $parent = null, bool $favorited = false) { - $this->id = $id ?? randomId(2); $this->name = $name; $this->description = $description; $this->visibility = $visibility; @@ -24,33 +23,101 @@ class Space $this->posts[] = $posts; $this->parent = $parent; - /* $stmt = 'INSERT INTO spaces (spaceId, name, description, parentSpaceIdk)'; */ + if ($id < 0) { + $this->id = $id * -1; + error_log('ID: ' . $this->id); + + $stmt = 'INSERT INTO space (id, name, description, visibility, parentSpace) VALUES (?, ?, ?, ?, ?)'; + exec_stmt($stmt, 'issii', $this->id, $name, $description, $visibility, (isset($parent) ? $parent->getId() : null)); + error_log('Added space in DB'); + } else + $this->id = $id; + } + + public static function exists(int $id): bool + { + $stmt = 'SELECT count(id) FROM space WHERE id = ?'; + $result = exec_stmt($stmt, 'i', $id)->fetch_assoc(); + + if ($result['count(id)'] == 1) + return true; + else if ($result['count(id)'] > 1) + return true; + else + return false; + + return true; + } + + public static function addUserToSpace(int $user, int $space, int $role = 3, bool $favorited = false) + { + $stmt = 'INSERT INTO spaceMembers (user, space, role, favorited) VALUES (?, ?, ?, ?)'; + exec_stmt($stmt, 'iiii', $user, $space, $role, $favorited); + } + + public function getSubSpaces(): ?array + { + $stmt = 'SELECT id FROM space WHERE parentSpace = ?'; + $rawSubSpaces = exec_stmt($stmt, 'i', $this->id); + + $subSpaces = []; + foreach ($rawSubSpaces as $ss) { + $subSpaces[] = Space::retrieveFromDB($ss['id']); + } + + return $subSpaces; + } + + public function getParentSpace(): ?Space + { + return $this->parent; } public function getCardHTML(): string { $html = ' -
- +
+

' . $this->name . '

-
- Something + '; + + if (isset($_SESSION['user'])) { + $user = unserialize($_SESSION['user']); + if ($user->getId() == $this->getOwner()->getId()) { + $html .= ' + + '; + } + } + + $html .= ' +

' . $this->description . '

-
'; return $html; } + public function getOwner(): User + { + $stmt = 'SELECT user FROM spaceMembers WHERE space = ? AND role = ?'; + $user = exec_stmt($stmt, 'ii', $this->id, 1)->fetch_assoc(); + + return User::retrieveFromDB($user['user']); + } + public static function retrieveFromDB(int $space, int $user = -1): ?Space { if ($user != -1) { @@ -68,41 +135,32 @@ class Space $stmt = 'SELECT * FROM space WHERE id = ?'; $rawSpace = exec_stmt($stmt, 'i', $space)->fetch_assoc(); - if (!empty($rawSpace['parentSpace'])) - $parent = Space::retrieveFromDB($rawSpace['parentSpace'], $user); - else - $parent = null; + if ($rawSpace) { + if (!empty($rawSpace['parentSpace'])) + $parent = Space::retrieveFromDB($rawSpace['parentSpace'], $user); + else + $parent = null; - $stmt = 'SELECT user FROM spaceMembers WHERE space = ?'; - $members = []; - $rawMembers = exec_stmt($stmt, 'i', $rawSpace['id']); - foreach ($rawMembers as $m) { - $members[] = User::retrieveFromDB($m['user']); - } + $stmt = 'SELECT user FROM spaceMembers WHERE space = ?'; + $members = []; + $rawMembers = exec_stmt($stmt, 'i', $rawSpace['id']); + foreach ($rawMembers as $m) + $members[] = User::retrieveFromDB($m['user']); - $stmt = 'SELECT post FROM spacePosts WHERE space = ?'; - $posts = []; - $rawPosts = exec_stmt($stmt, 'i', $rawSpace['id']); - while ($row = $rawPosts->fetch_assoc()) { - $posts[] = Post::retrieveFromDB($row['post']); - } + $stmt = 'SELECT post FROM spacePosts INNER JOIN post ON spacePosts.post = post.id WHERE space = ? ORDER BY post.createdAt DESC'; + $posts = []; + $rawPosts = exec_stmt($stmt, 'i', $rawSpace['id']); + while ($row = $rawPosts->fetch_assoc()) + $posts[] = Post::retrieveFromDB($row['post']); - $space = new Space($rawSpace['id'], $rawSpace['name'], $rawSpace['description'], $rawSpace['visibility'], $members, $posts, $parent); - - foreach ($posts as $p) - $p->setSpace($space); - - return $space; + return new Space($rawSpace['id'], $rawSpace['name'], $rawSpace['description'], $rawSpace['visibility'], $members, $posts, $parent); + } else + return null; } public function allowUser(User $user) { - $stmt = 'SELECT space FROM spaceMembers WHERE user = ?'; - $result = exec_stmt($stmt, 'i', $user->getId())->fetch_assoc(); - if ($result['space'] == $this->id) - return true; - else - return false; + return in_array($user, $this->members); } public function getPosts() diff --git a/functions/user.php b/functions/user.php index b0ed817..999e565 100644 --- a/functions/user.php +++ b/functions/user.php @@ -33,15 +33,13 @@ class User private int $id; private ?array $spaces; private string $username; - private ?DateTime $createdAt; - private ?DateTime $lastActive; private bool $isActive; private ?string $profilePicture; private ?string $bio; private ?string $website; private int $role; - public function __construct(int $id = -1, ?array $spaces, string $username, ?string $profilePicture = null, ?string $bio = null, ?string $website = null, int $role = 3) + public function __construct(int $id = -1, ?array $spaces, string $username, ?string $profilePicture = null, ?string $bio = null, ?string $website = null, int $role = 3, ?string $passwordHash) { // Basic XSS prevention on construction (can be enhanced) $username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8'); @@ -50,77 +48,81 @@ class User $profilePicture = htmlspecialchars($profilePicture ?? 'default-profile.png', ENT_QUOTES, 'UTF-8'); // Basic SQL injection prevention (should primarily rely on prepared statements) - if (preg_match('/[\'";\-\_]/', $username) || preg_match('/[\'";\-\_]/', $bio) || preg_match('/[\'";\-\_]/', $website) || preg_match('/[\'";\-\_]/', $profilePicture)) { - throw new InvalidArgumentException('Invalid characters in user data.'); - } + /* if (preg_match('/[\'";\-\_]/', $username) || preg_match('/[\'";\-\_]/', $bio) || preg_match('/[\'";\-\_]/', $website) || preg_match('/[\'";\-\_]/', $profilePicture)) { */ + /* throw new InvalidArgumentException('Invalid characters in user data.'); */ + /* } */ $this->username = $username; $this->profilePicture = $profilePicture; $this->bio = $bio; $this->website = $website; - $this->createdAt = new DateTime(); - $this->lastActive = null; $this->isActive = false; $this->role = $role; $this->spaces = null; - if ($id == -1) { - $this->id = randomId(0); - $stmt = 'INSERT INTO user (userId, username, profilePicture, bio, website) VALUES (?, ?, ?, ?, ?)'; - exec_stmt($stmt, 'issss', $this->id, $this->username, $this->profilePicture, $this->bio, $this->website); + if ($id < 0) { + $this->id = $id * -1; + $stmt = 'INSERT INTO user (id, username, passwordHash, profilePicture, bio, website) VALUES (?, ?, ?, ?, ?, ?)'; + exec_stmt($stmt, 'isssss', $this->id, $this->username, $passwordHash, $this->profilePicture, $this->bio, $this->website); + + Space::addUserToSpace($this->id, 2025); + new Space($id, $this->username, $this->username . "'s private Space.", 5, [$this], [], null, true); } else $this->id = $id; } - public static function exists(int $id): bool + public static function exists($identifier): bool { - $stmt = 'SELECT count(id) FROM user WHERE id = ?'; - $result = exec_stmt($stmt, 'i', $id)->fetch_assoc(); + if (is_int($identifier)) { + $stmt = 'SELECT count(id) FROM user WHERE id = ?'; + $result = exec_stmt($stmt, 'i', $identifier)->fetch_assoc(); - if ($result['count(id)'] == 1) - return true; - else if ($result['count(id)'] > 1) - return true; - else - return false; + if ($result['count(id)'] == 1) + return true; + else if ($result['count(id)'] > 1) + return true; + else + return false; + } else if (is_string($identifier)) { + $stmt = 'SELECT count(id) FROM user WHERE username = ?'; + $result = exec_stmt($stmt, 's', $identifier)->fetch_assoc(); + + if ($result['count(id)'] == 1) + return true; + else if ($result['count(id)'] > 1) + return true; + else + return false; + } + + return true; } public static function retrieveFromDB(int $userId): ?User { - $stmt = 'SELECT id, username, profilePicture, bio, website FROM user WHERE id = ?'; + $stmt = 'SELECT id, username, profilePicture, bio, website, role FROM user WHERE id = ?'; $user = exec_stmt($stmt, 'i', $userId)->fetch_assoc(); - if ($user) { - return new User($user['id'], null, $user['username'], $user['profilePicture'], $user['bio'], $user['website']); - } else { + if ($user) + return new User($user['id'], null, $user['username'], $user['profilePicture'], $user['bio'], $user['website'], $user['role'], null); + else return null; - } } public static function getSignupHTML() { - $html = ' -
-

Sign Up

+ $experimentalHTML = '
- + + + - - - - - - - - - - No file selected. @@ -129,17 +131,28 @@ class User + '; + + $html = ' +
+

Sign Up

+
+ + + + + + + + + + + - - -
- -
+
'; @@ -148,28 +161,30 @@ class User public static function getLoginHTML() { - $html = ' -
-

Log In

-
- - + $stayCheckedIn = ' + + '; - + $html = ' +
+

Log In

+
+ + + + - - - +
'; @@ -183,8 +198,17 @@ class User if (!password_verify($credentials->getPassword(), $user['passwordHash'])) return null; - else - return new User($user['id'], null, $user['username'], $user['profilePicture'], $user['bio'], $user['website']); + else { + $spaces = []; + $stmt = 'SELECT space FROM spaceMembers WHERE user = ?'; + $rawSpaces = exec_stmt($stmt, 'i', $user['id'])->fetch_assoc(); + + if ($rawSpaces) + foreach ($rawSpaces as $s) + $spaces[] = serialize(Space::retrieveFromDB($s)); + + return new User($user['id'], $spaces, $user['username'], $user['profilePicture'], $user['bio'], $user['website'], $user['role'], null); + } } public static function logout(): void @@ -205,14 +229,26 @@ class User public function getSpaces() { - $stmt = 'SELECT * FROM spaceMembers WHERE user = ?'; - $spaces = []; + $stmt = 'SELECT space FROM spaceMembers WHERE user = ?'; $rawSpaces = exec_stmt($stmt, 'i', $this->id); - while ($row = $rawSpaces->fetch_assoc()) { - $spaces[] = Space::retrieveFromDB($row['space'], $row['user']); + $spaces = []; + while ($r = $rawSpaces->fetch_assoc()) { + $space = Space::retrieveFromDB($r['space']); + if (!$space->getParentSpace()) + $spaces[] = $space; } - $this->spaces = $spaces; + return $spaces; + } + + public function getAllSpaces() + { + $stmt = 'SELECT space FROM spaceMembers WHERE user = ?'; + $rawSpaces = exec_stmt($stmt, 'i', $this->id); + $spaces = []; + while ($r = $rawSpaces->fetch_assoc()) + $spaces[] = Space::retrieveFromDB($r['space']); + return $spaces; } @@ -231,16 +267,6 @@ class User return $this->username; } - public function getCreatedAt(): ?DateTime - { - return $this->createdAt; - } - - public function getLastActive(): ?DateTime - { - return $this->lastActive; - } - public function isActive(): bool { return $this->isActive; @@ -261,11 +287,6 @@ class User return $this->website; } - public function setLastActive(?DateTime $lastActive): void - { - $this->lastActive = $lastActive; - } - public function setIsActive(bool $isActive): void { $this->isActive = $isActive; diff --git a/index.php b/index.php index 87ae9ba..253175c 100644 --- a/index.php +++ b/index.php @@ -1,2 +1,5 @@ { + document.querySelector("#primaryActions").style.display = "none"; + document.querySelector("#spaceActions").style.display = "flex"; + + document.querySelector("#spaceBack").addEventListener("click", () => { + document.querySelector("#primaryActions").style.display = "flex"; + document.querySelector("#spaceActions").style.display = "none"; + }); + + let home = document.querySelector("#spaceHome"); + home.addEventListener("click", () => { + applyTransition(home.getAttribute("href")); + }); + + document.querySelector("#spaceNew").addEventListener("click", () => { + fetch("director.php", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "ajaxId=newSpaceHTML", + }) + .then((response) => { + return response.json(); + }) + .then((data) => { + openModal(data["html"]); + document.querySelector("#redirect").value = window.location.href; + }); + }); +}); + +actionPost.addEventListener("click", () => { + document.querySelector("#primaryActions").style.display = "none"; + document.querySelector("#postActions").style.display = "flex"; + + document.querySelector("#postBack").addEventListener("click", () => { + document.querySelector("#primaryActions").style.display = "flex"; + document.querySelector("#postActions").style.display = "none"; + }); + + document.querySelector("#postNew").addEventListener("click", (e) => { + fetch("director.php", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "ajaxId=newPostHTML", + }) + .then((response) => { + return response.json(); + }) + .then((data) => { + openModal(data["html"]); + + document.querySelector("#redirect").value = window.location.href; + + let newNavThought = document.querySelector("#newNavThought"); + let newThought = document.querySelector("#newThought"); + let newNavMoment = document.querySelector("#newNavMoment"); + let newMoment = document.querySelector("#newMoment"); + let newNavEssay = document.querySelector("#newNavEssay"); + let newEssay = document.querySelector("#newEssay"); + let newPostLabel = document.querySelector("#newPostLabel"); + let postType = document.querySelector("#postType"); + + let share = document.querySelector("#share"); + + newNavThought.addEventListener("click", () => { + newMoment.style.display = "none"; + newEssay.style.display = "none"; + + newThought.style.display = "flex"; + newPostLabel.innerText = "New Thought"; + newThought.querySelector('input[type="text"]').focus(); + postType.value = "Thought"; + }); + + newNavMoment.addEventListener("click", () => { + newThought.style.display = "none"; + newEssay.style.display = "none"; + + newMoment.style.display = "flex"; + newPostLabel.innerText = "New Moment"; + newMoment.querySelector('input[type="text"]').focus(); + postType.value = "Moment"; + }); + + newNavEssay.addEventListener("click", () => { + newThought.style.display = "none"; + newMoment.style.display = "none"; + + newEssay.style.display = "flex"; + newPostLabel.innerText = "New Essay"; + newEssay.querySelector('input[type="text"]').focus(); + postType.value = "Essay"; + }); + }); + }); +}); diff --git a/js/actionButton.js b/js/actionButton.js deleted file mode 100644 index 52233b1..0000000 --- a/js/actionButton.js +++ /dev/null @@ -1,118 +0,0 @@ -let actionButton = document.querySelector("#actionButton"); -let newPostButton = document.querySelector("#newPost"); -let newSpaceButton = document.querySelector("#newSpace"); -let filterSortButton = document.querySelector("#filterSort"); -let circle = document.querySelector(".circleLayout"); -let modal = document.querySelector("#stdModal"); - -let state = "hide"; - -actionButton.addEventListener("click", () => { - if (state == "hide") { - state = "show"; - newPostButton.style.display = "flex"; - newSpaceButton.style.display = "flex"; - filterSortButton.style.display = "flex"; - circle.style.backgroundColor = "#FFFFFF"; - - newPostButton.addEventListener("click", () => { - fetch("director.php", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: "ajaxId=newPostHTML", - }) - .then((response) => { - return response.json(); - }) - .then((data) => { - modal.innerHTML = data["html"]; - modal.style.display = "flex"; - - let newNavThought = document.querySelector("#newNavThought"); - let newThought = document.querySelector("#newThought"); - let newNavMoment = document.querySelector("#newNavMoment"); - let newMoment = document.querySelector("#newMoment"); - let newNavEssay = document.querySelector("#newNavEssay"); - let newEssay = document.querySelector("#newEssay"); - let newPostLabel = document.querySelector("#newPostLabel"); - let postType = document.querySelector("#postType"); - - let share = document.querySelector("#share"); - - share.addEventListener("click", (e) => { - e.preventDefault(); - switch (postType.value) { - case "Thought": - let thought = document.querySelector("#thought"); - - // TODO: Retrieve space dynamically. - - fetch("director.php", { - method: "POST", - - // TODO: Add Authorization header with JWT - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: - "ajaxId=newThought&space=1&thought=" + - encodeURIComponent(thought.value), - }).then((response) => { - console.log(response.json()); - modal.style.display = "none"; - }); - break; - case "Moment": - break; - case "Essay": - break; - } - }); - - newNavThought.addEventListener("click", () => { - newMoment.style.display = "none"; - newEssay.style.display = "none"; - - newThought.style.display = "flex"; - newPostLabel.innerText = "New Thought"; - newThought.querySelector('input[type="text"]').focus(); - postType.value = "Thought"; - }); - - newNavMoment.addEventListener("click", () => { - newThought.style.display = "none"; - newEssay.style.display = "none"; - - newMoment.style.display = "flex"; - newPostLabel.innerText = "New Moment"; - newMoment.querySelector('input[type="text"]').focus(); - postType.value = "Moment"; - }); - - newNavEssay.addEventListener("click", () => { - newThought.style.display = "none"; - newMoment.style.display = "none"; - - newEssay.style.display = "flex"; - newPostLabel.innerText = "New Essay"; - newEssay.querySelector('input[type="text"]').focus(); - postType.value = "Essay"; - }); - - return data; - }); - }); - - newSpaceButton.addEventListener("click", () => { }); - - filterSortButton.addEventListener("click", () => { }); - } else if (state == "show") { - state = "hide"; - newPostButton.style.display = "none"; - newSpaceButton.style.display = "none"; - filterSortButton.style.display = "none"; - circle.style.backgroundColor = "transparent"; - } -}); diff --git a/js/dropdown.js b/js/dropdown.js new file mode 100644 index 0000000..a209f94 --- /dev/null +++ b/js/dropdown.js @@ -0,0 +1,47 @@ +let dropdowns = document.querySelectorAll(".dropdown"); + +dropdowns.forEach((el) => { + el.addEventListener("click", (e) => { + e.stopPropagation(); + let dropContent = el.querySelector(".dropdownContent"); + dropContent.style.display = "block"; + + dropContent.addEventListener("click", (e) => { + e.stopPropagation(); + }); + }); +}); + +window.addEventListener("click", () => { + document.querySelectorAll(".dropdownContent").forEach((dc) => { + dc.style.display = "none"; + }); +}); + +function deleteSpace(id) { + document.querySelector("#space-" + id).remove(); + + fetch("director.php", { + method: "POST", + + // TODO: Add Authorization header with JWT + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "ajaxId=deleteSpace&id=" + id, + }); +} + +function deletePost(id) { + document.querySelector("#post-" + id).remove(); + + fetch("director.php", { + method: "POST", + + // TODO: Add Authorization header with JWT + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "ajaxId=deletePost&id=" + id, + }); +} diff --git a/js/login.js b/js/login.js deleted file mode 100644 index 498de18..0000000 --- a/js/login.js +++ /dev/null @@ -1,93 +0,0 @@ -const login = document.querySelector("#login"); -const logout = document.querySelector("#logout"); -const stdModal = document.querySelector("#stdModal"); - -if (logout) { - logout.addEventListener("click", () => { - fetch("director.php", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: "ajaxId=logout", - }).then(() => { - if ("startViewTransition" in document) { - document.startViewTransition(() => { - window.location.href = "spaces.php?view=list"; - }); - } else { - window.location.href = "spaces.php?view=list"; - } - }); - }); -} - -if (login) { - login.addEventListener("click", () => { - console.log("click"); - stdModal.innerHTML = ` -
-

Log In

-
- - - - - - - - - - - - -
- - -
- `; - - stdModal.style.display = "flex"; - - const form = document.querySelector("#login_form"); - const username_field = form.querySelector("#username"); - const passwordField = form.querySelector("#password"); - const submit_button = form.querySelector('input[type="submit"]'); - - submit_button.addEventListener("click", async (e) => { - e.preventDefault(); - requestAuth(username_field.value, passwordField.value); - }); - - function requestAuth(username, password) { - fetch("director.php", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: "ajaxId=login&username=" + username + "&password=" + password, - }) - .then((response) => { - if (!response.ok) { - console.log("Login Failed!"); - } - - return response.json(); - }) - .then((data) => { - console.log(data); - if ("startViewTransition" in document) { - document.startViewTransition(() => { - window.location.href = data["redirect"]; - }); - } else { - // Fallback for browsers that don't support View Transitions - window.location.href = data["redirect"]; - } - }); - } - }); -} diff --git a/js/nav.js b/js/nav.js index 7c362f9..28b2b58 100644 --- a/js/nav.js +++ b/js/nav.js @@ -1,16 +1,35 @@ -const nav = document.querySelector('nav'); -const nav_links = nav.querySelectorAll('ul li a'); -const theme_toggle = nav.querySelector('button'); +const nav = document.querySelector("nav"); +const nav_links = nav.querySelectorAll("ul li a"); +const theme_toggle = nav.querySelector("button"); +const back = document.querySelector("#backButton"); +const links = document.querySelectorAll(".fillDiv"); -nav_links.forEach((el) => { - el.addEventListener('click', () => { +if (back) + back.addEventListener("click", () => { + console.log("back"); + history.back(); + }); + +links.forEach((el) => { + el.addEventListener("click", () => { el.preventDefault(); - if ('startViewTransition' in document) { - document.startViewTransition(() => { - window.location.href = el.getAttribute('href'); - }); - } else { - window.location.href = el.getAttribute('href'); - } + applyTransition(el.getAttribute("href")); }); }); + +nav_links.forEach((el) => { + el.addEventListener("click", () => { + el.preventDefault(); + applyTransition(el.getAttribute("href")); + }); +}); + +function applyTransition(href) { + if ("startViewTransition" in document) { + document.startViewTransition(() => { + window.location.href = href; + }); + } else { + window.location.href = href; + } +} diff --git a/js/clickable_rows.js b/js/old/clickable_rows.js similarity index 100% rename from js/clickable_rows.js rename to js/old/clickable_rows.js diff --git a/js/md_preview.js b/js/old/md_preview.js similarity index 100% rename from js/md_preview.js rename to js/old/md_preview.js diff --git a/js/profile_picture_cropper_modal.js b/js/old/profile_picture_cropper_modal.js similarity index 100% rename from js/profile_picture_cropper_modal.js rename to js/old/profile_picture_cropper_modal.js diff --git a/js/todo.js b/js/old/todo.js similarity index 100% rename from js/todo.js rename to js/old/todo.js diff --git a/js/upload.js b/js/old/upload.js similarity index 100% rename from js/upload.js rename to js/old/upload.js diff --git a/js/user_actions_modal.js b/js/old/user_actions_modal.js similarity index 100% rename from js/user_actions_modal.js rename to js/old/user_actions_modal.js diff --git a/js/override_browser_styles.js b/js/override_browser_styles.js deleted file mode 100644 index e69de29..0000000 diff --git a/js/theme.js b/js/theme.js new file mode 100644 index 0000000..4dc9b80 --- /dev/null +++ b/js/theme.js @@ -0,0 +1,34 @@ +const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)"); +if (darkThemeMq.matches) { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = "css/dark.css"; + document.head.appendChild(link); + // Theme set to dark. +} else { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = "css/light.css"; + document.head.appendChild(link); + // Theme set to light. +} + +darkThemeMq.addEventListener("change", (e) => { + if (e.matches) { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = "css/dark.css"; + document.head.appendChild(link); + // Theme set to dark. + } else { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = "css/light.css"; + document.head.appendChild(link); + // Theme set to light. + } +}); diff --git a/js/user.js b/js/user.js new file mode 100644 index 0000000..1856fe1 --- /dev/null +++ b/js/user.js @@ -0,0 +1,245 @@ +const login = document.querySelector("#login"); +const logout = document.querySelector("#logout"); +const signup = document.querySelector("#signup"); + +if (logout) { + logout.addEventListener("click", () => { + fetch("director.php", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "ajaxId=logout", + }).then(() => { + if ("startViewTransition" in document) { + document.startViewTransition(() => { + window.location.href = "spaces.php?view=list"; + }); + } else { + window.location.href = "spaces.php?view=list"; + } + }); + }); +} + +if (login) { + login.addEventListener("click", () => { + console.log("click"); + + fetch("director.php", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "ajaxId=loginHTML", + }) + .then((response) => { + return response.json(); + }) + .then((data) => { + openModal(data["html"]); + + const username_field = modalContent.querySelector("#username"); + const passwordField = modalContent.querySelector("#password"); + const submit_button = modalContent.querySelector( + 'input[type="submit"]', + ); + + submit_button.addEventListener("click", async (e) => { + e.preventDefault(); + requestAuth(username_field.value, passwordField.value); + }); + + function requestAuth(username, password) { + fetch("director.php", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "ajaxId=login&username=" + username + "&password=" + password, + }) + .then((response) => { + if (!response.ok) { + console.log("Login Failed!"); + } + + return response.json(); + }) + .then((data) => { + if ("startViewTransition" in document) { + document.startViewTransition(() => { + window.location.href = data["redirect"]; + }); + } else { + // Fallback for browsers that don't support View Transitions + window.location.href = data["redirect"]; + } + }); + } + }); + }); +} + +if (signup) { + signup.addEventListener("click", () => { + fetch("director.php", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "ajaxId=signupHTML", + }) + .then((response) => { + return response.json(); + }) + .then((data) => { + openModal(data["html"]); + + const submit = modalContent.querySelector('input[type="submit"]'); + submit.disabled = true; + + const signupErrorArea = modalContent.querySelector("#signupError"); + + const usernameField = modalContent.querySelector("#username"); + usernameField.addEventListener("blur", checkUsername); + + const passwordField = modalContent.querySelector("#password"); + passwordField.addEventListener("input", checkPW); + + const vPasswordField = modalContent.querySelector("#vPassword"); + vPasswordField.addEventListener("input", matchPW); + + submit.addEventListener("click", (e) => { + e.preventDefault(); + + checkUsername(); + checkPW(); + matchPW(); + + if ( + usernameField.value != "" && + passwordField.value != "" && + vPasswordField.value != "" && + signupErrorArea.innerHTML == "" + ) + modalContent.querySelector("form").submit(); + }); + + function checkUsername() { + const username = usernameField.value; + + fetch("director.php", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "ajaxId=usernameCheck&username=" + username, + }) + .then((response) => { + if (!response.ok) { + console.log("Failed to check if username exists!"); + } + + return response.json(); // Parse response body as JSON + }) + .then((data) => { + if (data["exists"]) { + console.log("user exists"); + if (!document.querySelector("#userExists")) { + let p = document.createElement("p"); + p.id = "userExists"; + p.innerText = "Username is taken."; + + signupErrorArea.append(p); + submit.disabled = true; + } + } else { + let p = signupErrorArea.querySelector("#userExists"); + if (p) p.remove(); + matchPW(); + + if ( + signupErrorArea.innerHTML == "" && + passwordField.value != "" && + vPasswordField.value != "" + ) + submit.disabled = false; + } + }); + } + + function checkPW() { + let pw = passwordField.value; + if (pw.length < 8) { + if (!document.querySelector("#pwLength")) { + let p = document.createElement("p"); + p.id = "pwLength"; + p.innerText = + "Password length must be greater than 8 characters."; + + signupErrorArea.append(p); + } + } else { + let p = signupErrorArea.querySelector("#pwLength"); + if (p) p.remove(); + matchPW(); + + if ( + signupErrorArea.innerHTML == "" && + passwordField.value != "" && + vPasswordField.value != "" + ) + submit.disabled = false; + } + + const num_regex = /\d/; + if (!num_regex.test(pw)) { + if (!document.querySelector("#pwNum")) { + let p = document.createElement("p"); + p.id = "pwNum"; + p.innerText = "Password must have at least 1 digit."; + + signupErrorArea.append(p); + submit.disabled = true; + } + } else { + let p = signupErrorArea.querySelector("#pwNum"); + if (p) p.remove(); + matchPW(); + + if ( + signupErrorArea.innerHTML == "" && + passwordField.value != "" && + vPasswordField.value != "" + ) + submit.disabled = false; + } + } + + function matchPW() { + let pw = passwordField.value; + let vPW = vPasswordField.value; + + if (pw != vPW) { + if (!document.querySelector("#pwMatch")) { + let p = document.createElement("p"); + p.id = "pwMatch"; + p.innerText = "Passwords must match."; + + signupErrorArea.append(p); + submit.disabled = true; + } + } else { + let p = signupErrorArea.querySelector("#pwMatch"); + if (p) p.remove(); + if ( + signupErrorArea.innerHTML == "" && + passwordField.value != "" && + vPasswordField.value != "" + ) + submit.disabled = false; + } + } + }); + }); +} diff --git a/js/util.js b/js/util.js new file mode 100644 index 0000000..68942ca --- /dev/null +++ b/js/util.js @@ -0,0 +1,21 @@ +const stdModal = document.querySelector("#stdModal"); +let modalContent = document.querySelector(".modalContent"); + +function openModal(html) { + stdModal.innerHTML = html; + stdModal.style.display = "flex"; + modalContent = document.querySelector(".modalContent"); + + window.addEventListener("click", () => { + closeModal(); + }); + + modalContent.addEventListener("click", (e) => { + e.stopPropagation(); + }); +} + +function closeModal() { + stdModal.innerHTML = ""; + stdModal.style.display = "none"; +} diff --git a/spaces.php b/spaces.php index 3681c9b..ed6edaa 100644 --- a/spaces.php +++ b/spaces.php @@ -12,15 +12,11 @@ if (isset($_GET['view'])) { case 'essay': if (isset($_GET['id'])) { // TODO: Need another check here to verify the user is in the Essay's space. - $essay = Essay::retrieveFromDB($_GET['id']); - $space = $essay->getSpace(); - - if (isset($_SESSION['user']) && $space->allowUser($_SESSION['user'])) - echo displayEssay($essay); - else if ($space->getVisibility() == 1) + $essay = Post::retrieveFromDB($_GET['id']); + if ($essay) echo displayEssay($essay); else - echo displaySpace(2025); + echo errorScreen('Essay does not exist!', 'The Essay ID provided does not exist in the database.'); } else echo displaySpace(2025); break; @@ -28,19 +24,22 @@ if (isset($_GET['view'])) { // TODO: After implementing the notification modal, notify the user on success/fail auth events. if (isset($_GET['id'])) { $space = Space::retrieveFromDB($_GET['id']); - if (isset($_SESSION['user'])) - if ($space->allowUser(unserialize($_SESSION['user']))) - echo displaySpace($_GET['id']); - else if ($space->getVisibility() == 1) - echo displaySpace($_GET['id']); - else - echo 'TODO: Notification Modal -> User not a member'; - else { - if ($space->getVisibility() == 1) - echo displaySpace($_GET['id']); - else - echo 'TODO: Notification Modal -> Space is not Open'; - } + if ($space) { + if (isset($_SESSION['user'])) + if ($space->allowUser(unserialize($_SESSION['user']))) + echo displaySpace($_GET['id']); + else if ($space->getVisibility() == 1) + echo displaySpace($_GET['id']); + else + echo 'TODO: Notification Modal -> User not a member'; + else { + if ($space->getVisibility() == 1) + echo displaySpace($_GET['id']); + else + echo 'TODO: Notification Modal -> Space is not Open'; + } + } else + echo errorScreen('Space does not exist!', 'The Space ID provided does not exist in the database.'); } else echo displaySpaceList(); break; @@ -60,19 +59,55 @@ if (isset($_GET['view'])) { } if (isset($_SESSION['user'])) - echo actionButton(); + echo actionBar(); + +if (isset($_GET['id'])) + echo backButton(); require_once 'components/core/foot.php'; -function actionButton() +function backButton() { - // TODO: Position these on the arc of a circle. $html = ' -
- - - -
+ + '; + + return $html; +} + +function actionBar() +{ + $user = unserialize($_SESSION['user']); + + $html = ' +
+
+ + +
+ + +
+ '; + + return $html; +} + +function errorScreen(String $heading, String $msg) +{ + $html = ' +
+

' . $heading . '

+

' . $msg . '

'; @@ -81,9 +116,16 @@ function actionButton() function displayEssay(Essay $e) { + /* if (isset($_SESSION['user'])) */ + /* if ($e->allowUser(unserialize($_SESSION['user'])) || $e->getSpace()->getVisibility() != 1) */ + /* return errorScreen('Access Denied!', 'You do not have access to the Space that this Essay is in.'); */ + + $parsedown = new Parsedown(); + $parsedown->setSafeMode(true); + $html = ' -
- ' . $e->getHTML() . ' +
+ ' . $parsedown->parse($e->getMarkdown()) . '
'; @@ -95,7 +137,7 @@ function displaySpaceList() $user = unserialize($_SESSION['user']); $html = ' -
+

Spaces

@@ -103,9 +145,8 @@ function displaySpaceList() '; $spaces = $user->getSpaces(); - foreach ($spaces as $s) { + foreach ($spaces as $s) $html .= $s->getCardHTML(); - } $html .= '
@@ -119,7 +160,7 @@ function displaySpace($id = 2025) $space = Space::retrieveFromDB($id); if ($space) { $html = ' -
+

' . $space->getName() . '

' . $space->getDescription() . '

@@ -127,6 +168,11 @@ function displaySpace($id = 2025)
'; + $subspaces = $space->getSubSpaces(); + foreach ($subspaces as $ss) { + $html .= $ss->getCardHTML(); + } + // TODO: Need to investigate this bug in Posts $posts = $space->getPosts(); foreach ($posts as $post) {