Skip to content

Commit

Permalink
Merge pull request #33 from AveselsJS/real-time-notifications
Browse files Browse the repository at this point in the history
Real time notifications
  • Loading branch information
pestsov-v authored Nov 8, 2021
2 parents 14696ad + 2b3ee2d commit 5ea57da
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 118 deletions.
14 changes: 14 additions & 0 deletions public/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -814,4 +814,18 @@ button span {
#notificationBadge.active,
#messagesBadge.active {
visibility: visible;
}

#notificationList {
position: fixed;
top: 5px;
right: 5px;
width: 420px;
background-color: white;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

#notificationList .active {
background-color: white;
}
9 changes: 9 additions & 0 deletions public/js/chatPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ $(document).ready(() => {
const messagesHtml = messages.join("");
addMessagesHtmlToPage(messagesHtml);
scrollToBottom(false);
markAllMessagesAsRead();

$(".loadingSpinnerContainer").remove()
$(".chatContainer").css("visibility", "visible")
Expand Down Expand Up @@ -183,4 +184,12 @@ function scrollToBottom(animated) {
} else {
container.scrollTop(scrollHeight);
}
}

function markAllMessagesAsRead() {
$.ajax({
url: `/api/chats/${chatId}/messages/markAsRead`,
type: 'PUT',
success: () => refreshMessagesBadge()
})
}
7 changes: 5 additions & 2 deletions public/js/clientSocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ socket.emit("setup", userLoggedIn);
socket.on("connected", () => connected = true)
socket.on("message received", (newMessage) => messageReceived(newMessage))

socket.on("notification received", (newNotification) => {
console.log("ss")
socket.on("notification received", () => {
$.get("/api/notifications/latest", (notificationData) => {
showNotificationPopup(notificationData)
refreshNotificationsBadge()
})
})

function emitNotification(userId) {
Expand Down
140 changes: 137 additions & 3 deletions public/js/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -692,8 +692,8 @@ function getOtherChatUsers(users) {
}

function messageReceived(newMessage) {
if($(".chatContainer").length == 0) {

if($(`[data-room="${newMessage.chat._id}"]`).length == 0) {
showMessagePopup(newMessage)
} else {
addChatMessageHtml(newMessage);
}
Expand Down Expand Up @@ -740,4 +740,138 @@ function refreshNotificationsBadge() {
}

})
}
}

function showNotificationPopup(data) {
const html = createNotificationHtml(data);
const element = $(html);
element.hide().prependTo("#notificationList").slideDown("fast")

setTimeout(() => element.fadeOut(400), 5000)
}

function showMessagePopup(data) {

if (!data.chat.latestMessage._id) {
data.chat.latestMessage = data
}

const html = createChatHtml(data.chat);
const element = $(html);
element.hide().prependTo("#notificationList").slideDown("fast")

setTimeout(() => element.fadeOut(400), 5000)
}

function outputNotificationList(notifications, container) {
notifications.forEach(notification => {
const html = createNotificationHtml(notification)
container.append(html)
})

if (notifications.length == 0) {
container.append("<span class='NoResults'>У вас нет никаких уведомлений</span>")
}
}

function createNotificationHtml(notification) {
const userFrom = notification.userFrom;
const text = getNotificationText(notification)
const href = getNotificationUrl(notification)
const className = notification.opened ? "" : "active"

return `<a href='${href}' class='resultListItem notification ${className}' data-id='${notification._id}'>
<div class='resultsImageContainer'>
<img src='${userFrom.profilePic}'>
</div>
<div class='resultsDetailsContainer ellipsis'>
<span ckass='ellipsis'>${text}</span>
</div>
<a/>`
}

function getNotificationText(notification) {

const userFrom = notification.userFrom
if (!userFrom.firstName || !userFrom.lastName) {
return alert("Пользователь не был запопулейтен")
}

const userFromName = `${userFrom.firstName} ${userFrom.lastName}`;
let text

if (notification.notificationType == "retweet") {
text = `${userFromName} ретвитнул один из Ваших постов`
} else if (notification.notificationType == "like") {
text = `${userFromName} понравился один из Ваших постов`
} else if (notification.notificationType == "reply") {
text = `${userFromName} репостнул один из Ваших постов`
} else if (notification.notificationType == "follow") {
text = `${userFromName} подписался на Вас`
}

return `<span class='ellipsis'>${text}</span>`
}

function getNotificationUrl(notification) {
let url

if (notification.notificationType == "retweet" ||
notification.notificationType == "like" ||
notification.notificationType == "reply") {
url = `/posts/${notification.entityId}`
} else if (notification.notificationType == "follow") {
url = `/profile/${notification.entityId}`
}

return url
}


function createChatHtml(chatData) {
const chatName = getChatName(chatData);
const image = getChatImageElements(chatData);
const latestMessage = getLatestMessage(chatData.latestMessage);

const activeClass = !chatData.latestMessage || chatData.latestMessage.readBy.includes(userLoggedIn._id) ? "" : "active"

return `<a href='/messages/${chatData._id}' class='resultListItem ${activeClass}'>
${image}
<div class="resultsDetailsContainer ellipsis">
<span class="heading ellipsis">${chatName}</span>
<span class="subText ellipsis">${latestMessage}</span>
</div>
</a>`
}

function getLatestMessage(latestMessage) {
if (latestMessage != null) {
const sender = latestMessage.sender;
return `${sender.firstName} ${sender.lastName}: ${latestMessage.content}`
}

return "Новый чат"
}

function getChatImageElements(chatData) {
const otherChatUsers = getOtherChatUsers(chatData.users);

let groupChatClass = "";
let chatImage = getUserChatElement(otherChatUsers[0]);

if (otherChatUsers.length > 1) {
groupChatClass = "groupChatImage";
chatImage += getUserChatElement(otherChatUsers[1]);
}

return `<div class='resultsImageContainer ${groupChatClass}'>${chatImage}</div>`
}

function getUserChatElement(user) {
if (!user || !user.profilePic) {
return alert("Ошибочная информация от пользователя")
}

return `<img src='${user.profilePic}' alt="User's profile pic">`;
}

46 changes: 0 additions & 46 deletions public/js/inboxPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,49 +18,3 @@ function outputChatList(chatList, container) {
container.append("<span class='NoResults'>Вы не участвуете ни в одном чате</span>")
}
}

function createChatHtml(chatData) {
const chatName = getChatName(chatData);
const image = getChatImageElements(chatData);
const latestMessage = getLatestMessage(chatData.latestMessage);

return `<a href='/messages/${chatData._id}' class='resultListItem'>
${image}
<div class="resultsDetailsContainer ellipsis">
<span class="heading ellipsis">${chatName}</span>
<span class="subText ellipsis">${latestMessage}</span>
</div>
</a>`
}

function getLatestMessage(latestMessage) {
if (latestMessage != null) {
const sender = latestMessage.sender;
return `${sender.firstName} ${sender.lastName}: ${latestMessage.content}`
}

return "Новый чат"
}

function getChatImageElements(chatData) {
const otherChatUsers = getOtherChatUsers(chatData.users);

let groupChatClass = "";
let chatImage = getUserChatElement(otherChatUsers[0]);

if (otherChatUsers.length > 1) {
groupChatClass = "groupChatImage";
chatImage += getUserChatElement(otherChatUsers[1]);
}

return `<div class='resultsImageContainer ${groupChatClass}'>${chatImage}</div>`
}

function getUserChatElement(user) {
if (!user || !user.profilePic) {
return alert("Ошибочная информация от пользователя")
}

return `<img src='${user.profilePic}' alt="User's profile pic">`;
}

63 changes: 0 additions & 63 deletions public/js/notificationsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,3 @@ $(document).ready(() => {

$("#markNotificationsAsRead").click(() => markNotificationAsOpened())

function outputNotificationList(notifications, container) {
notifications.forEach(notification => {
const html = createNotificationHtml(notification)
container.append(html)
})

if (notifications.length == 0) {
container.append("<span class='NoResults'>У вас нет никаких уведомлений</span>")
}
}

function createNotificationHtml(notification) {
const userFrom = notification.userFrom;
const text = getNotificationText(notification)
const href = getNotificationUrl(notification)
const className = notification.opened ? "" : "active"

return `<a href='${href}' class='resultListItem notification ${className}' data-id='${notification._id}'>
<div class='resultsImageContainer'>
<img src='${userFrom.profilePic}'>
</div>
<div class='resultsDetailsContainer ellipsis'>
<span ckass='ellipsis'>${text}</span>
</div>
<a/>`
}

function getNotificationText(notification) {

const userFrom = notification.userFrom
if (!userFrom.firstName || !userFrom.lastName) {
return alert("Пользователь не был запопулейтен")
}

const userFromName = `${userFrom.firstName} ${userFrom.lastName}`;
let text

if (notification.notificationType == "retweet") {
text = `${userFromName} ретвитнул один из Ваших постов`
} else if (notification.notificationType == "like") {
text = `${userFromName} понравился один из Ваших постов`
} else if (notification.notificationType == "reply") {
text = `${userFromName} репостнул один из Ваших постов`
} else if (notification.notificationType == "follow") {
text = `${userFromName} подписался на Вас`
}

return `<span class='ellipsis'>${text}</span>`
}

function getNotificationUrl(notification) {
let url

if (notification.notificationType == "retweet" ||
notification.notificationType == "like" ||
notification.notificationType == "reply") {
url = `/posts/${notification.entityId}`
} else if (notification.notificationType == "follow") {
url = `/profile/${notification.entityId}`
}

return url
}
16 changes: 13 additions & 3 deletions routes/api/chats.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ router.get("/", async (req, res, next) => {
.sort({ updatedAt: -1 })
.then(async results => {

// if(req.query.unreadOnly != undefined && req.query.unreadOnly == "true") {
// results = results.filter(r => !r.latestMessage.readBy.includes(req.session.user._id));
// }
if (req.query.unreadOnly != undefined && req.query.unreadOnly == "true") {
results = results.filter(r => r.latestMessage && !r.latestMessage.readBy.includes(req.session.user._id));
}

results = await User.populate(results, {path: "latestMessage.sender"})
res.status(200).send(results)
Expand Down Expand Up @@ -85,4 +85,14 @@ router.get("/:chatId/messages", async (req, res, next) => {
})
})

router.put("/:chatId/messages/markAsRead", async (req, res, next) => {

Message.updateMany({ chat: req.params.chatId }, { $addToSet: { readBy: req.session.user._id } })
.then(() => res.sendStatus(204))
.catch(error => {
console.log(error);
res.sendStatus(400);
})
})

module.exports = router;
12 changes: 12 additions & 0 deletions routes/api/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ router.get("/", async (req, res, next) => {
})
})

router.get("/latest", async (req, res, next) => {
Notification.findOne({ userTo: req.session.user._id })
.populate("userTo")
.populate("userFrom")
.sort({ createdAt: -1 })
.then(results => res.status(200).send(results))
.catch(error => {
console.log(error);
res.sendStatus(400);
})
})

router.put("/:id/markAsOpened", async (req, res, next) => {
Notification.findByIdAndUpdate(req.params.id, {opened: true})
.then(() => res.sendStatus(200))
Expand Down
2 changes: 1 addition & 1 deletion views/chatPage.pug
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ block content
.mainContentContainer
.loadingSpinnerContainer
img(src="/images/loadingSpinner.gif", alt="Спиннер загрузки")
.chatContainer(style="visibility: hidden")
.chatContainer(style="visibility: hidden", data-room=chat._id)
ui.chatMessages

.typingDots
Expand Down
1 change: 1 addition & 0 deletions views/layouts/main-layout.pug
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ html(lang="en")
block content

.d-none.d-md-block.col-md-2.col-lg-4
#notificationList


script(src='https://code.jquery.com/jquery-3.6.0.min.js', integrity='sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=', crossorigin='anonymous')
Expand Down

0 comments on commit 5ea57da

Please sign in to comment.