var current_channel = null;
var private_manager = null;
var channel_manager = null;
- var bottom_callback;
- var ws_receive_msg;
- var ws_schemas = {"http:": "ws:", "https:": "wss:"};
-
- function no_bottom_callback() {
- bottom_callback = no_bottom_callback;
- }
- bottom_callback = no_bottom_callback;
+ var messages_manager = null;
function maybe_redirect(data) {
if (data.hasOwnProperty("Location")) {
url,
{
"abort": function (msg) {
- add_msg(null, "xhr_abort", msg);
+ messages_manager.add_msg(null, "xhr_abort", msg, null);
},
"error": function (msg) {
- add_msg(null, "xhr_error", msg);
+ messages_manager.add_msg(null, "xhr_error", msg, null);
},
"load": function () {
var response_data;
return get_data_id(_.dgEBCN0("user").children[0]);
}
- function set_msg(p, sender, msg) {
- _.clear_children(p);
- if (typeof sender === "number") {
- sender = private_manager.items_per_id[sender].username;
- }
- p.appendChild(_.dcTN(sender));
- p.appendChild(_.dcTN(": "));
- p.appendChild(_.dcTN(msg));
- return p;
- }
-
function make_tag_with_data_id(tag_name, data_id) {
var tag = document.createElement(tag_name);
- if (data_id !== null) {
+ if (data_id) {
tag.setAttribute("data-id", data_id);
}
return tag;
return a;
}
- function find_existing_msg(msg_id, callback) {
- var messages = _.dgEBCN0("messages");
- return _.foreach_arr(
- messages.children,
- function (item) {
- if (get_data_id(item) === msg_id) {
- callback(item);
- return true;
- }
- }
- );
- }
-
- function add_msg(id, user, text) {
- _.dgEBCN0("messages").appendChild(
- set_msg(make_tag_with_data_id("p", id), user, text)
- );
- }
-
- function add_msg_callback(item) {
- add_msg(item.id, item[current_channel.msg_user_field], item.text);
- }
-
function clear_messages_header() {
var messages_header = _.dgEBCN0("messages_header");
_.clear_children(messages_header.children[0]);
}
function clear_channel() {
- _.clear_children(_.dgEBCN0("messages"));
+ var messages_footer = _.dgEBCN0("messages_footer");
+ messages_manager.clear();
clear_messages_header();
_.dgEBCN0("messages_header").children[0].appendChild(_.dcTN("Chat"));
- _.dgEBCN0("messages_footer").children[0].disabled = true;
+ messages_footer.children[0].value = "";
+ messages_footer.children[0].disabled = true;
}
function set_channel(field_value) {
clear_channel();
if (this === window) {
current_channel = null;
- _.dgEBCN0("messages").appendChild(_.dcTN("No channel selected!"));
+ messages_manager.add_msg(null, null, "No channel selected!", null);
return;
}
current_channel = this;
+ "=" + field_value.toString()
),
function (msgs) {
- var messages = _.dgEBCN0("messages");
- // add a "load previous" button here
- _.foreach_arr(msgs.result, add_msg_callback);
- messages.scrollTop = (
- messages.scrollHeight - messages.clientHeight
- );
+ messages_manager.load_msgs_callback(msgs);
+ messages_manager.scroll_to_bottom();
}
);
}
}
function items_reload_callback(item) {
- var t = this;
+ var outer = this;
var li;
var a;
this.items_per_id[item.id] = item;
li = make_tag_with_data_id("li", item.id);
a = make_callback_a(
function () {
- t.set_channel(item.id);
+ outer.set_channel(item.id);
}
);
a.appendChild(this.item_to_tn(item));
}
function PrivateMessageManager() {
- var t = this;
+ var outer = this;
this.msg_url = "/api/privatemessage/";
this.msg_table = "chat_privatemessage";
this.msg_user_field = "sender_id";
data.result,
function (item) {
var p;
- items_reload_callback.call(t, item);
+ items_reload_callback.call(outer, item);
if (!item.is_authenticated) {
return;
}
_.dgEBCN0("user").appendChild(p);
}
);
- update_channel_header.call(t);
+ update_channel_header.call(outer);
}
);
};
}
function ChannelManager() {
- var t = this;
+ var outer = this;
this.msg_url = "/api/channelmessage/";
this.msg_table = "chat_channelmessage";
this.msg_user_field = "user_id";
});
messages_header.appendChild(btn);
messages_header.children[1].style.display = "none";
- // messages_header.style.display = "block";
};
this.items_url = "/api/channel/";
this.items_classname = "channels";
"get",
this.items_url,
function (data) {
- _.foreach_arr(data.result, items_reload_callback.bind(t));
- update_channel_header.call(t);
+ _.foreach_arr(
+ data.result,
+ items_reload_callback.bind(outer)
+ );
+ update_channel_header.call(outer);
}
);
};
this.items_reload();
}
- function stick_to_bottom(element) {
- if (element.clientHeight + element.scrollTop === element.scrollHeight) {
- bottom_callback = function () {
- element.scrollTop = element.scrollHeight - element.clientHeight;
- no_bottom_callback();
- };
- } else {
- no_bottom_callback();
- }
+ function Message(id, user, text) {
+ this.id = id;
+ this.p = make_tag_with_data_id("p", id);
+ this.update = function (user, text) {
+ this.user = user || this.user;
+ this.text = text || this.text;
+ _.clear_children(this.p);
+ if (this.user) {
+ if (typeof this.user === "number") {
+ user = private_manager.items_per_id[this.user].username;
+ } else {
+ user = this.user;
+ }
+ this.p.appendChild(_.dcTN(user));
+ this.p.appendChild(_.dcTN(": "));
+ }
+ this.p.appendChild(_.dcTN(this.text));
+ };
+ this.update(user, text);
}
- ws_receive_msg = {
- DELETE: function (data) {
- find_existing_msg(
- data.obj.id,
- function (tag) {
- tag.parentElement.removeChild(tag);
- }
- );
- },
- INSERT: function (data) {
- stick_to_bottom(_.dgEBCN0("messages"));
- xhr(
- "get",
- current_channel.msg_url + data.obj.id + "/",
- function (item) {
- add_msg_callback(item);
- bottom_callback();
- }
- );
- },
- TRUNCATE: function () {
- _.clear_children(_.dgEBCN0("messages"));
- },
- UPDATE: function (data) {
- var messages = _.dgEBCN0("messages");
- stick_to_bottom(messages);
- find_existing_msg(
- data.obj.id,
- function (tag) {
- stick_to_bottom(messages);
- xhr(
- "get",
- (
- current_channel.msg_url
- + get_data_id(tag).toString()
- + "/"
- ),
- function (msg) {
- set_msg(
- tag,
- msg[current_channel.msg_user_field],
- msg.text
- );
- bottom_callback();
- }
- );
- }
- );
+ function MessagesManager() {
+ var messages = _.dgEBCN0("messages");
+ this.msgs_by_id = {};
+ this.add_msg = function (id, user, text, next) {
+ var msg = new Message(id, user, text);
+ messages.insertBefore(msg.p, next || null);
+ if (id) {
+ this.msgs_by_id[id.toString(36)] = msg;
+ }
+ };
+ this.by_id = function (msg_id) {
+ return this.msgs_by_id[msg_id.toString(36)] || null;
+ };
+ this.clear = function () {
+ _.clear_attributes(this.msgs_by_id);
+ _.clear_children(messages);
+ };
+ this.remove = function (id) {
+ var msg = this.by_id(id);
+ if (msg) {
+ msg.p.parentElement.removeChild(msg.p);
+ delete this.msgs_by_id[msg.id.toString(36)];
+ }
+ };
+ this.previous_button = function (url) {
+ var p;
+ var button;
+ if (!url) {
+ return;
+ }
+ p = document.createElement("p");
+ button = document.createElement("button");
+ button.appendChild(_.dcTN("Load more"));
+ button.addEventListener("click", function () {
+ xhr(
+ "get",
+ url,
+ function (msgs) {
+ p.parentElement.removeChild(p);
+ messages_manager.load_msgs_callback(
+ msgs,
+ messages.firstElementChild
+ );
+ }
+ );
+ });
+ messages.insertBefore(p, messages.firstElementChild);
+ p.appendChild(button);
+ };
+ this.load_msgs_callback = function (msgs, next) {
+ this.previous_button(msgs.previous);
+ _.foreach_arr(msgs.result, function (item) {
+ messages_manager.add_msg(
+ item.id,
+ item[current_channel.msg_user_field],
+ item.text,
+ next
+ );
+ });
+ };
+ function no_bottom_callback() {
+ messages_manager.bottom_callback = no_bottom_callback;
}
- };
+ this.bottom_callback = no_bottom_callback;
+ this.stick_to_bottom = function () {
+ if (
+ messages.clientHeight + messages.scrollTop
+ === messages.scrollHeight
+ ) {
+ messages_manager.bottom_callback = function () {
+ messages_manager.scroll_to_bottom();
+ no_bottom_callback();
+ };
+ } else {
+ no_bottom_callback();
+ }
+ };
+ this.scroll_to_bottom = function () {
+ messages.scrollTop = (
+ messages.scrollHeight - messages.clientHeight
+ );
+ };
+ }
- function ws_receive(msg) {
- var data = JSON.parse(msg.data);
- maybe_redirect(data);
- // here we could add indication for the pm or channel being modified
- if (
- data.table === "chat_channel" || data.table === "chat_channeluser"
- ) {
- channel_manager.items_reload();
- } else if (data.table === "chat_user") {
- private_manager.items_reload();
- }
- if (current_channel !== null && current_channel.match_channel(data)) {
- ws_receive_msg[data.op](data);
+ function WebSocketManager() {
+ var ws_schemas = {"http:": "ws:", "https:": "wss:"};
+ var ws_receive_msg = {
+ DELETE: function (data) {
+ messages_manager.remove(data.obj.id);
+ },
+ INSERT: function (data) {
+ messages_manager.stick_to_bottom();
+ xhr(
+ "get",
+ current_channel.msg_url + data.obj.id + "/",
+ function (item) {
+ messages_manager.add_msg(
+ item.id,
+ item[current_channel.msg_user_field],
+ item.text,
+ null
+ );
+ messages_manager.bottom_callback();
+ }
+ );
+ },
+ TRUNCATE: function () {
+ messages_manager.clear();
+ },
+ UPDATE: function (data) {
+ var msg = messages_manager.by_id(data.obj.id);
+ if (!msg) {
+ return;
+ }
+ messages_manager.stick_to_bottom();
+ xhr(
+ "get",
+ (
+ current_channel.msg_url
+ + msg.id.toString()
+ + "/"
+ ),
+ function (data) {
+ msg.update(
+ data[current_channel.msg_user_field],
+ data.text
+ );
+ messages_manager.bottom_callback();
+ }
+ );
+ }
+ };
+ var ws = new WebSocket(
+ ws_schemas[window.location.protocol]
+ + "//"
+ + window.location.host
+ + "/"
+ );
+ function ws_receive(msg) {
+ var data;
+ try {
+ data = JSON.parse(msg.data);
+ } catch {
+ data = msg.data;
+ }
+ maybe_redirect(data);
+ if (typeof data === "string") {
+ console.log(data);
+ return;
+ }
+ // here we could add indication for the pm or channel being modified
+ if (
+ data.table === "chat_channel"
+ || data.table === "chat_channeluser"
+ ) {
+ channel_manager.items_reload();
+ } else if (data.table === "chat_user") {
+ private_manager.items_reload();
+ }
+ if (current_channel && current_channel.match_channel(data)) {
+ ws_receive_msg[data.op](data);
+ }
}
+ ws.addEventListener("message", ws_receive);
+ ws.addEventListener("close", function (event) {
+ event = event || window.event;
+ messages_manager.add_msg(null, "ws_close", "Closed", null);
+ // display reconnect button here
+ });
+ ws.addEventListener("error", function (event) {
+ event = event || window.event;
+ messages_manager.add_msg(null, "ws_error", event, null);
+ // display reconnect button here
+ });
}
function send_msg() {
var ta = _.dgEBCN0("messages_footer").children[0];
- var data = {"text": ta.value};
+ var data;
+ if (!current_channel || ta.value === "") {
+ return;
+ }
+ data = {"text": ta.value};
_.foreach_obj(
current_channel.msg_data,
function (name, value) {
data[name] = value;
}
);
- if (current_channel) {
- xhr("post", current_channel.msg_url, null, JSON.stringify(data));
- ta.value = "";
- }
+ ta.disabled = true;
+ xhr(
+ "post",
+ current_channel.msg_url,
+ function () {
+ ta.value = "";
+ ta.disabled = false;
+ },
+ JSON.stringify(data)
+ );
}
function input_onkeydown(event) {
}
}
- function logout() {
- window.location.href = "/logout/";
- }
-
window.addEventListener(
"load",
function (event) {
var messages_footer = _.dgEBCN0("messages_footer");
- var ws;
+ messages_manager = new MessagesManager();
set_channel.call(window);
event = event || window.event;
if (event.target.readyState !== "complete") {
}
private_manager = new PrivateMessageManager();
channel_manager = new ChannelManager();
- ws = new WebSocket(
- ws_schemas[window.location.protocol]
- + "//"
- + window.location.host
- + "/"
- );
- ws.addEventListener("message", ws_receive);
+ new WebSocketManager();
messages_footer.children[0].addEventListener(
"keydown",
input_onkeydown
);
messages_footer.children[0].value = "";
messages_footer.children[1].addEventListener("click", send_msg);
- _.dgEBCN0("messages_header").children[2].addEventListener(
- "click",
- logout
- );
}
);
}());
-from asgiref.sync import sync_to_async
from django.conf import settings
from django.contrib.auth.forms import SetPasswordForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import (
LoginView as DjangoLoginView,
)
-from django.contrib.auth.views import (
- LogoutView as DjangoLogoutView,
-)
-from django.contrib.staticfiles import finders
from django.core.mail import mail_admins
from django.core.signing import Signer
-from django.http import FileResponse, Http404, HttpResponseBase
+from django.http import Http404
from django.urls import reverse, reverse_lazy
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.safestring import mark_safe
-from django.views.generic import TemplateView, View
+from django.views.generic import TemplateView
from django.views.generic.detail import SingleObjectTemplateResponseMixin
from django.views.generic.edit import BaseCreateView, FormView, UpdateView
from .models import User
-class ChatView(LoginRequiredMixin, View):
- async def dispatch(self, request, *args, **kwargs):
- result = sync_to_async(super().dispatch)(request, *args, **kwargs)
- while not isinstance(result, HttpResponseBase):
- result = await result
- return result
+class ChatView(LoginRequiredMixin, TemplateView):
+ template_name = "chat/chat.html"
- async def get(self, request, *args, **kwargs):
- path = finders.find("chat/chat.html")
- with open(path) as fh:
- return FileResponse(fh.read(), filename=path, content_type="text/html")
+ @property
+ def title(self):
+ return str(self.request.user)
class LoginView(DjangoLoginView):
title = "Login"
-class LogoutView(DjangoLogoutView):
- http_method_names = ["post", "options", "get"]
- template_name = "chat/success.html"
- title = "Logout"
-
- def get(self, request, *args, **kwargs):
- return self.post(request, *args, **kwargs)
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context["msg"] = context["title"]
- return context
-
-
class PasswordResetView(FormView):
form_class = PasswordResetForm
template_name = "chat/login.html"
- title = "Reset Password"
success_url = reverse_lazy("chat-reset-password-success")
+ title = "Reset Password"
def form_valid(self, form):
"""If the form is valid, save the associated model."""