diff --git a/README.md b/README.md index fe82c877725..0c734580a13 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,30 @@ Tweeter is a simple, single-page Twitter clone. -This repository is the starter code for the project: Students will fork and clone this repository, then build upon it to practice their HTML, CSS, JS, jQuery and AJAX front-end skills, and their Node, Express back-end skills. +This app was created to practice with HTML, CSS, JS, jQuery, AJAX, and front-end development skills. As well as Node and Express back-end skills. + +## Final Product + +### Sending a Tweet! + +!["Sending a Tweet"](https://github.com/originallykevin/tweeter/blob/master/docs/sending-tweet.gif) + +### Browser View + +!["Browser View"](https://github.com/originallykevin/tweeter/blob/master/docs/browser-view.gif) + +### Tablet View + +!["Tablet View"](https://github.com/originallykevin/tweeter/blob/master/docs/tablet-view.gif) + +### Page Scrolling + +!["Page Scrolling"](https://github.com/originallykevin/tweeter/blob/master/docs/page-scrolling.gif) + +### Word Counter + +!["Word Counter"](https://github.com/originallykevin/tweeter/blob/master/docs/error-message.gif) + ## Getting Started diff --git a/docs/browser-view.gif b/docs/browser-view.gif new file mode 100644 index 00000000000..82e37b2c569 Binary files /dev/null and b/docs/browser-view.gif differ diff --git a/docs/error-message.gif b/docs/error-message.gif new file mode 100644 index 00000000000..aa12adee025 Binary files /dev/null and b/docs/error-message.gif differ diff --git a/docs/page-scrolling.gif b/docs/page-scrolling.gif new file mode 100644 index 00000000000..43101a9c4fe Binary files /dev/null and b/docs/page-scrolling.gif differ diff --git a/docs/sending-tweet.gif b/docs/sending-tweet.gif new file mode 100644 index 00000000000..faefaa68d49 Binary files /dev/null and b/docs/sending-tweet.gif differ diff --git a/docs/tablet-view.gif b/docs/tablet-view.gif new file mode 100644 index 00000000000..b0d95919c66 Binary files /dev/null and b/docs/tablet-view.gif differ diff --git a/package-lock.json b/package-lock.json index 8177739a4db..5761745aaed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -854,7 +854,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -875,12 +876,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -895,17 +898,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1022,7 +1028,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1034,6 +1041,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1048,6 +1056,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1055,12 +1064,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1079,6 +1090,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1159,7 +1171,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1171,6 +1184,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1256,7 +1270,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -1292,6 +1307,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1311,6 +1327,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1354,12 +1371,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/public/images/Image.png b/public/images/Image.png new file mode 100644 index 00000000000..32f86b03a8b Binary files /dev/null and b/public/images/Image.png differ diff --git a/public/index.html b/public/index.html index 3eff8a8578d..3b08f3629b6 100644 --- a/public/index.html +++ b/public/index.html @@ -1,29 +1,97 @@ - - - Tweeter - Home Page - - + + + Tweeter - Home Page - - + + + + + - - + + - - - + + + + + - - - + + - -
- - + + + + + + + + + + + + + + + + +
+
+
+ +
+ +
+

subuwu

+
+
+
+ + +
+ +
+ +
+
+
+ + +
+
+ + 140 +
+
+
+
+
+ + + +
+ + + + \ No newline at end of file diff --git a/public/scripts/client.js b/public/scripts/client.js index 80ad84592c5..43c8ae36a11 100644 --- a/public/scripts/client.js +++ b/public/scripts/client.js @@ -1,6 +1,90 @@ -/* - * Client-side JS logic goes here - * jQuery is already loaded - * Reminder: Use (and do all your DOM work in) jQuery's document ready function - */ +$(document).ready(() => { + // error message will be hidden until prompted + $('#error').hide(); + + // refactor. add .empty() to clear the repeat during new tweet + const loadTweets = function() { + $.get('/tweets') + .then((tweets) => { + $('#tweets-container').empty(); + $('#tweet-text').val(); + renderTweets(tweets); + }); + }; + + loadTweets(); + + // Method 2: Use an escape function. Preventing XSS + const escape = function(str) { + let div = document.createElement("div"); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + }; + + const createTweetElement = function(tweet) { + let username = tweet.user.name; + let handle = tweet.user.handle; + let content = tweet.content.text; + let date = tweet['created_at']; + + let $tweet = $(` +
+
+
+ ${username}
+
${handle}
+
+

${escape(content)}

+ +
+ `); + return $tweet; + }; + + // add event listener and prevent default behaviour(refresh) + $('#new-tweet').on('submit', function(event) { + event.preventDefault(); + // edge case for form submission + const input = $('#tweet-text').val(); + const icon = ``; + + // hiding error message + $('#error').slideUp(); + + if (input.length > 140) { + $('#error').html(`${icon} Too long. Tweet entered exceeds length limit! ${icon}`); + $('#error').slideDown(); + return; + } + + if (input === '' || input === null) { + $('#error').html(`${icon} Please enter something.. anything! ${icon}`); + $('#error').slideDown(); + return; + } + + const serializedData = $(this).serialize(); + // submit post request of serializedData. clear form and reset counter to 140. + $.post('/tweets', serializedData) + .then(() => { + $('#tweet-text').val(''); + $('output.counter').text(140); + loadTweets(); + }); + }); + + const renderTweets = function(tweets) { + for (const tweet of tweets) { + const newTweet = createTweetElement(tweet); + $('#tweets-container').prepend(newTweet); + } + }; +}); diff --git a/public/scripts/composer-char-counter.js b/public/scripts/composer-char-counter.js new file mode 100644 index 00000000000..9439b5b92cd --- /dev/null +++ b/public/scripts/composer-char-counter.js @@ -0,0 +1,24 @@ +$(document).ready(() => { + const $textarea = $('#tweet-text'); + + $textarea.on("input", function() { + // find letter counter + const $counter = $textarea.parent().siblings().find('output.counter'); + + let charNum = $textarea.val().length; + let charCount = 140 - charNum; + + // black if > 0 letters. red if < 0 letters + if (charCount > 0) { + $counter.val(charCount).css({ 'color': 'black' }); + } else if (charCount <= 0) { + $counter.val(charCount).css({ 'color': 'red' }); + } + }); +}); + + + + + + diff --git a/public/scripts/composer.js b/public/scripts/composer.js new file mode 100644 index 00000000000..7ff4e9006ba --- /dev/null +++ b/public/scripts/composer.js @@ -0,0 +1,29 @@ +$(document).ready(() => { + const topButton = $('#scroll-nav'); + const bottomButton = $('#scroll-top'); + + // hide bottom button when at top of page + bottomButton.hide(); + + // activation by scroll. 5px down will active button to appear. + $(window).scroll(function() { + if ($(this).scrollTop() > 5) { + $(bottomButton).fadeIn(); + } else { + $(bottomButton).fadeOut(); + } + }); + + // active on clicking it + // enable text area automatically after scroll up + topButton.on("click", function() { + $("html, body").animate({ scrollTop: 0 }, "slow"); + $('#tweet-text').focus(); + }); + + bottomButton.on("click", function() { + $("html, body").animate({ scrollTop: 0 }, "slow"); + $('#tweet-text').focus(); + }); + +}); diff --git a/public/styles/header.css b/public/styles/header.css new file mode 100644 index 00000000000..05cb5a0718e --- /dev/null +++ b/public/styles/header.css @@ -0,0 +1,12 @@ +header { + display: flex; + height: 400px; + width: 100%; + flex-direction: column; + align-items: center; + padding: 123px; +} + +.name { + color: white; +} \ No newline at end of file diff --git a/public/styles/layout.css b/public/styles/layout.css index 7ecca6f8137..207e7bd5cb9 100644 --- a/public/styles/layout.css +++ b/public/styles/layout.css @@ -1,12 +1,10 @@ -/* - * http://www.paulirish.com/2012/box-sizing-border-box-ftw/ - * https://css-tricks.com/international-box-sizing-awareness-day/ - * Do not modify these two selectors, please - */ html { box-sizing: border-box; } -*, *:before, *:after { + +*, +*:before, +*:after { box-sizing: inherit; } @@ -18,8 +16,121 @@ body { color: #545149; background-color: #f4f1ec; font-size: 24px; + font-family: 'Source Sans Pro', sans-serif; + background-image: url(https://www.transparenttextures.com/patterns/binding-light.png); + background-color: #eae8e8; } -/* - * Additional CSS can be defined below or in other CSS files, as per instructions - */ \ No newline at end of file +.container { + display: flex; + margin: 0 auto; + top: 100px; + padding: 0px 0 0 0; + position: relative; + width: 90%; + justify-content: center; + flex-direction: column; +} + +/* error message during input */ +#error { + display: flex; + justify-content: space-around; + font-size: xx-large; + padding: 15px; + margin-bottom: 25px; + color: red; + border: solid 3px red +} + +.tweet-container { + display: flex; + flex-direction: column; + font-size: large; + border: 3px solid black; + margin: 50px 0 70px 0; +} + +.tweet-container:hover { + box-shadow: 5px 10px grey; +} + +.tweet-container header { + display: flex; + flex-direction: row; + justify-content: space-between; + font-size: large; + padding: 25px 25px 50px 25px; + height: 100px; +} + +/* tweet body */ +.tweet-container p { + word-wrap: break-word; + width: 98%; + margin: 0 auto; + padding: 0 15px 25px 15px; + border-bottom: 3px solid black +} + +.tweet-container footer { + font-size: smaller; + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 25px; +} + +.tweet-icon-single:hover { + color: rgba(237, 216, 101, 0.737); +} + +.button { + cursor: pointer; +} + +.button:hover { + box-shadow: 3px 3px; + color: lightcoral; +} + + +/* Tablet Styles */ +@media screen and (max-width: 1024px) { + .profile { + /* background-color: #4056A1; */ + background-color: #5F6F94; + height: 475px; + } +} + +/* Desktop styles */ +@media only screen and (min-width:1024px) { + + .container { + padding: 50px; + width: 50%; + float: right; + } + + .tweet-container header { + font-size: x-large; + } + + .tweet-container { + font-size: x-large; + } + + .profile { + display: flex; + flex-direction: column; + align-items: center; + border-radius: 25px; + height: 400px; + width: 40%; + float: left; + margin: 150px 25px 25px 50px; + padding: 30px; + background-color: #5F6F94; + } +} \ No newline at end of file diff --git a/public/styles/nav.css b/public/styles/nav.css new file mode 100644 index 00000000000..3cddea73c5e --- /dev/null +++ b/public/styles/nav.css @@ -0,0 +1,78 @@ +nav { + display: flex; + z-index: 1; + top: 0; + position: fixed; + width: 100%; + height: 120px; + background-color: #25316D; + color: #FFFFFF; + padding: 45px 35px 0 35px; + justify-content: space-between; +} + +/* top right */ +.nav-message { + display: flex; + flex-direction: column; + align-items: center; + font-size: 24px; + line-height: 50px; +} + +/* top left */ +nav span { + font-family: 'Bungee', cursive; + font-size: 35px; +} + +/* button shortcut to writing a tweet */ +#scroll-nav { + display: flex; + color: red; + animation: updown 2s ease infinite; + cursor: pointer; +} + +#scroll-nav:active { + color: lightcoral; +} + +@keyframes updown { + 0% { + transform: translateY(-100%); + } + + 50% { + transform: translateY(100%); + } + + 100% { + transform: translateY(-100%); + } +} + +/* button for bottom right of page */ +#scroll-top { + position: fixed; + right: 15px; + bottom: 15px; + width: 90px; + height: 90px; + border-radius: 50%; + background: red; + color: white; + border: none; + cursor: pointer; +} + +#scroll-top:active { + background-color: lightcoral; +} + +/* Tablet Styles */ +@media only screen and (max-width: 1024px) { + nav { + background-color: #5F6F94; + } +} \ No newline at end of file diff --git a/public/styles/new-tweet.css b/public/styles/new-tweet.css new file mode 100644 index 00000000000..8572191c108 --- /dev/null +++ b/public/styles/new-tweet.css @@ -0,0 +1,42 @@ +.new-tweet { + margin: 0 auto; + width: 100%; +} + +.tweet-form { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + border-bottom: 4px solid rgb(0, 0, 0); + padding-bottom: 4px; + font-family: "Source Sans Pro"; +} + +#tweet-text { + font-size: 25px; + background-color: transparent; + border: none; + resize: none; + outline: none; + width: 100%; + height: 40px; +} + +.tweet-bar { + display: flex; + justify-content: space-between; + align-items: baseline; + padding-top: 5px; +} + +.button { + font-family: "Bungee", sans-serif; + color: white; + background-color: #4056A1; + padding: 4px; +} + +.counter { + font-family: "Bungee", sans-serif; +} diff --git a/server/data-files/initial-tweets.json b/server/data-files/initial-tweets.json index 2b03138f70b..096d6397438 100644 --- a/server/data-files/initial-tweets.json +++ b/server/data-files/initial-tweets.json @@ -1,24 +1,24 @@ [ { "user": { - "name": "Newton", - "avatars": "https://i.imgur.com/73hZDYK.png" - , - "handle": "@SirIsaac" + "name": "McRae", + "avatars": "https://i.imgur.com/73hZDYK.png", + "handle": "@colin555" }, "content": { - "text": "If I have seen further it is by standing on the shoulders of giants" + "text": "If in doubt, flat out." }, - "created_at": 1461116232227 + "created_at": 1663132490474 }, { "user": { - "name": "Descartes", + "name": "subuwu", "avatars": "https://i.imgur.com/nlhLi3I.png", - "handle": "@rd" }, + "handle": "@wrb" + }, "content": { - "text": "Je pense , donc je suis" + "text": "Car goes zoom, engine go boom!" }, - "created_at": 1461113959088 + "created_at": 1663218890474 } -] +] \ No newline at end of file