Update.
This commit is contained in:
parent
2deb8a2069
commit
f770dcf2db
@ -39,19 +39,20 @@ CREATE TABLE IF NOT EXISTS channel (
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_channel_e2577dd78b54fe28 ON channel (uid);
|
||||
CREATE TABLE IF NOT EXISTS channel_member (
|
||||
id INTEGER NOT NULL,
|
||||
channel_uid TEXT,
|
||||
created_at TEXT,
|
||||
deleted_at TEXT,
|
||||
is_banned BOOLEAN,
|
||||
is_moderator BOOLEAN,
|
||||
is_muted BOOLEAN,
|
||||
is_read_only BOOLEAN,
|
||||
label TEXT,
|
||||
new_count BIGINT,
|
||||
uid TEXT,
|
||||
updated_at TEXT,
|
||||
user_uid TEXT,
|
||||
id INTEGER NOT NULL,
|
||||
channel_uid TEXT,
|
||||
created_at TEXT,
|
||||
deleted_at TEXT,
|
||||
is_banned BOOLEAN,
|
||||
is_moderator BOOLEAN,
|
||||
is_muted BOOLEAN,
|
||||
is_read_only BOOLEAN,
|
||||
label TEXT,
|
||||
new_count BIGINT,
|
||||
last_read_at TEXT,
|
||||
uid TEXT,
|
||||
updated_at TEXT,
|
||||
user_uid TEXT,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS ix_channel_member_e2577dd78b54fe28 ON channel_member (uid);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
from snek.system.service import BaseService
|
||||
from snek.system.model import now
|
||||
|
||||
|
||||
class ChannelMemberService(BaseService):
|
||||
@ -8,6 +9,7 @@ class ChannelMemberService(BaseService):
|
||||
async def mark_as_read(self, channel_uid, user_uid):
|
||||
channel_member = await self.get(channel_uid=channel_uid, user_uid=user_uid)
|
||||
channel_member["new_count"] = 0
|
||||
channel_member["last_read_at"] = now()
|
||||
return await self.save(channel_member)
|
||||
|
||||
async def get_user_uids(self, channel_uid):
|
||||
|
||||
@ -10,6 +10,9 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit/lib/xterm-addon-fit.js"></script>
|
||||
|
||||
<div id="terminal" class="hidden"></div>
|
||||
<button id="jump-to-unread-btn" style="display: none; position: absolute; top: 10px; right: 10px; z-index: 1000; padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.2);">
|
||||
Jump to First Unread
|
||||
</button>
|
||||
<message-list class="chat-messages">
|
||||
{% if not messages %}
|
||||
|
||||
@ -300,7 +303,76 @@ function isScrolledPastHalf() {
|
||||
// --- Initial layout update ---
|
||||
updateLayout(true);
|
||||
|
||||
// --- Jump to unread functionality ---
|
||||
const jumpToUnreadBtn = document.getElementById('jump-to-unread-btn');
|
||||
let firstUnreadMessageUid = null;
|
||||
|
||||
async function checkForUnreadMessages() {
|
||||
try {
|
||||
const uid = await app.rpc.getFirstUnreadMessageUid(channelUid);
|
||||
if (uid) {
|
||||
firstUnreadMessageUid = uid;
|
||||
const messageElement = messagesContainer.querySelector(`[data-uid="${uid}"]`);
|
||||
if (messageElement && !messagesContainer.isElementVisible(messageElement)) {
|
||||
jumpToUnreadBtn.style.display = 'block';
|
||||
} else {
|
||||
jumpToUnreadBtn.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
jumpToUnreadBtn.style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking for unread messages:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function jumpToUnread() {
|
||||
if (!firstUnreadMessageUid) {
|
||||
await checkForUnreadMessages();
|
||||
}
|
||||
|
||||
if (firstUnreadMessageUid) {
|
||||
let messageElement = messagesContainer.querySelector(`[data-uid="${firstUnreadMessageUid}"]`);
|
||||
|
||||
if (!messageElement) {
|
||||
const messages = await app.rpc.getMessages(channelUid, 0, null);
|
||||
const targetMessage = messages.find(m => m.uid === firstUnreadMessageUid);
|
||||
|
||||
if (targetMessage) {
|
||||
const temp = document.createElement("div");
|
||||
temp.innerHTML = targetMessage.html;
|
||||
const newMessageElement = temp.firstChild;
|
||||
|
||||
messagesContainer.endOfMessages.after(newMessageElement);
|
||||
messagesContainer.messageMap.set(targetMessage.uid, newMessageElement);
|
||||
messagesContainer._observer.observe(newMessageElement);
|
||||
|
||||
messageElement = newMessageElement;
|
||||
}
|
||||
}
|
||||
|
||||
if (messageElement) {
|
||||
messageElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
messageElement.style.animation = 'highlight-fade 2s';
|
||||
setTimeout(() => {
|
||||
messageElement.style.animation = '';
|
||||
}, 2000);
|
||||
jumpToUnreadBtn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jumpToUnreadBtn.addEventListener('click', jumpToUnread);
|
||||
checkForUnreadMessages();
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes highlight-fade {
|
||||
0% { background-color: rgba(255, 255, 0, 0.3); }
|
||||
100% { background-color: transparent; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@ -175,6 +175,26 @@ class RPCView(BaseView):
|
||||
messages.append(extended_dict)
|
||||
return messages
|
||||
|
||||
async def get_first_unread_message_uid(self, channel_uid):
|
||||
self._require_login()
|
||||
channel_member = await self.services.channel_member.get(
|
||||
channel_uid=channel_uid, user_uid=self.user_uid
|
||||
)
|
||||
if not channel_member:
|
||||
return None
|
||||
|
||||
last_read_at = channel_member.get("last_read_at")
|
||||
if not last_read_at:
|
||||
return None
|
||||
|
||||
async for message in self.services.channel_message.query(
|
||||
"SELECT uid FROM channel_message WHERE channel_uid=:channel_uid AND created_at > :last_read_at AND deleted_at IS NULL ORDER BY created_at ASC LIMIT 1",
|
||||
{"channel_uid": channel_uid, "last_read_at": last_read_at}
|
||||
):
|
||||
return message["uid"]
|
||||
|
||||
return None
|
||||
|
||||
async def get_channels(self):
|
||||
self._require_login()
|
||||
channels = []
|
||||
|
||||
Loading…
Reference in New Issue
Block a user