Initial implementation of notification icon.
This commit is contained in:
parent
61cecf5a70
commit
6fccfb2436
5 changed files with 92 additions and 4 deletions
|
@ -20,6 +20,7 @@ enum ChatHandlers {
|
|||
TYPING = "typing",
|
||||
DELETE = "delete",
|
||||
SPEAK = "speak",
|
||||
READ = "read",
|
||||
}
|
||||
|
||||
interface ChatProviderContext {
|
||||
|
@ -65,6 +66,11 @@ export function ChatProvider({ children }: PropsWithChildren) {
|
|||
const [notifications, setNotifications] = useState<number>(0);
|
||||
const [messageLookup, setMessageLookup] = useState({});
|
||||
|
||||
const setMessagesAndRead = useCallback((messages: IChatMessage[]) => {
|
||||
setMessages(messages);
|
||||
trySendReadMessage();
|
||||
}, []);
|
||||
|
||||
const addMessage = useCallback((message: IChatMessage) => {
|
||||
if (message.id === OPTIMISTIC_MESSAGE_ID) {
|
||||
setMessages((prev) => prev.concat(message));
|
||||
|
@ -139,6 +145,23 @@ export function ChatProvider({ children }: PropsWithChildren) {
|
|||
} catch (error) {}
|
||||
}, []);
|
||||
|
||||
const [lastMaxTime, setLastMaxTime] = useState<number | null>(null);
|
||||
const trySendReadMessage = useCallback(() => {
|
||||
if (messages.length === 0) {
|
||||
return; // Exit if the messages array is empty
|
||||
}
|
||||
|
||||
if (document.hasFocus()) {
|
||||
const maxTime = Math.max(...messages.map(msg => msg.time));
|
||||
|
||||
if (maxTime !== lastMaxTime) { // Only emit if there's a new maxTime
|
||||
setLastMaxTime(maxTime); // Update the stored maxTime
|
||||
socket.current?.emit(ChatHandlers.READ, maxTime);
|
||||
}
|
||||
}
|
||||
}, [messages, lastMaxTime]);
|
||||
|
||||
|
||||
const context = useMemo<ChatProviderContext>(
|
||||
() => ({
|
||||
online,
|
||||
|
@ -170,7 +193,7 @@ export function ChatProvider({ children }: PropsWithChildren) {
|
|||
socket.current = io();
|
||||
|
||||
socket.current
|
||||
.on(ChatHandlers.CATCHUP, setMessages)
|
||||
.on(ChatHandlers.CATCHUP, setMessagesAndRead)
|
||||
.on(ChatHandlers.ONLINE, setOnline)
|
||||
.on(ChatHandlers.TYPING, setTyping)
|
||||
.on(ChatHandlers.SPEAK, addMessage)
|
||||
|
@ -193,6 +216,8 @@ export function ChatProvider({ children }: PropsWithChildren) {
|
|||
}, [draft]);
|
||||
|
||||
useEffect(() => {
|
||||
trySendReadMessage();
|
||||
|
||||
if (focused || document.hasFocus()) {
|
||||
setNotifications(0);
|
||||
}
|
||||
|
|
|
@ -4874,6 +4874,7 @@ img.golden, img[g] {
|
|||
.fa-x:before{content:"\58"}
|
||||
.fa-scale-balanced:before{content:"\f24e"}
|
||||
.fa-hippo:before{content:"\f6ed"}
|
||||
.fa-booth:before{content:"\f734"}
|
||||
|
||||
.awards-wrapper input[type="radio"] {
|
||||
display: none;
|
||||
|
|
|
@ -12,6 +12,7 @@ from files.classes.alts import Alt
|
|||
from files.classes.award import AwardRelationship
|
||||
from files.classes.badges import Badge
|
||||
from files.classes.base import CreatedBase
|
||||
from files.classes.chat_message import ChatMessage
|
||||
from files.classes.clients import * # note: imports Comment and Submission
|
||||
from files.classes.follows import Follow
|
||||
from files.classes.mod_logs import ModAction
|
||||
|
@ -162,6 +163,30 @@ class User(CreatedBase):
|
|||
def can_manage_reports(self):
|
||||
return self.admin_level > 1
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def can_access_chat(self):
|
||||
if self.is_suspended_permanently:
|
||||
return False
|
||||
if self.admin_level >= PERMS['CHAT_FULL_CONTROL']:
|
||||
return True
|
||||
if self.chat_authorized:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def unread_chat_messages_count(self):
|
||||
if not self.can_access_chat:
|
||||
return 0 # return 0 if the user can't access chat
|
||||
|
||||
# Query for all chat messages that are newer than the user's last seen timestamp
|
||||
unread_messages_count = g.db.query(ChatMessage)\
|
||||
.filter(ChatMessage.created_datetimez > self.chat_lastseen)\
|
||||
.count()
|
||||
|
||||
return unread_messages_count
|
||||
|
||||
@property
|
||||
def age_days(self):
|
||||
return (datetime.now() - datetime.fromtimestamp(self.created_utc)).days
|
||||
|
|
|
@ -18,9 +18,11 @@ def chat_is_allowed(perm_level: int=0):
|
|||
@functools.wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> bool | None:
|
||||
v = get_logged_in_user()
|
||||
if not v or v.is_suspended_permanently or v.admin_level < perm_level:
|
||||
if not v:
|
||||
return abort(403)
|
||||
if v.admin_level < PERMS['CHAT_FULL_CONTROL'] and not v.chat_authorized:
|
||||
if not v.can_access_chat:
|
||||
return abort(403)
|
||||
if v.admin_level < perm_level:
|
||||
return abort(403)
|
||||
kwargs['v'] = v
|
||||
return func(*args, **kwargs)
|
||||
|
@ -73,7 +75,7 @@ def send_system_reply(text):
|
|||
"username": "System",
|
||||
"text": text,
|
||||
"text_html": sanitize(text),
|
||||
'time': int(self.created_datetimez.timestamp()),
|
||||
'time': time.time(),
|
||||
}
|
||||
emit('speak', data)
|
||||
|
||||
|
@ -188,6 +190,21 @@ def typing_indicator(data, v):
|
|||
emit('typing', typing, broadcast=True)
|
||||
|
||||
|
||||
@socketio.on('read')
|
||||
@chat_is_allowed()
|
||||
def read(data, v):
|
||||
limiter.check()
|
||||
if v.is_banned: return '', 403
|
||||
|
||||
# This value gets truncated at some point in the pipeline and I haven't really spent time to figure out where.
|
||||
# Instead, we just bump it by one.
|
||||
timestamp = datetime.fromtimestamp(int(data) + 1)
|
||||
|
||||
v.chat_lastseen = timestamp
|
||||
g.db.add(v)
|
||||
g.db.commit()
|
||||
|
||||
|
||||
@socketio.on('delete')
|
||||
@chat_is_allowed(PERMS['CHAT_MODERATION'])
|
||||
def delete(id, v):
|
||||
|
|
|
@ -25,6 +25,14 @@
|
|||
<a class="mobile-nav-icon d-md-none" onclick="location.reload()"><i class="fas fa-arrow-rotate-right align-middle text-gray-500 black"></i></a>
|
||||
{% endif %}
|
||||
|
||||
{% if v.can_access_chat %}
|
||||
{% if v.unread_chat_messages_count > 0 %}
|
||||
<a class="mobile-nav-icon d-md-none pl-0" href="/chat" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Voting Booth"><i class="fas fa-booth align-middle text-danger"></i><span class="notif-count ml-1" style="padding-left: 4.5px;">{{v.unread_chat_messages_count}}</span></a>
|
||||
{% else %}
|
||||
<a class="mobile-nav-icon d-md-none" href="/chat" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Voting Booth"><i class="fas fa-booth align-middle text-gray-500 black"></i></a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if v %}
|
||||
{% if v.notifications_count %}
|
||||
<a class="mobile-nav-icon d-md-none pl-0" href="/notifications{% if v.do_posts %}/posts{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Notifications"><i class="fas fa-bell align-middle text-danger" {% if v.do_posts %}style="color:blue!important"{% endif %}></i><span class="notif-count ml-1" style="padding-left: 4.5px;{% if v.do_posts %}background:blue{% endif %}">{{v.notifications_count}}</span></a>
|
||||
|
@ -53,6 +61,18 @@
|
|||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ml-auto d-none d-md-flex">
|
||||
|
||||
{% if v.can_access_chat %}
|
||||
{% if v.unread_chat_messages_count > 0 %}
|
||||
<li class="nav-item d-flex align-items-center text-center justify-content-center mx-1">
|
||||
<a class="nav-link position-relative" href="/chat" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Voting Booth"><i class="fas fa-booth text-danger"></i><span class="notif-count ml-1" style="padding-left: 4.5px;">{{v.unread_chat_messages_count}}</span></a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item d-flex align-items-center text-center justify-content-center mx-1">
|
||||
<a class="nav-link" href="/chat" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Voting Booth"><i class="fas fa-booth"></i></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if v %}
|
||||
{% if v.notifications_count %}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue