Compare commits
No commits in common. "a1afaacb2ea6ded2eb14eb6d8fbdaf34708d9568" and "1807cff67d3a4306f122d7df4436cc88137f299c" have entirely different histories.
a1afaacb2e
...
1807cff67d
@ -1,9 +1,7 @@
|
|||||||
import pathlib
|
import pathlib
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from snek.view.threads import ThreadsView
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
@ -114,7 +112,6 @@ class Application(BaseApplication):
|
|||||||
self.router.add_get("/http-photo", self.handle_http_photo)
|
self.router.add_get("/http-photo", self.handle_http_photo)
|
||||||
self.router.add_get("/rpc.ws", RPCView)
|
self.router.add_get("/rpc.ws", RPCView)
|
||||||
self.router.add_view("/channel/{channel}.html", WebView)
|
self.router.add_view("/channel/{channel}.html", WebView)
|
||||||
self.router.add_view("/threads.html", ThreadsView)
|
|
||||||
|
|
||||||
self.add_subapp(
|
self.add_subapp(
|
||||||
"/docs",
|
"/docs",
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
from snek.model.channel_message import ChannelMessageModel
|
|
||||||
from snek.system.model import BaseModel, ModelField
|
from snek.system.model import BaseModel, ModelField
|
||||||
|
|
||||||
|
|
||||||
@ -11,11 +10,3 @@ class ChannelModel(BaseModel):
|
|||||||
is_listed = ModelField(name="is_listed", required=True, kind=bool, value=True)
|
is_listed = ModelField(name="is_listed", required=True, kind=bool, value=True)
|
||||||
index = ModelField(name="index", required=True, kind=int, value=1000)
|
index = ModelField(name="index", required=True, kind=int, value=1000)
|
||||||
last_message_on = ModelField(name="last_message_on", required=False, kind=str)
|
last_message_on = ModelField(name="last_message_on", required=False, kind=str)
|
||||||
|
|
||||||
async def get_last_message(self)->ChannelMessageModel:
|
|
||||||
async for model in self.app.services.channel_message.query("SELECT * FROM channel_message WHERE channel_uid=:channel_uid ORDER BY created_at DESC LIMIT 1",dict(channel_uid=self['uid'])):
|
|
||||||
return model
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_members(self):
|
|
||||||
return await self.app.services.channel_member.find(channel_uid=self['uid'],deleted_at=None,is_banned=False)
|
|
||||||
@ -14,27 +14,3 @@ class ChannelMemberModel(BaseModel):
|
|||||||
is_muted = ModelField(name="is_muted", required=True, kind=bool, value=False)
|
is_muted = ModelField(name="is_muted", required=True, kind=bool, value=False)
|
||||||
is_banned = ModelField(name="is_banned", required=True, kind=bool, value=False)
|
is_banned = ModelField(name="is_banned", required=True, kind=bool, value=False)
|
||||||
new_count = ModelField(name="new_count", required=False, kind=int, value=0)
|
new_count = ModelField(name="new_count", required=False, kind=int, value=0)
|
||||||
|
|
||||||
async def get_user(self):
|
|
||||||
return await self.app.services.user.get(uid=self['user_uid'])
|
|
||||||
|
|
||||||
async def get_channel(self):
|
|
||||||
return await self.app.services.channel.get(uid=self['channel_uid'])
|
|
||||||
|
|
||||||
async def get_name(self):
|
|
||||||
if self["channel_uid"] == "dm":
|
|
||||||
user = await self.get_other_dm_user()
|
|
||||||
return user['nick']
|
|
||||||
channel = await self.get_channel()
|
|
||||||
return channel['name']
|
|
||||||
|
|
||||||
async def get_other_dm_user(self):
|
|
||||||
channel = await self.get_channel()
|
|
||||||
if channel["tag"] != "dm":
|
|
||||||
return None
|
|
||||||
|
|
||||||
async for model in self.app.services.channel_member.find(channel_uid=channel['uid']):
|
|
||||||
if model["uid"] != self['uid']:
|
|
||||||
return await self.app.services.user.get(uid=model["user_uid"])
|
|
||||||
return await self.get_user()
|
|
||||||
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
from snek.model.user import UserModel
|
|
||||||
from snek.system.model import BaseModel, ModelField
|
from snek.system.model import BaseModel, ModelField
|
||||||
|
|
||||||
|
|
||||||
@ -7,9 +6,3 @@ class ChannelMessageModel(BaseModel):
|
|||||||
user_uid = ModelField(name="user_uid", required=True, kind=str)
|
user_uid = ModelField(name="user_uid", required=True, kind=str)
|
||||||
message = ModelField(name="message", required=True, kind=str)
|
message = ModelField(name="message", required=True, kind=str)
|
||||||
html = ModelField(name="html", required=False, kind=str)
|
html = ModelField(name="html", required=False, kind=str)
|
||||||
|
|
||||||
async def get_user(self)->UserModel:
|
|
||||||
return await self.app.services.user.get(uid=self["user_uid"])
|
|
||||||
|
|
||||||
async def get_channel(self):
|
|
||||||
return await self.app.services.channel.get(uid=self["channel_uid"])
|
|
||||||
@ -31,7 +31,3 @@ class UserModel(BaseModel):
|
|||||||
password = ModelField(name="password", required=True, min_length=1)
|
password = ModelField(name="password", required=True, min_length=1)
|
||||||
|
|
||||||
last_ping = ModelField(name="last_ping", required=False, kind=str)
|
last_ping = ModelField(name="last_ping", required=False, kind=str)
|
||||||
|
|
||||||
async def get_channel_members(self):
|
|
||||||
async for channel_member in self.app.services.channel_member.find(user_uid=self['uid'],is_banned=False,deleted_at=None):
|
|
||||||
yield channel_member
|
|
||||||
|
|||||||
@ -32,9 +32,9 @@ class NotificationService(BaseService):
|
|||||||
user = await self.services.user.get(uid=channel_message["user_uid"])
|
user = await self.services.user.get(uid=channel_message["user_uid"])
|
||||||
async for channel_member in self.services.channel_member.find(
|
async for channel_member in self.services.channel_member.find(
|
||||||
channel_uid=channel_message["channel_uid"],
|
channel_uid=channel_message["channel_uid"],
|
||||||
is_banned=False,
|
#is_banned=False,
|
||||||
is_muted=False,
|
#is_muted=False,
|
||||||
deleted_at=None,
|
#deleted_at=None,
|
||||||
):
|
):
|
||||||
if not channel_member['new_count']:
|
if not channel_member['new_count']:
|
||||||
channel_member['new_count'] = 0
|
channel_member['new_count'] = 0
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class BaseMapper:
|
|||||||
return self.app.db
|
return self.app.db
|
||||||
|
|
||||||
async def new(self):
|
async def new(self):
|
||||||
return self.model_class(mapper=self, app=self.app)
|
return self.model_class(mapper=self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def table(self):
|
def table(self):
|
||||||
|
|||||||
@ -270,8 +270,7 @@ class BaseModel:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self._mapper = kwargs.get("mapper")
|
self._mapper = None
|
||||||
self.app = kwargs.get("app")
|
|
||||||
self.fields = {}
|
self.fields = {}
|
||||||
for key in dir(self.__class__):
|
for key in dir(self.__class__):
|
||||||
obj = getattr(self.__class__, key)
|
obj = getattr(self.__class__, key)
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
<a class="no-select" href="/web.html">🏠</a>
|
<a class="no-select" href="/web.html">🏠</a>
|
||||||
<a class="no-select" href="/search-user.html">🔍</a>
|
<a class="no-select" href="/search-user.html">🔍</a>
|
||||||
<a class="no-select" style="display:none" id="install-button" href="#">📥</a>
|
<a class="no-select" style="display:none" id="install-button" href="#">📥</a>
|
||||||
<a class="no-select" href="/threads.html">👥</a>
|
<a class="no-select" href="/web.html">👥</a>
|
||||||
<a class="no-select" href="#">⚙️</a>
|
<a class="no-select" href="#">⚙️</a>
|
||||||
<a class="no-select" href="/logout.html">đź”’</a>
|
<a class="no-select" href="/logout.html">đź”’</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@ -1,153 +0,0 @@
|
|||||||
{% extends "app.html" %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
<section class="chat-area" id="chat">
|
|
||||||
<div class="chat-header">
|
|
||||||
<h2>?</h2>
|
|
||||||
</div>
|
|
||||||
<div class="chat-messages">
|
|
||||||
{% for thread in threads %}
|
|
||||||
{% autoescape false %}
|
|
||||||
<div style="max-width:100%;" data-uid="{{thread.uid}}" data-color="{{thread.last_message_user_color}}" data-channel_uid="{{thread.uid}}"
|
|
||||||
data-user_nick="{{last_message_user_nick}}" data-created_at="{{thread.created_at}}" data-user_uid="{{user_uid}}"
|
|
||||||
class="message">
|
|
||||||
<div class="avatar" style="background-color: {{thread.last_message_user_color}}; color: black;"><img
|
|
||||||
src="/avatar/{{thread.last_message_user_uid}}.svg" /></div>
|
|
||||||
<div class="message-content">
|
|
||||||
<div class="author" style="color: {{thread.last_message_user_color}};">{{thread.last_message_user_nick}}</div>
|
|
||||||
<div class="text">{% autoescape false %}{% emoji %}{% linkify %}{% markdown %}{% autoescape false %}{{ thread.last_message_text }}{%raw %} {% endraw%}{%endautoescape%}{% endmarkdown %}{% endlinkify %}{% endemoji %}{%
|
|
||||||
endautoescape %}</div>
|
|
||||||
<div class="time no-select" data-created_at="{{thread.created_at}}"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endautoescape %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<chat-window style="display:none" class="chat-area"></chat-window>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function updateTimes() {
|
|
||||||
document.querySelectorAll(".time").forEach((time) => {
|
|
||||||
time.innerText = app.timeDescription(time.dataset.created_at);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isElementVisible(element) {
|
|
||||||
const rect = element.getBoundingClientRect();
|
|
||||||
return (
|
|
||||||
rect.top >= 0 &&
|
|
||||||
rect.left >= 0 &&
|
|
||||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
||||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const messagesContainer = document.querySelector(".chat-messages");
|
|
||||||
|
|
||||||
function isScrolledPastHalf() {
|
|
||||||
let scrollTop = messagesContainer.scrollTop;
|
|
||||||
let scrollableHeight = messagesContainer.scrollHeight - messagesContainer.clientHeight;
|
|
||||||
|
|
||||||
if (scrollTop < scrollableHeight / 2) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let isLoadingExtra = false;
|
|
||||||
|
|
||||||
async function loadExtra() {
|
|
||||||
const firstMessage = messagesContainer.querySelector(".message:first-child");
|
|
||||||
if (isLoadingExtra) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isScrolledPastHalf()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoadingExtra = true;
|
|
||||||
|
|
||||||
const messages = await app.rpc.getMessages(channelUid, 0, firstMessage.dataset.created_at);
|
|
||||||
|
|
||||||
messages.forEach((message) => {
|
|
||||||
firstMessage.insertAdjacentHTML("beforebegin", message.html);
|
|
||||||
})
|
|
||||||
updateLayout(false);
|
|
||||||
|
|
||||||
isLoadingExtra = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
messagesContainer.addEventListener("scroll", () => {
|
|
||||||
loadExtra();
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateLayout(doScrollDown) {
|
|
||||||
const messagesContainer = document.querySelector(".chat-messages");
|
|
||||||
updateTimes();
|
|
||||||
let previousUser = null;
|
|
||||||
document.querySelectorAll(".message").forEach((message) => {
|
|
||||||
if (previousUser !== message.dataset.user_uid) {
|
|
||||||
message.classList.add("switch-user");
|
|
||||||
previousUser = message.dataset.user_uid;
|
|
||||||
} else {
|
|
||||||
message.classList.remove("switch-user");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
lastMessage = messagesContainer.querySelector(".message:last-child");
|
|
||||||
if (doScrollDown) {
|
|
||||||
lastMessage.scrollIntoView({ inline: "nearest" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setInterval(updateTimes, 1000);
|
|
||||||
|
|
||||||
function isMentionToMe(message) {
|
|
||||||
const mentionText = '@{{ user.username.value }}';
|
|
||||||
return message.toLowerCase().includes(mentionText);
|
|
||||||
}
|
|
||||||
function extractMentions(message) {
|
|
||||||
return [...new Set(message.match(/@\w+/g) || [])];
|
|
||||||
}
|
|
||||||
function isMentionForSomeoneElse(message) {
|
|
||||||
const mentions = extractMentions(message);
|
|
||||||
const mentionText = '@{{ user.username.value }}';
|
|
||||||
return mentions.length > 0 && mentions.indexOf(mentionText) == -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.addEventListener("channel-message", (data) => {
|
|
||||||
if (data.channel_uid !== channelUid) {
|
|
||||||
if (!isMentionForSomeoneElse(data.message)) {
|
|
||||||
channelSidebar.notify(data);
|
|
||||||
app.playSound("messageOtherChannel");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.username !== "{{ user.username.value }}") {
|
|
||||||
if (isMentionToMe(data.message)) {
|
|
||||||
app.playSound("mention");
|
|
||||||
} else if (!isMentionForSomeoneElse(data.message)) {
|
|
||||||
app.playSound("message");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const messagesContainer = document.querySelector(".chat-messages");
|
|
||||||
const lastMessage = messagesContainer.querySelector(".message:last-child");
|
|
||||||
const doScrollDownBecauseLastMessageIsVisible = !lastMessage || isElementVisible(lastMessage);
|
|
||||||
|
|
||||||
const message = document.createElement("div");
|
|
||||||
message.innerHTML = data.html;
|
|
||||||
document.querySelector(".chat-messages").appendChild(message.firstChild);
|
|
||||||
updateLayout(doScrollDownBecauseLastMessageIsVisible);
|
|
||||||
setTimeout(() => {
|
|
||||||
updateLayout(doScrollDownBecauseLastMessageIsVisible)
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
initInputField(document.querySelector("textarea"));
|
|
||||||
updateLayout(true);
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
from snek.system.view import BaseView
|
|
||||||
|
|
||||||
class ThreadsView(BaseView):
|
|
||||||
|
|
||||||
async def get(self):
|
|
||||||
threads = []
|
|
||||||
user = await self.services.user.get(uid=self.session.get("uid"))
|
|
||||||
async for channel_member in user.get_channel_members():
|
|
||||||
thread = {}
|
|
||||||
channel = await self.services.channel.get(uid=channel_member["channel_uid"])
|
|
||||||
thread["uid"] = channel['uid']
|
|
||||||
thread["name"] = await channel_member.get_name()
|
|
||||||
thread["new_count"] = channel_member["new_count"]
|
|
||||||
thread["last_message_on"] = channel["last_message_on"]
|
|
||||||
thread['created_at'] = thread['last_message_on']
|
|
||||||
last_message = await channel.get_last_message()
|
|
||||||
if last_message:
|
|
||||||
thread["last_message_text"] = last_message["message"]
|
|
||||||
thread['last_message_user_uid'] = last_message["user_uid"]
|
|
||||||
user_last_message = await self.app.services.user.get(uid=last_message["user_uid"])
|
|
||||||
thread['last_message_user_nick'] = user_last_message["nick"]
|
|
||||||
thread['last_message_user_color'] = user_last_message['color']
|
|
||||||
else:
|
|
||||||
thread["last_message_text"] = None
|
|
||||||
thread['last_message_user_uid'] = None
|
|
||||||
thread['last_message_user_nick'] = None
|
|
||||||
thread['last_message_user_color'] = None
|
|
||||||
threads.append(thread)
|
|
||||||
|
|
||||||
|
|
||||||
return await self.render_template("threads.html", dict(threads=threads,user=user))
|
|
||||||
Loading…
Reference in New Issue
Block a user