master #6

Open
karimov-adel wants to merge 39 commits from master into adel_branch_2.0
20 changed files with 452 additions and 150 deletions
Showing only changes of commit b9626aa860 - Show all commits

View File

@ -17,8 +17,8 @@
<h1 class="popup-window-msg">Nickname for summoned user</h1> <h1 class="popup-window-msg">Nickname for summoned user</h1>
<input class="one-line-input" id="summoned-user-nickname"> <input class="one-line-input" id="summoned-user-nickname">
<input type="checkbox" id="summoned-user-is-read-only"> <input type="checkbox" id="summoned-user-is-read-only">
<label>Make read only</label> <label>Make read only</label><br>
<button class="popup-window-btn-yes" id="user-summoning-yes">Summon</button> <button class="popup-window-btn-yes" id="user-summoning-yes">Yes, summon</button>
<button class="popup-window-btn-no" id="user-summoning-no">No, cancel</button> <button class="popup-window-btn-no" id="user-summoning-no">No, cancel</button>
</div> </div>
@ -29,7 +29,7 @@
<button class="popup-window-btn-no" id="user-deletion-no">No, cancel</button> <button class="popup-window-btn-no" id="user-deletion-no">No, cancel</button>
</div> </div>
<div id="document-container"> <div class="document-container resp-container">
<div id="navigation-panel" class="panel"> <div id="navigation-panel" class="panel">
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> <a href="/list-rooms" id="go-to-chat-list" class="panel-thing">
<img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px"> <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">

View File

@ -14,14 +14,24 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/assets/img/favicon.png"> <link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/debug.css">
<link rel="stylesheet" href="/assets/css/common.css"> <link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/common-popup.css">
<link rel="stylesheet" href="/assets/css/chat.css"> <link rel="stylesheet" href="/assets/css/chat.css">
<title>Chat</title> <title>Chat</title>
</head> </head>
<body> <body>
<!-- todo: Write the actual chat script --> {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
<!--% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %-->
<div id="fullscreen-container"> <div id="msg-deletion-win" class="popup-window">
<h1 class="popup-window-msg">Are you sure you want to delete this message?</h1>
<!-- message preview will be actually rewritten before each window activation-->
<p class="message-in-popup-preview" id="win-deletion-msg-preview">Lorem ipsum dolor</p>
<button class="popup-window-btn-yes" id="msg-deletion-yes">Yes, delete</button>
<button class="popup-window-btn-no" id="msg-deletion-no">No, cancel</button>
</div>
<div class="fullscreen-container resp-container">
<div class="panel" id="navigation-info-panel"> <div class="panel" id="navigation-info-panel">
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> <a href="/list-rooms" id="go-to-chat-list" class="panel-thing">
<img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px"> <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
@ -34,10 +44,15 @@
<img alt="Settings of chat. List of members" src="/assets/img/settings-iron.svg" width="32px"> <img alt="Settings of chat. List of members" src="/assets/img/settings-iron.svg" width="32px">
</a> </a>
</div> </div>
<div id="chat-widget"></div> <div id="chat-widget">
<div class="chat-debug-rect chat-debug-rect-top" id="debug-line-highest"></div>
<div class="chat-debug-rect" id="debug-line-lowest"></div>
</div>
<div class="panel" id="input-panel"> <div class="panel" id="input-panel">
<div contentEditable id="message-input" class="panel-thing"></div> <div contentEditable id="message-input" class="panel-thing"></div>
</div> </div>
<script src="/assets/js/common.js"></script>
<script src="/assets/js/common-popup.js"></script>
<script src="/assets/js/chat.js"></script> <script src="/assets/js/chat.js"></script>
</div> </div>
</body> </body>

View File

@ -11,7 +11,7 @@
</head> </head>
<body> <body>
<div id="document-container"> <div class="document-container">
<div id="navigation-panel" class="panel"> <div id="navigation-panel" class="panel">
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> <a href="/list-rooms" id="go-to-chat-list" class="panel-thing">
<img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px"> <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">
@ -35,7 +35,7 @@
</p> </p>
</div> </div>
<div class="profile-container"> <div class="profile-container resp-container">
<h1 class="wide-centered-header">Change user attributes</h1> <h1 class="wide-centered-header">Change user attributes</h1>
<form action = "/user/{% WRITE alienprofile.nickname %}" method="post" enctype="application/x-www-form-urlencoded"> <form action = "/user/{% WRITE alienprofile.nickname %}" method="post" enctype="application/x-www-form-urlencoded">
<table class="logins-input-table"> <table class="logins-input-table">

View File

@ -49,7 +49,7 @@
</div> </div>
x x
<div id="document-container"> <div class="document-container resp-container">
<div id="navigation-panel" class="panel"> <div id="navigation-panel" class="panel">
<a href="/user/{% WRITE userinfo.nickname %}" id="go-to-my-profile" class="panel-thing"> <a href="/user/{% WRITE userinfo.nickname %}" id="go-to-my-profile" class="panel-thing">
<img alt="Go to my profile" src="/assets/img/user.svg" width="32px"> <img alt="Go to my profile" src="/assets/img/user.svg" width="32px">

View File

@ -11,7 +11,7 @@
</head> </head>
<body> <body>
<div id="document-container"> <div class="document-container resp-container">
<div id="navigation-panel" class="panel"> <div id="navigation-panel" class="panel">
<a href="/list-rooms" id="go-to-chat-list" class="panel-thing"> <a href="/list-rooms" id="go-to-chat-list" class="panel-thing">
<img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px"> <img alt="Go to list of chats" src="/assets/img/list-rooms.svg" width="32px">

View File

@ -10,16 +10,44 @@ body, html {
overflow: hidden; overflow: hidden;
} }
#chat-widget .message-box { .message-box-mine {
position: absolute; position: absolute;
right: 5px; right: 5px;
max-width: 300px; max-width: 400px;
border: 2px solid #82a173;
padding: 5px;
background-color: #cdff9b;
color: black;
}
.message-box-alien {
position: absolute;
left: 5px;
max-width: 400px;
border: 2px solid dimgrey; border: 2px solid dimgrey;
padding: 5px; padding: 5px;
background-color: white; background-color: white;
color: black; color: black;
} }
/* Only non-system messages can be deleted. Deleted messages do not have delete button
This class should be used with (and, ofcourse, after) class message-box-my/message-box-alien */
.message-box-deleted {
font-weight: bold;
border: 2px solid #cb0005;
background-color: #ffc1bc;
}
.message-box-system {
position: absolute;
left: 15px;
max-width: 500px;
padding: 4px;
background-color: #2d2d2d;
color: white;
font-weight: bold;
}
/* in #chat-widget .message-box */ /* in #chat-widget .message-box */
.message-box-top{ .message-box-top{
/* You see, each message contains a 20+2+2 px high icon that HAS TO BE LOADED FIRST. /* You see, each message contains a 20+2+2 px high icon that HAS TO BE LOADED FIRST.
@ -30,11 +58,18 @@ body, html {
} }
.message-box-sender-name{ .message-box-sender-name{
font-weight: bold;
color: black; color: black;
text-decoration: none; text-decoration: none;
padding: 2px; padding: 2px;
display: inline; display: inline;
font-size: 0.8em;
}
/* Additional to message-box-sender-name */
.message-box-sender-shortname {
font-weight: bold;
padding-left: 3px;
font-size: 0.94em;
} }
.message-box-sender-name:hover{ .message-box-sender-name:hover{
@ -51,7 +86,11 @@ body, html {
word-wrap: break-word; word-wrap: break-word;
} }
#input-panel #message-input{ #input-panel {
min-height: 20px;
}
#message-input {
padding: 15px; padding: 15px;
height: auto; height: auto;
width: 100%; width: 100%;
@ -62,3 +101,13 @@ body, html {
font-size: .9rem; font-size: .9rem;
margin: 10px; margin: 10px;
} }
.message-in-popup-preview{
border: 4px solid red;
width: 80%;
max-width: 200px;
margin-left: auto;
margin-right: auto;
max-height: 20%;
word-wrap: break-word;
}

View File

@ -54,12 +54,12 @@
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }
#document-container { .document-container {
width: 80%; /* Full width of the viewport */ width: 80%; /* Full width of the viewport */
margin: 0 auto; /* Center the container horizontally */ margin: 0 auto; /* Center the container horizontally */
} }
#fullscreen-container { .fullscreen-container {
width: 80%; /* Full width of the viewport */ width: 80%; /* Full width of the viewport */
height: 100vh; /* Full height of the viewport */ height: 100vh; /* Full height of the viewport */
display: flex; display: flex;
@ -67,6 +67,18 @@
margin: 0 auto; /* Center the container horizontally */ margin: 0 auto; /* Center the container horizontally */
} }
@media (orientation: landscape) {
.resp-container{
width: 80%;
}
}
@media (orientation: portrait){
.resp-container{
width: 100%;
}
}
body { body {
background-color: #f000f0; background-color: #f000f0;
background-image: url("/assets/img/clavicle-transparent.png"), url("/assets/img/broken-clavicle.png"); background-image: url("/assets/img/clavicle-transparent.png"), url("/assets/img/broken-clavicle.png");

12
assets/css/debug.css Normal file
View File

@ -0,0 +1,12 @@
.chat-debug-rect{
width: 100%;
position: absolute;
left: 0;
background-color: rgba(255, 50, 50, 160);
height: 4px;
z-index: 2;
}
.chat-debug-rect-top{
background-color: rgba(148, 0, 211, 160);
}

View File

@ -76,12 +76,10 @@ function updateLocalStateFromChatUpdResp(chatUpdResp){
// If my role is updated, we need to update all the boes of already set users (kick button can appear and disappear) // If my role is updated, we need to update all the boes of already set users (kick button can appear and disappear)
let literalMemberList = document.getElementById("CM-list"); let literalMemberList = document.getElementById("CM-list");
// We ignore messages and everything related to them. Dang, I really should add an argument to disable message lookup here // We ignore messages and everything related to them. Dang, I really should add an argument to disable message lookup here
// let haveToUpdateAllBoxes = false;
for (let memberSt of chatUpdResp.members){ for (let memberSt of chatUpdResp.members){
console.log([memberSt, userinfo.uid, myRoleHere]); console.log([memberSt, userinfo.uid, myRoleHere]);
if (memberSt.userId === userinfo.uid && myRoleHere !== memberSt.roleHere){ if (memberSt.userId === userinfo.uid && myRoleHere !== memberSt.roleHere){
myRoleHere = memberSt.roleHere; myRoleHere = memberSt.roleHere;
// haveToUpdateAllBoxes = true;
for (let [id, memberSt] of members){ for (let [id, memberSt] of members){
let box = memberBoxes.get(id); let box = memberBoxes.get(id);
updateBoxWithSt(box, memberSt); updateBoxWithSt(box, memberSt);
@ -115,7 +113,7 @@ function configureSummonUserInterface(){
document.getElementById("user-summoning-yes").onclick = function(ev ){ document.getElementById("user-summoning-yes").onclick = function(ev ){
if (ev.button !==0) if (ev.button !==0)
return; return;
let nickname = document.getElementById("summoned-user-nickname").value; let nickname = String(document.getElementById("summoned-user-nickname").value);
let isReadOnly = document.getElementById("summoned-user-is-read-only").checked; let isReadOnly = document.getElementById("summoned-user-is-read-only").checked;
deactivateActivePopup(); deactivateActivePopup();
let Sent = genSentBase(); let Sent = genSentBase();
@ -174,8 +172,7 @@ function configureKickUserInterfaceWinPart(){
__mainloopDelayMS = 5000; __mainloopDelayMS = 5000;
__guestMainloopPollerAction = function (){ __guestMainloopPollerAction = function (){
console.log("Hello, world"); console.log("Hello, world");
let Sent = genSentBase(); apiRequest("chatPollEvents", genSentBase()).
apiRequest("chatPollEvents", Sent).
then((Recv) => { then((Recv) => {
console.log(Recv); console.log(Recv);
updateLocalStateFromRecv(Recv); updateLocalStateFromRecv(Recv);

View File

@ -1,17 +1,6 @@
// In real world, I would get this variables from server through nytl let LocalHistoryId = 0;
let pres = {lang: ''}
let userinfo = {id: 2, nickname: 'pv', name: 'Pavlov Vladimir'}; let members = new Map();
let openedchat = {id: 100, name: "Some chat", nickname: 'chat'};
let initial_chatUpdResp = {
LocalHistoryId: 0,
lastMsgId: -1,
messages: [],
members: [
{id: 1, name: 'grisha', nickname: 'gri', role: 'admin'},
{id: 2, name: 'Pavlov Vladimir', nickname: 'pv', role: 'regular'},
{id: 3, name: 'Ivan', nickname: 'ivan', role: 'read-only'}
]
};
let loadedMessages = new Map(); // messageSt objects let loadedMessages = new Map(); // messageSt objects
let visibleMessages = new Map(); // HTMLElement objects let visibleMessages = new Map(); // HTMLElement objects
@ -19,14 +8,37 @@ let visibleMessages = new Map(); // HTMLElement objects
let anchoredMsg = -1; let anchoredMsg = -1;
let visibleMsgSegStart = -1; let visibleMsgSegStart = -1;
let visibleMsgSegEnd = -2; let visibleMsgSegEnd = -2;
let offsetOfAnchor = 100; let offsetOfAnchor = 500;
let highestPoint = null;
let lowestPoint = null;
let lastMsgId = -1;
let myRoleHere = null; // Dung local state updates should be updated first
// Would start with true if opened `/chat/<>` // Would start with true if opened `/chat/<>`
let bumpedAtTheBottom = false; let bumpedAtTheBottom = false;
let members = new Map(); // Hidden variable. When deletion window popup is active
for (let memberSt of initial_chatUpdResp.members){ // Persists from popup activation until popup deactivation
members.set(memberSt.id, memberSt); let storeHiddenMsgIdForDeletionWin = -1;
// Positive in production, negative for debug
let softZoneSz = -150;
let chatPadding = 300;
function genSentBase(){
return {
'chatUpdReq': {
'LocalHistoryId': LocalHistoryId,
'chatId': openedchat.id
}
};
}
function genSentBaseGMN(){
let Sent = genSentBase();
Sent.amount = 1;
return Sent;
} }
function updateOffsetOfVisibleMsg(msgId, offset){ function updateOffsetOfVisibleMsg(msgId, offset){
@ -54,59 +66,117 @@ function updateOffsetsDown(){
} }
function updateOffsetsSane(){ function updateOffsetsSane(){
let highest_point = updateOffsetsUpToTop(); if (anchoredMsg < 0)
let lowest_point = updateOffsetsDown(); return;
return [highest_point, lowest_point]; highestPoint = updateOffsetsUpToTop();
lowestPoint = updateOffsetsDown();
} }
function updateOffsets(){ function updateOffsets(){
if (anchoredMsg < 0) if (anchoredMsg < 0)
return; return;
let [highest_point, lowest_point] = updateOffsetsSane(); updateOffsetsSane()
let winTop = document.getElementById("chat-widget").offsetHeight; let winTop = document.getElementById("chat-widget").offsetHeight;
if (lowest_point > 5 || (highest_point - lowest_point) <= winTop){ if (lowestPoint > chatPadding || (highestPoint - lowestPoint) <= winTop){
bumpedAtTheBottom = true; bumpedAtTheBottom = true;
anchoredMsg = visibleMsgSegEnd; anchoredMsg = visibleMsgSegEnd;
offsetOfAnchor = 5; offsetOfAnchor = chatPadding;
updateOffsetsSane(); updateOffsetsSane();
} else if (highest_point < winTop - 5){ } else if (highestPoint < winTop - chatPadding) {
console.log("Adancing by " + (winTop - 5 - highest_point)) console.log("Advancing by " + (winTop - chatPadding - highestPoint))
offsetOfAnchor += (winTop - 5 - highest_point); offsetOfAnchor += (winTop - chatPadding - highestPoint);
updateOffsetsSane(); updateOffsetsSane();
} }
} }
function makeMessageBox(messageSt){ function shouldShowDeleteMesgBtn(messageSt){
if (!messageSt.exists || messageSt.isSystem) return !messageSt.isSystem && messageSt.exists && (
throw new Error("Not ready for this"); messageSt.myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid);
const parentDiv = document.getElementById("chat-widget"); }
function getMsgTypeClassSenderBased(messageSt){
if (messageSt.isSystem)
return "message-box-system"
if (messageSt.senderUserId === userinfo.uid)
return "message-box-mine"
return "message-box-alien";
}
function getMsgFullTypeClassName(messageSt){
return getMsgTypeClassSenderBased(messageSt) + (messageSt.exists ? "" : " message-box-deleted");
}
/* Two things can be updated: messages existance and delete button visibility */
function updateMessageBox(id, box, messageSt){
box.querySelector(".message-box-button-delete").style.display = shouldShowDeleteMesgBtn(messageSt) ? "block" : "none";
box.className = getMsgFullTypeClassName(messageSt);
// Notice, that no check of previous state is performed. Double loading is a rare event,
// and I can afford to be slow
if (!messageSt.exists)
box.querySelector(".message-box-msg").innerText = msgErased;
}
function decodeSystemMessage(text){
let [subject, verb, object] = text.split(',');
let subjectId = Number(subject);
let objectId = Number(object);
let subjectRef = members.has(subjectId) ? members.get(subjectId).nickname : "???";
let objectRef = members.has(objectId) ? members.get(objectId).nickname : "???";
if (verb === "kicked"){
return subjectRef + " kicked " + objectRef;
} else if (verb === "summoned"){
return subjectRef + " summoned " + objectId;
} else if (verb === "left"){
return subjectRef + " left chat";
} else if (verb === "joined"){
return subjectId + " joined chat";
} else if (verb === "created"){
return subjectId + " created this chat";
}
return "... Bad log ...";
}
function convertMessageStToBox(messageSt){
let box = document.createElement("div"); let box = document.createElement("div");
parentDiv.appendChild(box); box.className = getMsgFullTypeClassName(messageSt);
box.className = "message-box";
let ID = messageSt.id;
let topPart = document.createElement("div"); let topPart = document.createElement("div");
box.appendChild(topPart); box.appendChild(topPart);
topPart.className = "message-box-top"; topPart.className = "message-box-top";
if (!members.has(messageSt.senderUserId))
throw new Error("First - update members");
let senderMemberSt = members.get(messageSt.senderUserId);
let senderProfileURI = "/user/" + senderMemberSt.nickname;
let inTopPartSenderName = document.createElement("a"); let inTopPartSenderName = document.createElement("a");
topPart.appendChild(inTopPartSenderName); topPart.appendChild(inTopPartSenderName);
inTopPartSenderName.className = "message-box-sender-name"; inTopPartSenderName.className = "message-box-sender-name";
if (!members.has(messageSt.senderUserId)) inTopPartSenderName.innerText = senderMemberSt.name;
throw new Error("MMMHMMMMGMHMMMM. First - update members. Then messages"); inTopPartSenderName.href = senderProfileURI;
let memberSt = members.get(messageSt.senderUserId);
inTopPartSenderName.innerText = memberSt.name + " (" + memberSt.nickname + ")";
inTopPartSenderName.href = "/user/" + memberSt.nickname;
let ID = messageSt.id; let inTopPartSenderNickname = document.createElement("a");
topPart.appendChild(inTopPartSenderNickname);
inTopPartSenderNickname.className = "message-box-sender-name message-box-sender-shortname"
inTopPartSenderNickname.innerText = senderMemberSt.nickname;
inTopPartSenderNickname.href = senderProfileURI;
let inTopPartButtonDelete = document.createElement("img"); let inTopPartButtonDelete = document.createElement("img");
topPart.appendChild(inTopPartButtonDelete); topPart.appendChild(inTopPartButtonDelete);
inTopPartButtonDelete.className = "message-box-button"; inTopPartButtonDelete.className = "message-box-button message-box-button-delete";
inTopPartButtonDelete.src = "/assets/img/delete.svg"; inTopPartButtonDelete.src = "/assets/img/delete.svg";
inTopPartButtonDelete.onclick = (ev) => { inTopPartButtonDelete.onclick = (ev) => {
if (ev.button === 0){ if (ev.button !== 0)
console.log("Tried to delete message " + ID); return;
} let msgText = box.querySelector(".message-box-msg").innerText;
let previewText = senderMemberSt.nickname + ":\n" + msgText;
if (previewText.length > 1000)
previewText = previewText.substring(0, 1000 - 3);
document.getElementById("win-deletion-msg-preview").innerText = previewText;
storeHiddenMsgIdForDeletionWin = ID;
activatePopupWindowById("msg-deletion-win");
}; };
let inTopPartButtonGetLink = document.createElement("img"); let inTopPartButtonGetLink = document.createElement("img");
@ -114,34 +184,41 @@ function makeMessageBox(messageSt){
inTopPartButtonGetLink.className = "message-box-button"; inTopPartButtonGetLink.className = "message-box-button";
inTopPartButtonGetLink.src = "/assets/img/link.svg"; inTopPartButtonGetLink.src = "/assets/img/link.svg";
inTopPartButtonGetLink.onclick = (ev) => { inTopPartButtonGetLink.onclick = (ev) => {
if (ev.button === 0){ if (ev.button !== 0)
return;
let URI = window.location.host + "/chat/" + openedchat.nickname + "/m/" + String(ID);
document.getElementById("message-input").innerText += (" " + URI + "");
console.log("Tried to get link on message " + ID); console.log("Tried to get link on message " + ID);
}
}; };
let msgPart = document.createElement("p"); let msgPart = document.createElement("p");
box.appendChild(msgPart); box.appendChild(msgPart);
msgPart.className = "message-box-msg"; msgPart.className = "message-box-msg";
if (messageSt.exists){
if (messageSt.isSystem)
msgPart.innerText = decodeSystemMessage(messageSt.text);
else
msgPart.innerText = messageSt.text; msgPart.innerText = messageSt.text;
} else
msgPart.innerText = msgErased;
return box; return box;
} }
function makeVisible(msgId){ function makeVisible(msgId){
visibleMessages.set(msgId, makeMessageBox(loadedMessages.get(msgId))) let box = convertMessageStToBox(loadedMessages.get(msgId));
const chatWin = document.getElementById("chat-widget");
chatWin.appendChild(box);
visibleMessages.set(msgId, box);
} }
function opaNewMessage(messageSt){ function opaNewMessageSt(messageSt){
let msgId = messageSt.id; let msgId = messageSt.id;
let text = messageSt.text;
const chatWin = document.getElementById("chat-widget");
if (loadedMessages.has(msgId)){ if (loadedMessages.has(msgId)){
throw new Error("Not ready yet"); loadedMessages.set(msgId, messageSt);
// loadedMessages.get(msgId).text = text; if (visibleMessages.has(msgId)){
// if (visibleMessages.has(msgId)){ updateMessageBox(msgId, visibleMessages.get(msgId), messageSt);
// visibleMessages.get(msgId).textContent = text; }
// updateOffsets();
// }
} else { } else {
loadedMessages.set(msgId, messageSt); loadedMessages.set(msgId, messageSt);
if (anchoredMsg < 0){ if (anchoredMsg < 0){
@ -149,7 +226,6 @@ function opaNewMessage(messageSt){
visibleMsgSegStart = msgId; visibleMsgSegStart = msgId;
visibleMsgSegEnd = msgId; visibleMsgSegEnd = msgId;
makeVisible(msgId); makeVisible(msgId);
updateOffsets();
} else if (msgId + 1 === visibleMsgSegStart) { } else if (msgId + 1 === visibleMsgSegStart) {
visibleMsgSegStart--; visibleMsgSegStart--;
makeVisible(msgId); makeVisible(msgId);
@ -157,7 +233,6 @@ function opaNewMessage(messageSt){
visibleMsgSegStart--; visibleMsgSegStart--;
makeVisible(visibleMsgSegStart); makeVisible(visibleMsgSegStart);
} }
updateOffsets();
} else if (msgId - 1 === visibleMsgSegEnd){ } else if (msgId - 1 === visibleMsgSegEnd){
visibleMsgSegEnd++; visibleMsgSegEnd++;
makeVisible(msgId); makeVisible(msgId);
@ -165,57 +240,174 @@ function opaNewMessage(messageSt){
visibleMsgSegEnd++; visibleMsgSegEnd++;
makeVisible(visibleMsgSegEnd); makeVisible(visibleMsgSegEnd);
} }
}
}
}
function canISendMessages(){
return myRoleHere === userChatRoleRegular || myRoleHere === userChatRoleAdmin;
}
function updateLocalStateFromChatUpdRespBlind(chatUpdResp){
LocalHistoryId = chatUpdResp.HistoryId;
for (let memberSt of chatUpdResp.members){
let id = memberSt.userId;
if (id === userinfo.uid && myRoleHere !== memberSt.roleHere) {
myRoleHere = memberSt.roleHere;
for (let [msgId, box] of visibleMessages){
updateMessageBox(msgId, loadedMessages.get(msgId), box);
}
document.getElementById("message-input").style.display = (canISendMessages() ? "block" : "none");
}
}
for (let memberSt of chatUpdResp.members){
let id = memberSt.userId;
members.set(id, memberSt);
}
lastMsgId = chatUpdResp.lastMsgId;
for (let messageSt of chatUpdResp.messages){
opaNewMessageSt(messageSt);
}
updateOffsets(); updateOffsets();
} }
function updateLocalStateFromRecvBlind(Recv){
updateLocalStateFromChatUpdRespBlind(Recv.chatUpdResp);
}
async function requestMessageNeighbours(fromMsg, direction){
let Sent = genSentBaseGMN();
Sent.msgId = fromMsg;
Sent.direction = direction;
let Recv = await apiRequest("getMessageNeighbours", Sent);
updateLocalStateFromRecvBlind(Recv); // Blind to non-loaded whitespaces
}
function needToLoadWhitespace(){
let winTop = document.getElementById("chat-widget").offsetHeight;
if (anchoredMsg === -1){
if (lastMsgId >= 0)
console.log("NEEDED 1", anchoredMsg, lastMsgId);
return lastMsgId >= 0;
} else if (highestPoint < winTop + softZoneSz && visibleMsgSegStart > 0){
console.log("NEEDED 2", visibleMsgSegStart);
return true;
} else if (lowestPoint > 0 - softZoneSz && visibleMsgSegEnd < lastMsgId){
console.log("NEEDED 3");
return true;
}
return false;
}
async function tryLoadWhitespace(){
let winTop = document.getElementById("chat-widget").offsetHeight;
console.log(anchoredMsg, lastMsgId);
if (anchoredMsg === -1){
if (lastMsgId !== -1){
await requestMessageNeighbours(-1, "backward");
}
} else if (highestPoint < winTop + softZoneSz && visibleMsgSegStart > 0){
await requestMessageNeighbours(visibleMsgSegStart, "backward");
} else if (lowestPoint > 0 - softZoneSz && visibleMsgSegEnd < lastMsgId){
await requestMessageNeighbours(visibleMsgSegEnd, "forward");
} }
} }
function test(id, uid){ async function loadWhitespaceMultitry(){
opaNewMessage({ if (needToLoadWhitespace()){
id: id, text: "Message number " + String(id), senderUserId: uid, exists: true, isSystem: false} cancelMainloopTimeout();
); do {
} console.trace();
console.log("Normalnie ludi ne spyat");
let mainloopTimeout = null;
let mainloopPoller = null;
function setMainloopTimeout(){
mainloopTimeout = setTimeout(mainloopPoller, 1000);
}
mainloopPoller = function(){
try { try {
console.log("Hello, World!"); await tryLoadWhitespace();
} catch (error){} await sleep(100);
} catch (e) {
console.error(e);
await sleep(1500);
}
} while (needToLoadWhitespace());
setMainloopTimeout(); setMainloopTimeout();
} }
}
async function updateLocalStateFromRecv(Recv){
updateLocalStateFromRecvBlind(Recv);
await loadWhitespaceMultitry();
}
async function safeApiRequestWithLocalStUpdate(type, Sent, errMsg){
try {
let Recv = await apiRequest(type, Sent)
await updateLocalStateFromRecv(Recv);
} catch(e) {
console.error(e);
alert(errMsg);
}
}
function configureMsgDeletionPopupButtons(){
document.getElementById("msg-deletion-yes").onclick = function(ev){
if (ev.button !== 0)
return;
deactivateActivePopup();
let Sent = genSentBase();
Sent.id = storeHiddenMsgIdForDeletionWin;
safeApiRequestWithLocalStUpdate("deleteMessage", Sent, "Failed to delete message");
};
document.getElementById("msg-deletion-no").onclick = function (ev){
if (ev.button !== 0)
return;
deactivateActivePopup();
}
}
__mainloopDelayMs = 1000;
async function UPDATE(){
let Recv = await apiRequest("chatPollEvents", genSentBase());
await updateLocalStateFromRecv(Recv);
}
// __guestMainloopPollerAction = UPDATE();
window.onload = function (){ window.onload = function (){
console.log("Everything was loaded"); console.log("Page was loaded");
test(6, 1);
test(1, 2); document.body.addEventListener("wheel", function (event) {
test(3, 3); // event.preventDefault();
test(2, 1);
test(4, 2);
test(0, 3);
test(5, 2);
test(8, 1);
test(9, 2);
test(7, 3);
for (let i = 10; i < 30; i++){
test(i, 2);
}
document.body.addEventListener("wheel", (event) => {
event.preventDefault();
bumpedAtTheBottom = false; bumpedAtTheBottom = false;
console.log("Scroll of " + String(event.deltaY)); offsetOfAnchor += event.deltaY / 3;
offsetOfAnchor += event.deltaY / 5;
updateOffsets(); updateOffsets();
loadWhitespaceMultitry().then(dopDopYesYes);
}); });
mainloopPoller();
document.getElementById("message-input").addEventListener("keyup", (event) => { document.getElementById("message-input").addEventListener("keyup", function (event) {
if (event.ctrlKey && event.key === 'Enter'){ if (event.ctrlKey && event.key === 'Enter'){
let textarea = document.getElementById("message-input"); let textarea = document.getElementById("message-input");
console.log(textarea.value); let text = String(textarea.innerText);
console.log(text);
textarea.innerText = "";
let Sent = genSentBase();
Sent.content = {};
Sent.content.text = text;
safeApiRequestWithLocalStUpdate("sendMessage", Sent, "Failed to send message");
} }
}); });
let chatWg = document.getElementById("chat-widget");
let chatWgDebugLinesFnc = function (){
let H = chatWg.offsetHeight;
document.getElementById("debug-line-lowest").style.bottom = String(-softZoneSz) + "px";
document.getElementById("debug-line-highest").style.bottom = String(H + softZoneSz) + "px";
};
window.addEventListener("resize", chatWgDebugLinesFnc);
chatWgDebugLinesFnc();
configureMsgDeletionPopupButtons();
updateLocalStateFromChatUpdRespBlind(initial_chatUpdResp);
setMainloopTimeout();
loadWhitespaceMultitry();
} }

View File

@ -1,3 +1,9 @@
let dopDopYesYes = (ign) => {};
function sleep(ms){
return new Promise(res => setTimeout(res, ms));
}
async function apiRequest(type, req){ async function apiRequest(type, req){
let A = await fetch("/api/" + type, let A = await fetch("/api/" + type,
{method: 'POST', body: JSON.stringify(req)}); {method: 'POST', body: JSON.stringify(req)});
@ -10,17 +16,17 @@ async function apiRequest(type, req){
/* Framework for pages with mainloop (it can be npt only polling, but also literally anything else */ /* Framework for pages with mainloop (it can be npt only polling, but also literally anything else */
let __mainloopDelayMs = 3000; let __mainloopDelayMs = 3000;
let mainloopTimeout = null; let mainloopTimeout = null;
let mainloopPoller = null;
let __guestMainloopPollerAction = null; let __guestMainloopPollerAction = null;
function setMainloopTimeout(){ function setMainloopTimeout(){
mainloopTimeout = setTimeout(mainloopPoller, __mainloopDelayMs); mainloopTimeout = setTimeout(mainloopPoller, __mainloopDelayMs);
} }
function cancelMainloopTimeout(){ function cancelMainloopTimeout(){
clearTimeout(mainloopTimeout); clearTimeout(mainloopTimeout);
mainloopTimeout = null;
} }
mainloopPoller = function(){ function mainloopPoller(){
try { try {
console.log("Hello, World!"); if (__guestMainloopPollerAction)
__guestMainloopPollerAction(); __guestMainloopPollerAction();
} catch (error){ } catch (error){
console.log(error) console.log(error)
@ -50,3 +56,6 @@ function roleToColor(role) {
} }
return "#286500" // Bug return "#286500" // Bug
} }
// todo: replace it with translation
const msgErased = "[ ERASED ]";

View File

@ -108,8 +108,8 @@ function configureChatCreationInterface(){
return; return;
let chatNicknameInput = document.getElementById("chat-nickname-input"); let chatNicknameInput = document.getElementById("chat-nickname-input");
let chatNameInput = document.getElementById("chat-name-input"); let chatNameInput = document.getElementById("chat-name-input");
let nickname = chatNicknameInput.value; let nickname = String(chatNicknameInput.value);
let name = chatNameInput.value; let name = String(chatNameInput.value);
deactivateActivePopup(); deactivateActivePopup();
let Sent = genSentBase(); let Sent = genSentBase();
Sent.content = {}; Sent.content = {};

View File

@ -1,9 +1,11 @@
#include "server_data_interact.h" #include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h> #include <engine_engine_number_9/baza_throw.h>
#include "../debug.h"
namespace iu9cawebchat { namespace iu9cawebchat {
json::JSON internalapi_deleteMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { json::JSON internalapi_deleteMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatUpdReq"].asInteger().get_int(); // debug_print_json(Sent);
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId); int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
if (my_role_here == user_chat_role_deleted) if (my_role_here == user_chat_role_deleted)
een9_THROW("Unauthorized user tries to access internalapi_getChatInfo"); een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");

View File

@ -1,6 +1,7 @@
#include "server_data_interact.h" #include "server_data_interact.h"
#include <engine_engine_number_9/baza_throw.h> #include <engine_engine_number_9/baza_throw.h>
#include "../str_fields.h" #include "../str_fields.h"
#include "../debug.h"
namespace iu9cawebchat { namespace iu9cawebchat {
/* No authorization check is performed /* No authorization check is performed
@ -13,16 +14,19 @@ namespace iu9cawebchat {
int64_t chat_lastMsgId = get_lastMsgId_of_chat(conn, chatId); int64_t chat_lastMsgId = get_lastMsgId_of_chat(conn, chatId);
SqliteStatement req(conn, SqliteStatement req(conn,
"INSERT INTO `message` (`chatId`, `id`, `senderUserId`, `exists`, `isSystem`, `chat_IncHistoryId`, " "INSERT INTO `message` (`chatId`, `id`, `senderUserId`, `exists`, `isSystem`, `chat_IncHistoryId`, "
"`text`) VALUES (?1, ?2, ?3 1, ?4, ?5, ?6)", "`text`) VALUES (?1, ?2, ?3, 1, ?4, ?5, ?6)",
{{1, chatId}, {2, chat_lastMsgId + 1}, {4, (int64_t)isSystem}, {5, chat_HistoryId_BEFORE_MSG + 1}}, {{6, text}}); {{1, chatId}, {2, chat_lastMsgId + 1}, {4, (int64_t)isSystem}, {5, chat_HistoryId_BEFORE_MSG + 1}}, {{6, text}});
if (!isSystem) if (!isSystem)
sqlite_stmt_bind_int64(req, 3, uid); sqlite_stmt_bind_int64(req, 3, uid);
if (sqlite_stmt_step(req, {}, {}) != SQLITE_DONE)
een9_THROW("There must be something wrong");
sqlite_nooutput(conn, "UPDATE `chat` SET `lastMsgId` = ?1, `it_HistoryId` = ?2 WHERE `id` = ?3", sqlite_nooutput(conn, "UPDATE `chat` SET `lastMsgId` = ?1, `it_HistoryId` = ?2 WHERE `id` = ?3",
{{1, chat_lastMsgId + 1}, {2, chat_HistoryId_BEFORE_MSG + 1}, {3, chatId}}, {}); {{1, chat_lastMsgId + 1}, {2, chat_HistoryId_BEFORE_MSG + 1}, {3, chatId}}, {});
} }
json::JSON internalapi_sendMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { json::JSON internalapi_sendMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatUpdReq"].asInteger().get_int(); debug_print_json(Sent);
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId); int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
if (my_role_here == user_chat_role_deleted) if (my_role_here == user_chat_role_deleted)
een9_THROW("Unauthorized user tries to access internalapi_getChatInfo"); een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");
@ -36,6 +40,7 @@ namespace iu9cawebchat {
json::JSON Recv; json::JSON Recv;
poll_update_chat(conn, Sent, Recv); poll_update_chat(conn, Sent, Recv);
debug_print_json(Recv);
return Recv; return Recv;
} }
} }

View File

@ -1,6 +1,7 @@
#include "server_data_interact.h" #include "server_data_interact.h"
#include <assert.h> #include <assert.h>
#include <engine_engine_number_9/baza_throw.h> #include <engine_engine_number_9/baza_throw.h>
#include "../debug.h"
namespace iu9cawebchat { namespace iu9cawebchat {
json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId) { json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId) {
@ -102,7 +103,7 @@ namespace iu9cawebchat {
chatUpdResp["members"].asArray() = poll_update_chat_resp_members(conn, chatId, 0); chatUpdResp["members"].asArray() = poll_update_chat_resp_members(conn, chatId, 0);
json::jarr messages = chatUpdResp["messages"].asArray(); json::jarr& messages = chatUpdResp["messages"].asArray();
if (selectedMsg >= 0) { if (selectedMsg >= 0) {
RowMessage_Content msg = lookup_message_content(conn, chatId, selectedMsg); RowMessage_Content msg = lookup_message_content(conn, chatId, selectedMsg);
messages.push_back(make_messageSt_obj(msg.id, msg.senderUserId, msg.exists, msg.isSystem, msg.text)); messages.push_back(make_messageSt_obj(msg.id, msg.senderUserId, msg.exists, msg.isSystem, msg.text));
@ -165,7 +166,8 @@ namespace iu9cawebchat {
/* Reznya */ /* Reznya */
json::JSON internalapi_getMessageNeighbours(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { json::JSON internalapi_getMessageNeighbours(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatId"].asInteger().get_int(); debug_print_json(Sent);
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted) if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted)
een9_THROW("Authentication failure"); een9_THROW("Authentication failure");
int64_t lastMsgId = get_lastMsgId_of_chat(conn, chatId); int64_t lastMsgId = get_lastMsgId_of_chat(conn, chatId);
@ -192,6 +194,7 @@ namespace iu9cawebchat {
} }
} }
poll_update_chat_important_segment(conn, Sent, Recv, qBeg, qEnd); poll_update_chat_important_segment(conn, Sent, Recv, qBeg, qEnd);
debug_print_json(Recv);
return Recv; return Recv;
} }
} }

View File

@ -113,8 +113,6 @@ namespace iu9cawebchat {
int status = sqlite_stmt_step(req, {{0, &senderUserId}, {1, &exists}, {2, &isSystem}}, int status = sqlite_stmt_step(req, {{0, &senderUserId}, {1, &exists}, {2, &isSystem}},
{{3, &msg_text}}); {{3, &msg_text}});
if (status == SQLITE_ROW) { if (status == SQLITE_ROW) {
if (!(bool)exists.value)
een9_THROW("Message existed, but now it does not");
return {msgId, senderUserId.exist ? senderUserId.value : -1, (bool)exists.value, return {msgId, senderUserId.exist ? senderUserId.value : -1, (bool)exists.value,
(bool)isSystem.value, msg_text.value}; (bool)isSystem.value, msg_text.value};
} }

View File

@ -25,15 +25,14 @@ namespace iu9cawebchat {
} }
if (!check_nickname(chat_nickname)) if (!check_nickname(chat_nickname))
return page_E404(wgd); return page_E404(wgd);
if (path_segs.size() == 2) { bool show_chat_members = (path_segs[0] == "chat-members");
} else if (path_segs.size() == 4) { if (path_segs.size() == 4 && !show_chat_members) {
if (path_segs[2] != "m") if (path_segs[2] != "m")
return page_E404(wgd); return page_E404(wgd);
selected_message_id = std::stoll(path_segs[3]); selected_message_id = std::stoll(path_segs[3]);
} else if (path_segs.size() != 2) {
return page_E404(wgd); return page_E404(wgd);
} else }
return page_E404(wgd);
bool chat_members = (path_segs[0] == "chat-members");
if (userinfo.isNull()) if (userinfo.isNull())
return een9::form_http_server_response_303("/"); return een9::form_http_server_response_303("/");
@ -55,7 +54,7 @@ namespace iu9cawebchat {
// -1 means that nothing was selected // -1 means that nothing was selected
openedchat["selectedMessageId"].asInteger() = json::Integer(selected_message_id); openedchat["selectedMessageId"].asInteger() = json::Integer(selected_message_id);
json::JSON initial_chatUpdResp = poll_update_chat_ONE_MSG_resp(*wgd.db, chatInfo.id, selected_message_id); json::JSON initial_chatUpdResp = poll_update_chat_ONE_MSG_resp(*wgd.db, chatInfo.id, selected_message_id);
if (chat_members) if (show_chat_members)
return http_R200("chat-members", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp}); return http_R200("chat-members", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp});
return http_R200("chat", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp}); return http_R200("chat", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp});
} }

View File

@ -0,0 +1,6 @@
#ifndef IU9_CA_WEB_CHAT_LIB_DEBUG_H
#define IU9_CA_WEB_CHAT_LIB_DEBUG_H
#define debug_print_json(x) printf("%s\n", json::generate_str(x, json::print_pretty).c_str())
#endif

View File

@ -75,6 +75,9 @@ namespace iu9cawebchat {
"`chat_IncHistoryId` INTEGER NOT NULL," "`chat_IncHistoryId` INTEGER NOT NULL,"
"PRIMARY KEY (`chatId`, `id`)" "PRIMARY KEY (`chatId`, `id`)"
")"); ")");
std::vector<std::string> sus = {"unknown", "undefined", "null", "none", "None", "NaN"};
for (auto& s: sus)
reserve_nickname(conn, s);
add_user(conn, "root", "Rootov Root Rootovich", root_pw, "One admin to rule them all", 0); add_user(conn, "root", "Rootov Root Rootovich", root_pw, "One admin to rule them all", 0);
sqlite_nooutput(conn, "END"); sqlite_nooutput(conn, "END");
} catch (const std::exception& e) { } catch (const std::exception& e) {