Making /chat. Unstable save. Working message loading. Styles of messages are not perfect. Several iu9cachat bugs fixed
This commit is contained in:
parent
fc721d7f5c
commit
b9626aa860
@ -17,8 +17,8 @@
|
||||
<h1 class="popup-window-msg">Nickname for summoned user</h1>
|
||||
<input class="one-line-input" id="summoned-user-nickname">
|
||||
<input type="checkbox" id="summoned-user-is-read-only">
|
||||
<label>Make read only</label>
|
||||
<button class="popup-window-btn-yes" id="user-summoning-yes">Summon</button>
|
||||
<label>Make read only</label><br>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
<button class="popup-window-btn-no" id="user-deletion-no">No, cancel</button>
|
||||
</div>
|
||||
|
||||
<div id="document-container">
|
||||
<div class="document-container resp-container">
|
||||
<div id="navigation-panel" class="panel">
|
||||
<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">
|
||||
|
@ -14,32 +14,47 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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-popup.css">
|
||||
<link rel="stylesheet" href="/assets/css/chat.css">
|
||||
<title>Chat </title>
|
||||
<title>Chat</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- todo: Write the actual chat script -->
|
||||
<!--% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %-->
|
||||
<div id="fullscreen-container">
|
||||
<div class="panel" id="navigation-info-panel">
|
||||
<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">
|
||||
</a>
|
||||
<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">
|
||||
</a>
|
||||
<p class="panel-thing panel-header-txt"> {% WRITE openedchat.name %} ({% WRITE openedchat.nickname %})</p>
|
||||
<a href="/chat-members/{% WRITE openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing">
|
||||
<img alt="Settings of chat. List of members" src="/assets/img/settings-iron.svg" width="32px">
|
||||
</a>
|
||||
{% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
|
||||
|
||||
<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 id="chat-widget"></div>
|
||||
<div class="panel" id="input-panel">
|
||||
<div contentEditable id="message-input" class="panel-thing"></div>
|
||||
|
||||
<div class="fullscreen-container resp-container">
|
||||
<div class="panel" id="navigation-info-panel">
|
||||
<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">
|
||||
</a>
|
||||
<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">
|
||||
</a>
|
||||
<p class="panel-thing panel-header-txt"> {% WRITE openedchat.name %} ({% WRITE openedchat.nickname %})</p>
|
||||
<a href="/chat-members/{% WRITE openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing">
|
||||
<img alt="Settings of chat. List of members" src="/assets/img/settings-iron.svg" width="32px">
|
||||
</a>
|
||||
</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 contentEditable id="message-input" class="panel-thing"></div>
|
||||
</div>
|
||||
<script src="/assets/js/common.js"></script>
|
||||
<script src="/assets/js/common-popup.js"></script>
|
||||
<script src="/assets/js/chat.js"></script>
|
||||
</div>
|
||||
<script src="/assets/js/chat.js"></script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% ENDELDEF %}
|
||||
|
@ -11,7 +11,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="document-container">
|
||||
<div class="document-container">
|
||||
<div id="navigation-panel" class="panel">
|
||||
<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">
|
||||
@ -35,7 +35,7 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="profile-container">
|
||||
<div class="profile-container resp-container">
|
||||
<h1 class="wide-centered-header">Change user attributes</h1>
|
||||
<form action = "/user/{% WRITE alienprofile.nickname %}" method="post" enctype="application/x-www-form-urlencoded">
|
||||
<table class="logins-input-table">
|
||||
|
@ -49,7 +49,7 @@
|
||||
</div>
|
||||
|
||||
x
|
||||
<div id="document-container">
|
||||
<div class="document-container resp-container">
|
||||
<div id="navigation-panel" class="panel">
|
||||
<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">
|
||||
|
@ -11,7 +11,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="document-container">
|
||||
<div class="document-container resp-container">
|
||||
<div id="navigation-panel" class="panel">
|
||||
<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">
|
||||
|
@ -10,16 +10,44 @@ body, html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#chat-widget .message-box {
|
||||
.message-box-mine {
|
||||
position: absolute;
|
||||
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;
|
||||
padding: 5px;
|
||||
background-color: white;
|
||||
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 */
|
||||
.message-box-top{
|
||||
/* 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{
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
padding: 2px;
|
||||
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{
|
||||
@ -51,7 +86,11 @@ body, html {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#input-panel #message-input{
|
||||
#input-panel {
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
#message-input {
|
||||
padding: 15px;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
@ -62,3 +101,13 @@ body, html {
|
||||
font-size: .9rem;
|
||||
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;
|
||||
}
|
@ -54,12 +54,12 @@
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
#document-container {
|
||||
.document-container {
|
||||
width: 80%; /* Full width of the viewport */
|
||||
margin: 0 auto; /* Center the container horizontally */
|
||||
}
|
||||
|
||||
#fullscreen-container {
|
||||
.fullscreen-container {
|
||||
width: 80%; /* Full width of the viewport */
|
||||
height: 100vh; /* Full height of the viewport */
|
||||
display: flex;
|
||||
@ -67,6 +67,18 @@
|
||||
margin: 0 auto; /* Center the container horizontally */
|
||||
}
|
||||
|
||||
@media (orientation: landscape) {
|
||||
.resp-container{
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: portrait){
|
||||
.resp-container{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f000f0;
|
||||
background-image: url("/assets/img/clavicle-transparent.png"), url("/assets/img/broken-clavicle.png");
|
||||
|
12
assets/css/debug.css
Normal file
12
assets/css/debug.css
Normal 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);
|
||||
}
|
@ -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)
|
||||
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
|
||||
// let haveToUpdateAllBoxes = false;
|
||||
for (let memberSt of chatUpdResp.members){
|
||||
console.log([memberSt, userinfo.uid, myRoleHere]);
|
||||
if (memberSt.userId === userinfo.uid && myRoleHere !== memberSt.roleHere){
|
||||
myRoleHere = memberSt.roleHere;
|
||||
// haveToUpdateAllBoxes = true;
|
||||
for (let [id, memberSt] of members){
|
||||
let box = memberBoxes.get(id);
|
||||
updateBoxWithSt(box, memberSt);
|
||||
@ -115,7 +113,7 @@ function configureSummonUserInterface(){
|
||||
document.getElementById("user-summoning-yes").onclick = function(ev ){
|
||||
if (ev.button !==0)
|
||||
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;
|
||||
deactivateActivePopup();
|
||||
let Sent = genSentBase();
|
||||
@ -174,8 +172,7 @@ function configureKickUserInterfaceWinPart(){
|
||||
__mainloopDelayMS = 5000;
|
||||
__guestMainloopPollerAction = function (){
|
||||
console.log("Hello, world");
|
||||
let Sent = genSentBase();
|
||||
apiRequest("chatPollEvents", Sent).
|
||||
apiRequest("chatPollEvents", genSentBase()).
|
||||
then((Recv) => {
|
||||
console.log(Recv);
|
||||
updateLocalStateFromRecv(Recv);
|
||||
|
@ -1,17 +1,6 @@
|
||||
// In real world, I would get this variables from server through nytl
|
||||
let pres = {lang: ''}
|
||||
let userinfo = {id: 2, nickname: 'pv', name: 'Pavlov Vladimir'};
|
||||
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 LocalHistoryId = 0;
|
||||
|
||||
let members = new Map();
|
||||
|
||||
let loadedMessages = new Map(); // messageSt objects
|
||||
let visibleMessages = new Map(); // HTMLElement objects
|
||||
@ -19,14 +8,37 @@ let visibleMessages = new Map(); // HTMLElement objects
|
||||
let anchoredMsg = -1;
|
||||
let visibleMsgSegStart = -1;
|
||||
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/<>`
|
||||
let bumpedAtTheBottom = false;
|
||||
|
||||
let members = new Map();
|
||||
for (let memberSt of initial_chatUpdResp.members){
|
||||
members.set(memberSt.id, memberSt);
|
||||
// Hidden variable. When deletion window popup is active
|
||||
// Persists from popup activation until popup deactivation
|
||||
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){
|
||||
@ -54,59 +66,117 @@ function updateOffsetsDown(){
|
||||
}
|
||||
|
||||
function updateOffsetsSane(){
|
||||
let highest_point = updateOffsetsUpToTop();
|
||||
let lowest_point = updateOffsetsDown();
|
||||
return [highest_point, lowest_point];
|
||||
if (anchoredMsg < 0)
|
||||
return;
|
||||
highestPoint = updateOffsetsUpToTop();
|
||||
lowestPoint = updateOffsetsDown();
|
||||
}
|
||||
|
||||
function updateOffsets(){
|
||||
if (anchoredMsg < 0)
|
||||
return;
|
||||
let [highest_point, lowest_point] = updateOffsetsSane();
|
||||
updateOffsetsSane()
|
||||
let winTop = document.getElementById("chat-widget").offsetHeight;
|
||||
if (lowest_point > 5 || (highest_point - lowest_point) <= winTop){
|
||||
if (lowestPoint > chatPadding || (highestPoint - lowestPoint) <= winTop){
|
||||
bumpedAtTheBottom = true;
|
||||
anchoredMsg = visibleMsgSegEnd;
|
||||
offsetOfAnchor = 5;
|
||||
offsetOfAnchor = chatPadding;
|
||||
updateOffsetsSane();
|
||||
} else if (highest_point < winTop - 5){
|
||||
console.log("Adancing by " + (winTop - 5 - highest_point))
|
||||
offsetOfAnchor += (winTop - 5 - highest_point);
|
||||
} else if (highestPoint < winTop - chatPadding) {
|
||||
console.log("Advancing by " + (winTop - chatPadding - highestPoint))
|
||||
offsetOfAnchor += (winTop - chatPadding - highestPoint);
|
||||
updateOffsetsSane();
|
||||
}
|
||||
}
|
||||
|
||||
function makeMessageBox(messageSt){
|
||||
if (!messageSt.exists || messageSt.isSystem)
|
||||
throw new Error("Not ready for this");
|
||||
const parentDiv = document.getElementById("chat-widget");
|
||||
function shouldShowDeleteMesgBtn(messageSt){
|
||||
return !messageSt.isSystem && messageSt.exists && (
|
||||
messageSt.myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid);
|
||||
}
|
||||
|
||||
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");
|
||||
parentDiv.appendChild(box);
|
||||
box.className = "message-box";
|
||||
box.className = getMsgFullTypeClassName(messageSt);
|
||||
|
||||
let ID = messageSt.id;
|
||||
|
||||
let topPart = document.createElement("div");
|
||||
box.appendChild(topPart);
|
||||
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");
|
||||
topPart.appendChild(inTopPartSenderName);
|
||||
inTopPartSenderName.className = "message-box-sender-name";
|
||||
if (!members.has(messageSt.senderUserId))
|
||||
throw new Error("MMMHMMMMGMHMMMM. First - update members. Then messages");
|
||||
let memberSt = members.get(messageSt.senderUserId);
|
||||
inTopPartSenderName.innerText = memberSt.name + " (" + memberSt.nickname + ")";
|
||||
inTopPartSenderName.href = "/user/" + memberSt.nickname;
|
||||
inTopPartSenderName.innerText = senderMemberSt.name;
|
||||
inTopPartSenderName.href = senderProfileURI;
|
||||
|
||||
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");
|
||||
topPart.appendChild(inTopPartButtonDelete);
|
||||
inTopPartButtonDelete.className = "message-box-button";
|
||||
inTopPartButtonDelete.className = "message-box-button message-box-button-delete";
|
||||
inTopPartButtonDelete.src = "/assets/img/delete.svg";
|
||||
inTopPartButtonDelete.onclick = (ev) => {
|
||||
if (ev.button === 0){
|
||||
console.log("Tried to delete message " + ID);
|
||||
}
|
||||
if (ev.button !== 0)
|
||||
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");
|
||||
@ -114,34 +184,41 @@ function makeMessageBox(messageSt){
|
||||
inTopPartButtonGetLink.className = "message-box-button";
|
||||
inTopPartButtonGetLink.src = "/assets/img/link.svg";
|
||||
inTopPartButtonGetLink.onclick = (ev) => {
|
||||
if (ev.button === 0){
|
||||
console.log("Tried to get link on message " + ID);
|
||||
}
|
||||
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);
|
||||
};
|
||||
|
||||
|
||||
let msgPart = document.createElement("p");
|
||||
box.appendChild(msgPart);
|
||||
msgPart.className = "message-box-msg";
|
||||
msgPart.innerText = messageSt.text;
|
||||
if (messageSt.exists){
|
||||
if (messageSt.isSystem)
|
||||
msgPart.innerText = decodeSystemMessage(messageSt.text);
|
||||
else
|
||||
msgPart.innerText = messageSt.text;
|
||||
} else
|
||||
msgPart.innerText = msgErased;
|
||||
return box;
|
||||
}
|
||||
|
||||
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 text = messageSt.text;
|
||||
const chatWin = document.getElementById("chat-widget");
|
||||
if (loadedMessages.has(msgId)){
|
||||
throw new Error("Not ready yet");
|
||||
// loadedMessages.get(msgId).text = text;
|
||||
// if (visibleMessages.has(msgId)){
|
||||
// visibleMessages.get(msgId).textContent = text;
|
||||
// updateOffsets();
|
||||
// }
|
||||
loadedMessages.set(msgId, messageSt);
|
||||
if (visibleMessages.has(msgId)){
|
||||
updateMessageBox(msgId, visibleMessages.get(msgId), messageSt);
|
||||
}
|
||||
} else {
|
||||
loadedMessages.set(msgId, messageSt);
|
||||
if (anchoredMsg < 0){
|
||||
@ -149,7 +226,6 @@ function opaNewMessage(messageSt){
|
||||
visibleMsgSegStart = msgId;
|
||||
visibleMsgSegEnd = msgId;
|
||||
makeVisible(msgId);
|
||||
updateOffsets();
|
||||
} else if (msgId + 1 === visibleMsgSegStart) {
|
||||
visibleMsgSegStart--;
|
||||
makeVisible(msgId);
|
||||
@ -157,7 +233,6 @@ function opaNewMessage(messageSt){
|
||||
visibleMsgSegStart--;
|
||||
makeVisible(visibleMsgSegStart);
|
||||
}
|
||||
updateOffsets();
|
||||
} else if (msgId - 1 === visibleMsgSegEnd){
|
||||
visibleMsgSegEnd++;
|
||||
makeVisible(msgId);
|
||||
@ -165,57 +240,174 @@ function opaNewMessage(messageSt){
|
||||
visibleMsgSegEnd++;
|
||||
makeVisible(visibleMsgSegEnd);
|
||||
}
|
||||
updateOffsets();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function test(id, uid){
|
||||
opaNewMessage({
|
||||
id: id, text: "Message number " + String(id), senderUserId: uid, exists: true, isSystem: false}
|
||||
);
|
||||
function canISendMessages(){
|
||||
return myRoleHere === userChatRoleRegular || myRoleHere === userChatRoleAdmin;
|
||||
}
|
||||
|
||||
let mainloopTimeout = null;
|
||||
let mainloopPoller = null;
|
||||
function setMainloopTimeout(){
|
||||
mainloopTimeout = setTimeout(mainloopPoller, 1000);
|
||||
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();
|
||||
}
|
||||
mainloopPoller = function(){
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
async function loadWhitespaceMultitry(){
|
||||
if (needToLoadWhitespace()){
|
||||
cancelMainloopTimeout();
|
||||
do {
|
||||
console.trace();
|
||||
console.log("Normalnie ludi ne spyat");
|
||||
try {
|
||||
await tryLoadWhitespace();
|
||||
await sleep(100);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
await sleep(1500);
|
||||
}
|
||||
} while (needToLoadWhitespace());
|
||||
setMainloopTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
async function updateLocalStateFromRecv(Recv){
|
||||
updateLocalStateFromRecvBlind(Recv);
|
||||
await loadWhitespaceMultitry();
|
||||
}
|
||||
|
||||
async function safeApiRequestWithLocalStUpdate(type, Sent, errMsg){
|
||||
try {
|
||||
console.log("Hello, World!");
|
||||
} catch (error){}
|
||||
setMainloopTimeout();
|
||||
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 (){
|
||||
console.log("Everything was loaded");
|
||||
test(6, 1);
|
||||
test(1, 2);
|
||||
test(3, 3);
|
||||
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();
|
||||
console.log("Page was loaded");
|
||||
|
||||
document.body.addEventListener("wheel", function (event) {
|
||||
// event.preventDefault();
|
||||
bumpedAtTheBottom = false;
|
||||
console.log("Scroll of " + String(event.deltaY));
|
||||
offsetOfAnchor += event.deltaY / 5;
|
||||
offsetOfAnchor += event.deltaY / 3;
|
||||
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'){
|
||||
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();
|
||||
}
|
||||
|
@ -1,3 +1,9 @@
|
||||
let dopDopYesYes = (ign) => {};
|
||||
|
||||
function sleep(ms){
|
||||
return new Promise(res => setTimeout(res, ms));
|
||||
}
|
||||
|
||||
async function apiRequest(type, req){
|
||||
let A = await fetch("/api/" + type,
|
||||
{method: 'POST', body: JSON.stringify(req)});
|
||||
@ -10,18 +16,18 @@ async function apiRequest(type, req){
|
||||
/* Framework for pages with mainloop (it can be npt only polling, but also literally anything else */
|
||||
let __mainloopDelayMs = 3000;
|
||||
let mainloopTimeout = null;
|
||||
let mainloopPoller = null;
|
||||
let __guestMainloopPollerAction = null;
|
||||
function setMainloopTimeout(){
|
||||
mainloopTimeout = setTimeout(mainloopPoller, __mainloopDelayMs);
|
||||
}
|
||||
function cancelMainloopTimeout(){
|
||||
clearTimeout(mainloopTimeout);
|
||||
mainloopTimeout = null;
|
||||
}
|
||||
mainloopPoller = function(){
|
||||
function mainloopPoller(){
|
||||
try {
|
||||
console.log("Hello, World!");
|
||||
__guestMainloopPollerAction();
|
||||
if (__guestMainloopPollerAction)
|
||||
__guestMainloopPollerAction();
|
||||
} catch (error){
|
||||
console.log(error)
|
||||
}
|
||||
@ -50,3 +56,6 @@ function roleToColor(role) {
|
||||
}
|
||||
return "#286500" // Bug
|
||||
}
|
||||
|
||||
// todo: replace it with translation
|
||||
const msgErased = "[ ERASED ]";
|
||||
|
@ -108,8 +108,8 @@ function configureChatCreationInterface(){
|
||||
return;
|
||||
let chatNicknameInput = document.getElementById("chat-nickname-input");
|
||||
let chatNameInput = document.getElementById("chat-name-input");
|
||||
let nickname = chatNicknameInput.value;
|
||||
let name = chatNameInput.value;
|
||||
let nickname = String(chatNicknameInput.value);
|
||||
let name = String(chatNameInput.value);
|
||||
deactivateActivePopup();
|
||||
let Sent = genSentBase();
|
||||
Sent.content = {};
|
||||
|
@ -1,9 +1,11 @@
|
||||
#include "server_data_interact.h"
|
||||
#include <engine_engine_number_9/baza_throw.h>
|
||||
#include "../debug.h"
|
||||
|
||||
namespace iu9cawebchat {
|
||||
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);
|
||||
if (my_role_here == user_chat_role_deleted)
|
||||
een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "server_data_interact.h"
|
||||
#include <engine_engine_number_9/baza_throw.h>
|
||||
#include "../str_fields.h"
|
||||
#include "../debug.h"
|
||||
|
||||
namespace iu9cawebchat {
|
||||
/* No authorization check is performed
|
||||
@ -13,16 +14,19 @@ namespace iu9cawebchat {
|
||||
int64_t chat_lastMsgId = get_lastMsgId_of_chat(conn, chatId);
|
||||
SqliteStatement req(conn,
|
||||
"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}});
|
||||
if (!isSystem)
|
||||
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",
|
||||
{{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) {
|
||||
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);
|
||||
if (my_role_here == user_chat_role_deleted)
|
||||
een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");
|
||||
@ -36,6 +40,7 @@ namespace iu9cawebchat {
|
||||
|
||||
json::JSON Recv;
|
||||
poll_update_chat(conn, Sent, Recv);
|
||||
debug_print_json(Recv);
|
||||
return Recv;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "server_data_interact.h"
|
||||
#include <assert.h>
|
||||
#include <engine_engine_number_9/baza_throw.h>
|
||||
#include "../debug.h"
|
||||
|
||||
namespace iu9cawebchat {
|
||||
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);
|
||||
|
||||
|
||||
json::jarr messages = chatUpdResp["messages"].asArray();
|
||||
json::jarr& messages = chatUpdResp["messages"].asArray();
|
||||
if (selectedMsg >= 0) {
|
||||
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));
|
||||
@ -165,7 +166,8 @@ namespace iu9cawebchat {
|
||||
|
||||
/* Reznya */
|
||||
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)
|
||||
een9_THROW("Authentication failure");
|
||||
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);
|
||||
debug_print_json(Recv);
|
||||
return Recv;
|
||||
}
|
||||
}
|
||||
|
@ -113,8 +113,6 @@ namespace iu9cawebchat {
|
||||
int status = sqlite_stmt_step(req, {{0, &senderUserId}, {1, &exists}, {2, &isSystem}},
|
||||
{{3, &msg_text}});
|
||||
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,
|
||||
(bool)isSystem.value, msg_text.value};
|
||||
}
|
||||
|
@ -25,15 +25,14 @@ namespace iu9cawebchat {
|
||||
}
|
||||
if (!check_nickname(chat_nickname))
|
||||
return page_E404(wgd);
|
||||
if (path_segs.size() == 2) {
|
||||
} else if (path_segs.size() == 4) {
|
||||
bool show_chat_members = (path_segs[0] == "chat-members");
|
||||
if (path_segs.size() == 4 && !show_chat_members) {
|
||||
if (path_segs[2] != "m")
|
||||
return page_E404(wgd);
|
||||
selected_message_id = std::stoll(path_segs[3]);
|
||||
} else if (path_segs.size() != 2) {
|
||||
return page_E404(wgd);
|
||||
} else
|
||||
return page_E404(wgd);
|
||||
bool chat_members = (path_segs[0] == "chat-members");
|
||||
}
|
||||
|
||||
if (userinfo.isNull())
|
||||
return een9::form_http_server_response_303("/");
|
||||
@ -55,7 +54,7 @@ namespace iu9cawebchat {
|
||||
// -1 means that nothing was selected
|
||||
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);
|
||||
if (chat_members)
|
||||
if (show_chat_members)
|
||||
return http_R200("chat-members", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp});
|
||||
return http_R200("chat", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp});
|
||||
}
|
||||
|
0
src/web_chat/iu9_ca_web_chat_lib/debug.cpp
Normal file
0
src/web_chat/iu9_ca_web_chat_lib/debug.cpp
Normal file
6
src/web_chat/iu9_ca_web_chat_lib/debug.h
Normal file
6
src/web_chat/iu9_ca_web_chat_lib/debug.h
Normal 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
|
@ -75,6 +75,9 @@ namespace iu9cawebchat {
|
||||
"`chat_IncHistoryId` INTEGER NOT NULL,"
|
||||
"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);
|
||||
sqlite_nooutput(conn, "END");
|
||||
} catch (const std::exception& e) {
|
||||
|
Loading…
Reference in New Issue
Block a user