Compare commits

..

11 Commits

Author SHA1 Message Date
2219653f40 FIRST WORKING VERSION OF IU9 CA WEB CHAT. IT'S WORKINGgit add -A! YOU CAN SEND STUFF INTO CHAT AND IT WILL LOAD UP ON ANOTHER DEVICE EEEEEEEE 2024-09-05 00:17:33 +03:00
b9626aa860 Making /chat. Unstable save. Working message loading. Styles of messages are not perfect. Several iu9cachat bugs fixed 2024-09-04 12:39:39 +03:00
fc721d7f5c Fixed a lot of server-side bugs, /list-rooms page bugs. Added favicon, finished /chat-members page 2024-09-02 12:34:49 +03:00
3a8bdb99ec Fixed some problems with deletion buttons in /list-rooms, wrote a sketch for /chat-members 2024-09-02 01:11:28 +03:00
9283590122 fixed some backend bugs. Finished /list-rooms. Now we have these cool popup windows before serious actions 2024-09-01 22:24:55 +03:00
21a23be96e Doing frontend now. Added these cool popup windows in list-rooms.nytl.html 2024-09-01 13:03:52 +03:00
07711a93b0 Fixed some bugs in nytl, een9 ad chat, wrote a skeleton for list-rooms, chat pages, finished login, view-profile, edit-profile-pages. chat-members page still needs attention 2024-09-01 01:29:32 +03:00
c009b848b5 Merge branch 'master' into chat-wg-test
To pull all the images
2024-08-31 18:59:10 +03:00
cc8aa516bb New logo 2024-08-31 18:58:21 +03:00
711e495f15 Added some cool svg icons 2024-08-30 20:45:03 +03:00
5062e1ab70 asdasdasdasdasdasdas 2024-08-30 16:27:33 +03:00
56 changed files with 1974 additions and 1225 deletions

View File

@ -38,11 +38,11 @@ regexis024_build_system.sh
Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON. Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON.
Комментарии не поддерживаются. Пример такого файла находится в example/config.json. Комментарии не поддерживаются. Пример такого файла находится в example/config.json.
Вместе с бинарным фалом так же распространяются ассеты, необъходимые для работы сайта. Вместе с бинарным фалом так же распространяются ассеты, необходимые для работы сайта.
Их можно найти в папке assets. В настроках (поле `["assets"]`) указывается путь до Их можно найти в папке assets. В настроках (поле `["assets"]`) указывается путь до
папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории. папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории.
Поле настроек `["database"]` указывает как соединиться с базой данных. Поле настроек `["database"]` указывает как соединиться с базой данных.
Поддерживается только база данных sqlite. Поддерживается только хранение в файле. Поддерживается только база данных sqlite3. Поддерживается только хранение в файле.
Поле `["database"]["file"]` указывает путь где хранится sqlite база данных. Поле `["database"]["file"]` указывает путь где хранится sqlite база данных.
Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать

View File

@ -4,26 +4,56 @@
<head> <head>
<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">
<title>Веб-Чат Members</title> <link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/chat.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-members.css">
<title>Chat Members</title>
</head> </head>
<body> <body>
{% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %} {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
<!--TODO: ADD SOMETHING WRITE SOMETHING AAAAAA -->
<div class="overlay" id="overlay"> <div id="user-summoning-win" class="popup-window">
<div class="members-list" id="members-list"> <h1 class="popup-window-msg">Nickname for summoned user</h1>
<div class="members-list-header"> <input class="one-line-input" id="summoned-user-nickname">
<span class="close" onclick="closeMembersList()">&times;</span> <input type="checkbox" id="summoned-user-is-read-only">
<h2 class="all-members">Все участники</h2> <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>
<div id="user-deletion-win" class="popup-window">
<!-- header will actually be rewritten before showing the window to include user nickname -->
<h1 id="user-deletion-win-title" class="popup-window-msg">Are you sure you want to ban user?</h1>
<button class="popup-window-btn-yes" id="user-deletion-yes">Yes, delete</button>
<button class="popup-window-btn-no" id="user-deletion-no">No, cancel</button>
</div>
<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">
</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"> Members list of {% WRITE openedchat.name %} ({% WRITE openedchat.nickname %})</p>
<a href="/chat/{% WRITE openedchat.nickname %}" id="go-to-chat-settings" class="panel-thing">
<img alt="Back to chat" src="/assets/img/return.svg" width="32px">
</a>
</div> </div>
<div class="members-list-body">
<ul id="members-list-body"> <div class="dynamic-block-list">
<!-- Список участников будет добавлен динамически --> <img id="CM-btn-add" class="button-add centered-block-el" alt="New chat" src="/assets/img/add.svg">
</ul> <div class="dynamic-block-list-el-container" id="CM-list">
</div>
</div> </div>
</div> </div>
</div>
<script src="/assets/js/chat-members.js"></script> <script src="/assets/js/common.js"></script>
<script src="/assets/js/common-popup.js"></script>
<script src="/assets/js/chat-members.js"></script>
</body> </body>
</html> </html>
{% ENDELDEF %} {% ENDELDEF %}

View File

@ -9,30 +9,60 @@
{% ELDEF main JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %} {% ELDEF main JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru"> <html lang="en">
<head> <head>
<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">
<title>Веб-Чат</title> <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"> <link rel="stylesheet" href="/assets/css/chat.css">
<title>Chat</title>
</head> </head>
<body> <body>
{% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %} {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
<!-- TODO AAAAA-->
<div class="chat-container"> <div id="msg-deletion-win" class="popup-window">
<div class="chat-header"> <h1 class="popup-window-msg">Are you sure you want to delete this message?</h1>
<span class="room-name">Веб чат</span> <!-- message preview will be actually rewritten before each window activation-->
<button class="members" onclick="openMembersList()">Показать участников</button> <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>
<div class="chat-messages" id="chat-messages">
<!-- Сообщения чата будут здесь --> <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" id="debug-line-highest" style="background-color: #8600d3"></div>
<div class="chat-debug-rect" id="debug-line-top-padding" style="background-color: #ff00ae"></div>
<div class="chat-debug-rect" id="debug-line-bottom-padding" style="background-color: #ff0062"></div>
<div class="chat-debug-rect" id="debug-line-lowest" style="background-color: #ff2f00"></div>
<div id="top-loading" class="message-supercontainer">
<img class="loading-spinner" alt="Loading backward..." src="/assets/gif/loading.gif">
</div>
<div id="bottom-loading" class="message-supercontainer">
<img class="loading-spinner" alt="Loading forward..." src="/assets/gif/loading.gif">
</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> </div>
<div class="chat-footer">
<input type="text" class="chat-input" id="chat-input" placeholder="Введите сообщение...">
<button class="chat-send-button" onclick="sendMessage()">Отправить</button>
</div>
</div>
<script src="/assets/js/chat.js"></script>
</body> </body>
</html> </html>
{% ENDELDEF %} {% ENDELDEF %}

View File

@ -1,42 +1,69 @@
{% ELDEF main JSON pres JSON userprofile JSON errors %} {% ELDEF main JSON pres JSON userinfo JSON alienprofile JSON errors %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="/assets/css/profile.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Профиль</title> <link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/edit-profile.css">
<title>Edit user Profile</title>
</head> </head>
<body> <body>
<div class="main-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">
</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>
</div>
{% FOR error IN errors %} {% FOR error IN errors %}
<div> <div class="server-notif-error-msg-box">
<p>{% WRITE error.text %}</p> {% WRITE error.text %}
</div> </div>
{% ENDFOR %} {% ENDFOR %}
<div class="profile-header">
<h1>Редактирование профиля</h1> <div class="profile-container">
<h2 class="profile-name-text">{% WRITE alienprofile.name %}</h2>
<h3 class="profile-nickname-text">Nickname: {% WRITE alienprofile.nickname %}</h3>
<p class="profile-bio-text">
{% WRITE alienprofile.bio %}
</p>
</div>
<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">
<tr>
<td class="logins-input-td1">
<label for="new-name-input">Enter new name:</label>
</td>
<td class="logins-input-td2">
<input name="name" id="new-name-input" type="text" placeholder="New name" class="one-line-input">
</td>
</tr>
<tr>
<td class="logins-input-td1">
<label for="new-password-input">Enter new password: </label>
</td>
<td class="logins-input-td2">
<input name="password" id="new-password-input" type="password" placeholder="New password" class="one-line-input">
</td>
</tr>
</table>
<label for="input-change-bio">Change description:</label>
<br>
<textarea name="bio" id="input-change-bio" class="multiline-input"></textarea>
<button class="action-button centered-block-el" type="submit">Submit changes</button>
</form>
</div> </div>
<form method="post" action="/user/{% WRITE userprofile.nickname %}">
<div class="columns">
<div class="column">
<p>{% WRITE userprofile.name %} ( {% WRITE userprofile.nickname %} )</p>
<label for="name"> Изменить имя </label>
<input type="text" name="name" id="name">
<br>
<label for="password"> Изменить пароль </label>
<input type="password" name="password" id="password">
<br>
</div>
</div>
<div class="additional-info">
<p>О себе</p>
<p>{% WRITE userprofile.bio %}</p><br>
<label for="bio">Изменить</label>
<textarea name="bio" id="bio"></textarea>
</div>
<button class="save" type="submit">Сохранить изменения</button>
</form>
</div> </div>
</body> </body>
</html> </html>
{% ENDELDEF%} {% ENDELDEF%}

View File

@ -2,6 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<title>Not found</title> <title>Not found</title>
</head> </head>
<body> <body>

View File

@ -4,69 +4,71 @@
<head> <head>
<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">
<title>Список Чат-Комнат</title> <title>List of chat rooms</title>
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/common-popup.css">
<link rel="stylesheet" href="/assets/css/list-rooms.css"> <link rel="stylesheet" href="/assets/css/list-rooms.css">
</head> </head>
<body> <body>
<script> <script>
let pres = {% PUT jsinsert pres %}; let pres = {% PUT jsinsert pres %};
let userinfo = {% PUT jsinsert userinfo %}; let userinfo = {% PUT jsinsert userinfo %};
let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %}; let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %};
</script> </script>
<div class="container"> <div id="chat-creation-win" class="popup-window">
<h1 style="color: white;">Выберите Чат-Комнату</h1> <h1 class="popup-window-msg">Input identifying information for your new chat</h1>
<ul class="room-list"> <table class="id-str-input-table">
<!-- Здесь будет список комнат --> <tr>
</ul> <td class="id-str-input-td1">
<button class="create-room-button" onclick="openCreateRoomModal()">Создать Комнату</button> <label for="chat-nickname-input">Enter nickname for new chat:</label>
</div> </td>
<td class="id-str-input-td2">
<input id="chat-nickname-input" type="text" placeholder="Take a nickname" class="one-line-input" required>
</td>
</tr>
<tr>
<td class="id-str-input-td1">
<label for="chat-name-input">Enter name for new chat:</label>
</td>
<td class="id-str-input-td2">
<input id="chat-name-input" type="text" placeholder="Come up with name" class="one-line-input" required>
</td>
</tr>
</table>
<h1 class="popup-window-msg">Create new chat?</h1>
<button class="popup-window-btn-yes" id="chat-creation-win-yes">Yes, create</button>
<button class="popup-window-btn-no" id="chat-creation-win-no">No, cancel</button>
</div>
<!-- Модальное окно для создания комнаты --> <div id="chat-renunciation-win" class="popup-window">
<!--<div id="createRoomModal" class="modal">--> <!-- header will actually be rewritten before showing the window to include chat nickname -->
<!-- <div class="modal-content">--> <h1 id="chat-renunciation-win-title" class="popup-window-msg">Are you sure you want to leave chat?</h1>
<!-- <div class="modal-header">--> <button class="popup-window-btn-yes" id="chat-renunciation-win-yes">Yes, leave</button>
<!-- <span class="close" onclick="closeCreateRoomModal()">&times;</span>--> <button class="popup-window-btn-no" id="chat-renunciation-win-no">No, cancel</button>
<!-- <h2>Создать Комнату</h2>--> </div>
<!-- </div>-->
<!-- <div class="modal-body">-->
<!-- <input type="text" id="newRoomName" placeholder="Название комнаты">-->
<!-- <input type="password" id="newRoomNickname" placeholder="Никнейм комнаты">-->
<!-- </div>-->
<!-- <div id="error"></div>-->
<!-- <div class="modal-footer">-->
<!-- <button class="join-button" onclick="createRoom()">Создать</button>-->
<!-- </div>-->
<!-- </div>-->
<!--</div>-->
<!--&lt;!&ndash; Модальное окно для добавления участников &ndash;&gt;--> x
<!--<div class="overlay" id="add_members">--> <div class="document-container resp-container">
<!-- <div class="add-members">--> <div id="navigation-panel" class="panel">
<!-- <div class="add-members-header">--> <a href="/user/{% WRITE userinfo.nickname %}" id="go-to-my-profile" class="panel-thing">
<!-- <span class="close" onclick="closeAdd()">&times;</span>--> <img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
<!-- <h2>Добавить участников</h2>--> </a>
<!-- </div>--> <p class="panel-thing panel-header-txt">
<!-- <div class="add-members-body">--> List of available rooms
<!-- <input type="text" id="newMemberLogin" placeholder="Логин пользователя">--> </p>
<!-- </div>--> </div>
<!-- <div class="add-members-footer">-->
<!-- <button class="add-member-button" onclick="addMember()">Добавить</button>--> <div class="dynamic-block-list">
<!-- </div>--> <img id="CL-bacbe" class="button-add centered-block-el" alt="New chat" src="/assets/img/add.svg">
<!-- </div>--> <div class="dynamic-block-list-el-container" id="CL-dblec">
<!--</div>-->
<!--<div class="overlay" id="delete-chat">--> </div>
<!-- <div class="delete-chat">--> </div>
<!-- <div class="delete-chat-header">--> </div>
<!-- <span class="delete-close" onclick="closeConfirm()">&times;</span>--> <script src="/assets/js/common.js"></script>
<!-- <h2>Вы уверены, что хотите удалить чат?</h2>--> <script src="/assets/js/common-popup.js"></script>
<!-- </div>--> <script src="/assets/js/list-rooms.js"></script>
<!-- <div class="delete-chat-body">-->
<!-- <button class="confirm" onclick="deleteChat()">Да</button>-->
<!-- <button class="cancel" onclick="closeConfirm()">Нет</button>-->
<!-- </div>-->
<!-- </div>-->
<!--</div>-->
<script src="/assets/js/list-rooms.js"></script>
</body> </body>
</html> </html>
{% ENDELDEF %} {% ENDELDEF %}

View File

@ -1,29 +1,41 @@
{% ELDEF main JSON pres JSON userinfo JSON errors %} {% ELDEF main JSON pres JSON userinfo JSON errors %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{% WRITE pres.lang %}"> <html lang="en">
<head> <head>
<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">
<title>{% WRITE pres.phr.decl.page-login %}</title> <link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<link rel="stylesheet" href="/assets/css/login.css"> <link rel="stylesheet" href="/assets/css/login.css">
<title>Login Page</title>
</head> </head>
<body> <body>
{% FOR msg IN errors %} {% FOR error IN errors %}
<div class="error-msg"> <div class="server-notif-error-msg-box">
{% WRITE msg.text %} {% WRITE error.text %}
</div>
{% ENDFOR %}
<div class="form-container">
<h1 class="wide-centered-header">Login</h1>
<form action="/login" method="post" enctype="application/x-www-form-urlencoded">
<table class="logins-input-table">
<tr>
<td class="logins-input-td1"><label for="input-nickname">Enter user nickname:</label></td>
<td class="logins-input-td2">
<input name="nickname" id="input-nickname" type="text" placeholder="Nickname" class="one-line-input" required>
</td>
</tr>
<tr>
<td class="logins-input-td1"><label for="input-password">Enter password:</label></td>
<td class="logins-input-td2">
<input name="password" id="input-password" type="password" placeholder="Password" class="one-line-input" required>
</td>
</tr>
</table>
<button class="action-button centered-block-el" type="submit">Login</button>
</form>
</div> </div>
{% ENDFOR %}
<div class="form-container">
<h1 class="hide-cursor no-select">{% WRITE pres.phr.decl.enter %}</h1>
<form action="/login" method="post" enctype="application/x-www-form-urlencoded">
<label for="nickname">{% WRITE pres.phr.decl.nickname %}</label>
<input type="text" name="nickname" id="nickname"><br>
<label for="password">{% WRITE pres.phr.decl.password %}</label>
<input type="password" name="password" id="password"><br>
<button type="submit" class="hide-cursor no-select">{% WRITE pres.phr.act.enter %}</button>
</form>
</div>
</body> </body>
</html> </html>

View File

@ -1,26 +1,36 @@
{% ELDEF main JSON pres JSON userprofile %} {% ELDEF main JSON pres JSON userinfo JSON alienprofile %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="/assets/css/profile.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Профиль</title> <link rel="icon" type="image/png" href="/assets/img/favicon.png">
<link rel="stylesheet" href="/assets/css/common.css">
<!-- This page is so simple, that it does not even have it's separate css file -->
<title>User Profile</title>
</head> </head>
<body> <body>
<div class="main-container">
<div class="profile-header"> <div class="document-container resp-container">
<h1>Профиль пользователя</h1> <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">
</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>
</div> </div>
<div class="columns">
<div class="column"> <div class="profile-container">
<p>{% WRITE userprofile.name %} ( {% WRITE userprofile.nickname %} )</p> <h2 class="profile-name-text">{% WRITE alienprofile.name %}</h2>
</div> <h3 class="profile-nickname-text">Nickname: {% WRITE alienprofile.nickname %}</h3>
</div> <p class="profile-bio-text">
<div class="additional-info"> {% WRITE alienprofile.bio %}
<p>О себе</p> </p>
<p>{% WRITE userprofile.bio %}</p><br>
</div> </div>
</div> </div>
</body> </body>
</html> </html>
{% ENDELDEF%} {% ENDELDEF%}

View File

@ -0,0 +1,33 @@
#CM-btn-add {
margin-top: 6px;
margin-bottom: 4px;
display: none;
}
.CM-member-box {
display: flex;
flex-direction: row;
}
.CL-member-box-nickname {
margin-left: 8px;
justify-self: flex-start;
}
.CM-member-box-name {
margin-left: 14px;
justify-self: flex-start;
}
.CM-member-box-role {
margin-left: auto;
justify-self: flex-end;
}
.CM-member-box-leave-btn {
margin-left: 10px;
margin-right: 8px;
justify-self: flex-end;
width: 16px;
cursor: pointer;
}

View File

@ -1,202 +1,142 @@
body { body, html {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #e5e5e5;
}
.chat-container {
width: 100%;
max-width: 800px;
height: 90vh;
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
border-radius: 8px;
overflow: hidden;
}
.chat-header {
background-color: #007bb5;
color: white;
padding: 25px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.room-name {
position: absolute;
left: 50%;
font-size: 24px;
}
.members {
border: none;
position: absolute;
left: 80%;
border-radius: 10px;
cursor: pointer;
width: 150px;
background-color: #f7f7f7;
height: 25px;
transition: background-color 0.3s ease;
}
.members:hover {
background-color: #218838;
}
.chat-messages {
flex: 1;
padding: 15px;
overflow-y: auto;
background-color: #f7f7f7;
}
.chat-message {
display: flex;
align-items: flex-start;
margin-bottom: 15px;
}
.chat-message .avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
margin-right: 10px;
}
.chat-message .avatar img {
width: 100%;
height: 100%; height: 100%;
object-fit: cover;
} }
.chat-message .message-content { #chat-widget {
max-width: 70%; position: relative;
flex: 1;
background-color: #f1f1f1;
overflow: hidden;
}
.message-supercontainer{
position: absolute;
width: 100%;
left: 0;
background-color: rgba(150, 0, 100, 50);
/*display: flex;*/
/*flex-direction: row;*/
/*justify-content: center;*/
}
.message-box{
/*display: inline-block;*/
padding: 5px;
}
.message-box-mine {
margin-right: 5px;
margin-left: auto;
max-width: 400px;
border: 2px solid #82a173;
padding: 5px;
background-color: #cdff9b;
color: black;
/*justify-self: flex-end;*/
}
.message-box-alien {
margin-left: 5px;
margin-right: auto;
max-width: 400px;
border: 2px solid dimgrey;
padding: 5px;
background-color: white; background-color: white;
padding: 10px; color: black;
border-radius: 8px; /*justify-self: flex-start;*/
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
} }
.chat-message .message-content .username { /* 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 {
border: 2px solid #cb0005;
background-color: #ffc1bc;
}
.message-box-deleted .message-box-msg{
font-weight: bold; font-weight: bold;
margin-bottom: 5px;
} }
.chat-message .message-content .text { .message-box-system {
margin-left: auto;
margin-right: auto;
max-width: 500px;
padding: 4px;
background-color: #2d2d2d;
color: white;
font-weight: bold;
justify-self: center;
}
/* 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.
This happens after window.onload, so I added a crutch: loading won't update height in
unpredictable moment. cause it will be already high enough. BUGA-GA-GA!! */
min-height: 30px;
display: block;
}
.message-box-sender-name{
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{
color: #1060ff
}
.message-box-button{
width: 20px;
padding: 2px;
cursor: pointer;
display: inline;
}
.message-box-msg{
word-wrap: break-word; word-wrap: break-word;
} }
.chat-footer { #input-panel {
display: flex; min-height: 20px;
}
#message-input {
padding: 15px; padding: 15px;
padding-left: 50px; height: auto;
border-top: 1px solid #ddd;
}
.chat-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
margin-right: 10px;
outline: none;
}
.chat-send-button {
padding: 10px 20px;
border: none;
background-color: #0088cc;
color: white;
border-radius: 20px;
cursor: pointer;
outline: none;
}
.members-list {
display: none;
position: fixed;
background-color: #fff;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 400px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.members-list-header {
display: flex;
}
.all-members {
position: absolute;
left: 32%;
top: 0%;
margin-bottom: 30px;
font-family: Arial, sans-serif;
}
.close {
position: absolute;
right: 5%;
font-size: 24px;
font-weight: bold;
}
.members-list span {
cursor: pointer;
}
.members-list-body ul {
list-style-type: none;
left: 0%;
}
.members-list-body img {
margin-top: 10px;
left: 0%;
height: 30px;
width: 30px;
border-radius: 50%;
}
.members-list-body a {
margin-left: 5px;
margin-top: 10px;
text-decoration: none;
color: black;
}
.members-list-body a:hover {
text-decoration: underline;
color: #0088cc;
}
.members-list-body button {
padding: 5px 10px;
border: none;
background-color: #dc2e45;
color: white;
border-radius: 20px;
position: absolute;
left: 300px;
margin-top: 20px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.members-list-body button:hover {
background-color: #881527;
}
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%; width: 100%;
height: 100%; display: inline-block;
background-color: rgba(0, 0, 0, 0.5); background-color: white;
justify-content: center; border: 1px solid #1000d0;
align-items: center; border-radius : 7px;
z-index: 1000; font-size: .9rem;
margin: 10px;
} }
.chat-send-button:hover {
background-color: #007bb5; .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;
}
.loading-spinner{
margin-left: auto;
margin-right: auto;
background-color: rgba(0, 0, 0, 0);
width: 25px;
display: block;
} }

View File

@ -0,0 +1,50 @@
.popup-overlay-veil {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
z-index: 99;
display: none; /* Hidden by default */
}
.popup-window {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
z-index: 100;
display: none;
}
.popup-btn {
display: inline;
padding: 5px;
border-bottom: 3px;
}
.popup-window-btn-yes {
background-color: #0c7f0e;
border-radius: 5px;
padding: 12px;
color: white;
}
.popup-window-btn-no {
background-color: #ff0005;
border-radius: 5px;
padding: 12px;
color: white;
}
.popup-window-msg {
padding-left: 20px;
font-weight: bold;
font-size: 1.3em;
}

206
assets/css/common.css Normal file
View File

@ -0,0 +1,206 @@
/* Profile view elements */
.profile-container {
background: white;
border-radius: 5px;
padding: 20px;
margin-top: 60px; /* Space below the fixed panel */
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.3);
}
.profile-name-text {
color: black;
}
.profile-nickname-text{
color: #444;
text-align: left;
}
.profile-bio-text {
padding-top: 40px;
text-align: left;
line-height: 1.6;
color: black;
}
/* Panels */
.panel {
width: 100%;
border: 2px solid blue;
background-color: #54b3ff;
display: flex;
flex-direction: row;
align-items: center;
}
.panel-thing {
padding: 6px;
}
.panel-header-txt{
color: white;
font-size: 1.9em;
flex: 1;
text-align: center;
}
/* Containers for the whole document */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
.document-container {
width: 80%; /* Full width of the viewport */
margin: 0 auto; /* Center the container horizontally */
}
.fullscreen-container {
width: 80%; /* Full width of the viewport */
height: 100vh; /* Full height of the viewport */
display: flex;
flex-direction: column; /* Stack children vertically */
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");
background-repeat: revert;
background-size: 10%, 25%;
}
/* Notifications, returned from server and embedded into html page at render-time */
.server-notif-error-msg-box{
font-size: 1.3em;
text-align: center;
padding: 10px;
border: 2px solid red;
border-radius: 30px;
background-color: #ff5050;
max-width: 40%;
margin: 15px auto;
}
/* Centered headers */
.wide-centered-header {
width: 100%;
text-align: center;
font-size: 1.4em;
}
/* Cool buttons with text */
.action-button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.action-button:hover {
background-color: #0056b3; /* Darker blue on hover */
}
/* This is for centering non-100%wide block */
.centered-block-el {
display: block;
margin-left: auto;
margin-right: auto;
}
/* Beautiful text input */
.one-line-input {
width: 100%;
padding: 8px;
margin: 8px 0;
border: 1px solid #ccc;
border-radius: 4px;
}
.multiline-input {
width: 100%;
/*max-width: 600px;*/
height: 200px;
padding: 10px;
font-size: 1.15em;
border: 2px solid #ccc;
border-radius: 5px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); /* Subtle shadow */
outline: none; /* Remove default outline on focus */
resize: vertical; /* Allow resizing vertically */
transition: border-color 0.15s, box-shadow 0.15s; /* Smooth transition for border color and shadow */
}
.multiline-input:focus {
border-color: #007bff; /* Change border color on focus */
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5); /* Shadow on focus */
}
/* Handles the case of list of elements with dickanme, name, role and delete button
For list of chats and list of users in chat */
.dynamic-block-list {
margin-top:12px;
display: flex;
flex-direction: column;
background-color: white;
border: 1px solid #c7c7c7;
align-items: stretch;
padding-left: 8px;
padding-right: 8px;
padding-bottom: 8px;
}
.dynamic-block-list-el {
margin-top: 8px;
background-color: white;
border: 1px solid #c7c7c7;
color: black;
padding: 5px;
}
.button-add{
width: 50px;
cursor: pointer;
}
.dynamic-block-list-el-container{
width: 100%;
}
.entity-nickname-txt {
font-weight: bold;
color: black;
text-decoration: none;
font-size: 1.5em;
}
.entity-reg-field-txt {
/* For name and role */
color: #242424;
text-decoration: none;
font-size: 1.5em;
}

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

@ -0,0 +1,8 @@
.chat-debug-rect{
width: 100%;
position: absolute;
left: 0;
opacity: 0.3;
height: 3px;
z-index: 2;
}

View File

@ -0,0 +1,23 @@
/* The morbid thing */
table.logins-input-table {
width: 100%;
border-collapse: collapse; /* Combine borders */
}
.logins-input-td1, .logins-input-td2 {
border: none;
}
.logins-input-td1 {
text-align: left;
padding-right: 5px;
white-space: nowrap; /* Prevent text wrap, keeping it in one line */
overflow: hidden; /* Hide overflow content */
text-overflow: ellipsis; /* Show ellipsis for overflowing text */
}
.logins-input-td2 {
width: 100%;
}
#input-change-bio{
margin-top: 5px;
margin-bottom: 5px;
}

View File

@ -1,253 +1,51 @@
body { #CL-bacbe {
font-family: Arial, sans-serif; margin-top: 6px;
background-color: #f0f0f0; margin-bottom: 4px;
margin: 0;
padding: 0;
} }
.container { .CL-my-chat-box {
max-width: 800px;
margin: 30px auto;
padding: 20px;
background-color: #007bff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
h1 {
text-align: center;
color: #fff;
}
.room-list {
list-style-type: none;
padding: 0;
}
.room-item {
display: flex; display: flex;
justify-content: space-between; flex-direction: row;
align-items: center;
padding: 15px;
margin: 10px 0;
background-color: #fafafa;
border: 1px solid #ddd;
border-radius: 5px;
transition: background-color 0.3s ease;
} }
.room-item:hover { .CL-my-chat-box-nickname {
background-color: #eaeaea; margin-left: 8px;
justify-self: flex-start;
} }
.room-name { .CL-my-chat-box-name {
font-size: 18px; margin-left: 14px;
color: #555; justify-self: flex-start;
} }
.join-button { .CL-my-chat-box-my-role {
padding: 10px 15px; margin-left: auto;
font-size: 16px; justify-self: flex-end;
color: white;
background-color: #007bff;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.add-members-header {
text-align: center;
}
.add-members-footer {
text-align: right;
margin-top: 5px;
}
.add-members-button {
background-color: #218838;
padding: 10px 15px;
font-size: 16px;
color: white;
border: none;
border-radius: 5px;
position: absolute;
margin-left: 502px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.add-members-button:hover {
background-color: #006509
}
.delete-chat-button {
background-color: #dc2e45;
border: none;
color: white;
font-size: 16px;
border-radius: 5px;
position: absolute;
cursor: pointer;
transition: background-color 0.3s ease;
padding: 10px 15px;
margin-left: 380px;
}
.delete-chat-button:hover {
background-color: #881527;
}
#newMemberLogin {
width: 93.5%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
}
.add-member-button {
background-color: #218838;
padding: 10px 15px;
font-size: 16px;
color: white;
border: none;
border-radius: 5px;
position: absolute;
margin-left: -105px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.join-button:hover {
background-color: #0056b3;
} }
.modal { .CL-my-chat-box-leave-btn {
display: none; margin-left: 10px;
position: fixed; margin-right: 8px;
z-index: 1; justify-self: flex-end;
left: 0; width: 16px;
top: 0; cursor: pointer;
}
/* The morbid thing */
table.id-str-input-table {
width: 100%; width: 100%;
height: 100%; border-collapse: collapse; /* Combine borders */
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
} }
.id-str-input-td1, .id-str-input-td2 {
.modal-content {
background-color: #fff;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 400px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.modal-header, .modal-footer {
padding: 10px;
color: #333;
}
.modal-header {
text-align: center;
}
.modal-footer {
text-align: right;
}
.modal input {
width: 93.5%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
}
.create-room-button {
display: block;
width: 100%;
padding: 10px;
font-size: 16px;
color: white;
background-color: #1609ab;
border: none; border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
margin-top: 20px;
} }
.id-str-input-td1 {
.create-room-button:hover { text-align: left;
background-color: #218838; padding-right: 5px;
white-space: nowrap; /* Prevent text wrap, keeping it in one line */
overflow: hidden; /* Hide overflow content */
text-overflow: ellipsis; /* Show ellipsis for overflowing text */
} }
.id-str-input-td2 {
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%; width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
z-index: 1000;
}
.overlay .add-members {
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
max-width: 400px;
width: 100%;
height: 18%;
position: fixed;
}
.overlay .delete-chat {
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
max-width: 400px;
width: 100%;
height: 18%;
position: fixed;
}
.delete-close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.delete-chat-header {
text-align: center;
}
.confirm {
background-color: #1609ab;
padding: 20px 70px;
font-size: 16px;
color: white;
border: none;
border-radius: 5px;
position: absolute;
margin-left: 20px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.cancel {
background-color: #1609ab;
padding: 20px 70px;
font-size: 16px;
color: white;
border: none;
border-radius: 5px;
position: absolute;
margin-left: 220px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
} }

View File

@ -1,59 +1,34 @@
/* I have no idea what is going on here */
body { body {
font-family: Arial, sans-serif;
display: flex; display: flex;
flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh; height: 100vh; /* Full viewport height */
margin: 0; margin: 0;
background-color: #e5e5e5;
} }
.form-container { .form-container {
width: 100%; background-color: #ffffff; /* Brighter box color */
max-width: 400px; padding: 20px;
background-color: white; border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
display: flex; width: 50%; /* Set width of the form */
flex-direction: column;
border-radius: 8px;
padding: 40px;
text-align: center;
} }
h1 { /* The morbid thing */
margin-bottom: 20px; table.logins-input-table {
color: #2F4F4F;
}
input {
width: 100%; width: 100%;
background: #f7f7f7; border-collapse: collapse; /* Combine borders */
font-size: 16px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
margin-bottom: 15px;
outline: none;
} }
.logins-input-td1, .logins-input-td2 {
button {
width: 100%;
padding: 15px;
border: none; border: none;
background-color: #0088cc;
color: white;
border-radius: 20px;
cursor: pointer;
outline: none;
font-size: 16px;
font-weight: bold;
transition: background-color 0.3s;
} }
.logins-input-td1 {
.error-msg { padding-right: 5px;
color: red; white-space: nowrap; /* Prevent text wrap, keeping it in one line */
background-color: #ffc0c0; overflow: hidden; /* Hide overflow content */
border-color: red; text-overflow: ellipsis; /* Show ellipsis for overflowing text */
border-radius: 5px; }
} .logins-input-td2 {
width: 100%;
}

View File

@ -1,129 +0,0 @@
body {
display: flex;
justify-content: center;
align-items: center;
height: 90vh;
background-color: #e5e5e5;
font-family: Arial, sans-serif;
}
.main-container {
width: 700px;
height: 700px;
border-color: antiquewhite;
background-color: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
border-radius: 10px;
}
.profile-header {
width: 700px;
height: 160px;
border-color: antiquewhite;
background-color: #0088cc;
border-radius: 10px;
position: relative;
}
.return {
background-color: #f0f0f0;
cursor: pointer;
width: 100px;
text-decoration: none;
color: black;
display: flex;
justify-content: center;
align-items: center;
height: 30px;
border-radius: 10px;
position: absolute;
left: 20px;
top: 25px;
border: none;
}
.return:hover{
text-decoration: underline;
color: #0088cc;
}
form {
display: flex;
flex-direction: column;
align-items: center;
}
.columns {
display: flex;
justify-content: center;
align-items: flex-start;
gap: 20px;
margin-bottom: 20px;
}
.column {
display: flex;
flex-direction: column;
align-items: center;
}
.add {
width: 100px;
height: 40px;
border-width: 2px;
cursor: pointer;
font-size: 16px;
border-radius: 10px;
}
.add:hover {
background-color: #007bb5;
}
.image-button:hover {
opacity: 0.8;
}
.image-button:active {
transform: scale(0.95);
}
#login {
font-family: Arial, sans-serif;
font-size:16px;
width: 150px;
height: 20px;
border-radius: 10px;
border-color: #2F4F4F;
}
#username {
font-family: Arial, sans-serif;
font-size:16px;
width: 150px;
height: 20px;
margin-bottom: 1px;
margin-top: 50px;
border-radius: 10px;
border-color: #2F4F4F;
}
#bio {
height: 150px;
width: 500px;
padding: 10px;
box-sizing: border-box;
font-family: Arial, sans-serif;
font-size:14px;
text-align: left;
vertical-align: top;
margin-bottom: 5px;
}
.save {
cursor:pointer;
font-size: 16px;
border-radius: 15px;
border-color: #2F4F4F;
height: 40px;
width: 150px;
}
.save:hover {
background-color: #007bb5;
}
.avatar {
border-radius: 50%;
object-fit: cover;
}

View File

@ -1,77 +0,0 @@
dy {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #e5e5e5;
}
.form-container {
width: 100%;
max-width: 400px;
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
border-radius: 8px;
padding: 40px;
text-align: center;
}
h1 {
margin-bottom: 20px;
color: #2F4F4F;
}
input {
width: 100%;
background: #f7f7f7;
font-size: 16px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 20px;
margin-bottom: 15px;
outline: none;
}
button {
width: 100%;
padding: 15px;
border: none;
background-color: #0088cc;
color: white;
border-radius: 20px;
cursor: pointer;
outline: none;
font-size: 16px;
font-weight: bold;
transition: background-color 0.3s;
}
button:hover,
button:focus-visible {
background-color: #007bb5;
}
.hide-cursor::placeholder {
color: #000;
}
.hide-cursor {
caret-color: transparent;
}
.no-select {
-webkit-user-select: none; /* Для Safari */
-moz-user-select: none; /* Для Firefox */
user-select: none; /* Для всех остальных браузеров */
}
div {
color: red;
font-size: 15px;
margin-top: 10px;
display: none;
}

BIN
assets/gif/loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

20
assets/img/add.svg Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="128"
height="128"
viewBox="0 0 3.84 3.84"
fill="none"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 1.9142674,3.7842088 c -0.8814997,0 -1.32224859,0 -1.59609402,-0.2738529 C 0.04432607,3.2365216 0.04432607,2.7957577 0.04432607,1.9142674 c 0,-0.8814997 0,-1.32224859 0.27384731,-1.59609402 C 0.59201881,0.04432607 1.0327677,0.04432607 1.9142674,0.04432607 c 0.8814903,0 1.3222542,0 1.5960885,0.27384731 0.2738529,0.27384543 0.2738529,0.71459432 0.2738529,1.59609402 0,0.8814903 0,1.3222542 -0.2738529,1.5960885 C 3.2365216,3.7842088 2.7957577,3.7842088 1.9142674,3.7842088 Z m 0,-2.5711694 c 0.077453,0 0.1402457,0.062791 0.1402457,0.1402456 v 0.4207368 h 0.4207367 c 0.077453,0 0.1402456,0.062793 0.1402456,0.1402456 0,0.077453 -0.062793,0.1402457 -0.1402456,0.1402457 H 2.0545131 v 0.4207367 c 0,0.077453 -0.062793,0.1402456 -0.1402457,0.1402456 -0.077453,0 -0.1402456,-0.062793 -0.1402456,-0.1402456 V 2.0545131 H 1.353285 c -0.077455,0 -0.1402456,-0.062793 -0.1402456,-0.1402457 0,-0.077453 0.062791,-0.1402456 0.1402456,-0.1402456 H 1.7740218 V 1.353285 c 0,-0.077455 0.062793,-0.1402456 0.1402456,-0.1402456 z"
fill="#1c274c"
id="path1"
style="stroke-width:0.186994" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

18
assets/img/delete.svg Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
fill="#000000"
width="128"
height="128"
viewBox="-1.7 0 3.264 3.264"
class="cf-icon-svg"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<path
d="M 1.4376583,1.6137431 A 1.5211864,1.5211864 0 1 1 -0.08352807,0.09255663 1.5209944,1.5209944 0 0 1 1.4376583,1.6137431 Z m -1.30733258,0.00192 0.58257383,-0.582766 A 0.15217629,0.15217629 0 0 0 0.49770077,0.81769971 L -0.08468094,1.4004657 -0.66763909,0.81769971 A 0.15217629,0.15217629 0 0 0 -0.88283792,1.0328985 l 0.5829582,0.5827659 -0.58276597,0.5827661 a 0.15217629,0.15217629 0 0 0 0.21519874,0.2150066 l 0.58257388,-0.582766 0.58276608,0.582766 A 0.15217629,0.15217629 0 0 0 0.71309179,2.1982382 Z"
id="path1"
style="display:inline;fill:#ff0000;fill-opacity:1;stroke-width:0.192143" />
</svg>

After

Width:  |  Height:  |  Size: 929 B

BIN
assets/img/exit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
assets/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 B

30
assets/img/link.svg Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
fill="#000000"
version="1.1"
id="Capa_1"
width="128"
height="128"
viewBox="0 0 70.75936 70.75936"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs3" />
<g
id="g3"
style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1"
transform="matrix(0.1490509,0,0,0.14948634,3.4471925,2.5065977)">
<g
id="g2"
style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1">
<path
d="m 409.657,32.474 c -43.146,-43.146 -113.832,-43.146 -156.978,0 l -84.763,84.762 c 29.07,-8.262 60.589,-6.12 88.129,6.732 l 44.063,-44.064 c 17.136,-17.136 44.982,-17.136 62.118,0 17.136,17.136 17.136,44.982 0,62.118 l -55.386,55.386 -36.414,36.414 c -17.136,17.136 -44.982,17.136 -62.119,0 l -47.43,47.43 c 11.016,11.017 23.868,19.278 37.332,24.48 36.415,14.382 78.643,8.874 110.467,-16.219 3.06,-2.447 6.426,-5.201 9.18,-8.262 l 57.222,-57.222 34.578,-34.578 c 43.453,-43.145 43.453,-113.525 10e-4,-156.977 z"
id="path1"
style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1" />
<path
d="m 184.135,320.114 -42.228,42.228 c -17.136,17.137 -44.982,17.137 -62.118,0 -17.136,-17.136 -17.136,-44.981 0,-62.118 l 91.8,-91.799 c 17.136,-17.136 44.982,-17.136 62.119,0 l 47.43,-47.43 c -11.016,-11.016 -23.868,-19.278 -37.332,-24.48 -38.25,-15.3 -83.232,-8.262 -115.362,20.502 -1.53,1.224 -3.06,2.754 -4.284,3.978 l -91.8,91.799 c -43.146,43.146 -43.146,113.832 0,156.979 43.146,43.146 113.832,43.146 156.978,0 l 82.927,-83.845 c -42.23,9.791 -52.022,8.568 -88.13,-5.814 z"
id="path2"
style="fill:#c8f5ff;fill-opacity:1;stroke:#6adeef;stroke-width:8.62702;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

28
assets/img/list-rooms.svg Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="127.99999"
height="127.99999"
viewBox="0 0 33.866664 33.866664"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 6.2911505,3.4844006 V 6.6404405 H 3.0362141 V 28.776832 h 4.7418555 v 2.929792 H 0.58956039 V 3.438992 Z"
id="path63" />
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 27.557741,31.386555 v -3.15604 h 3.254937 V 6.0941238 h -4.741856 v -2.929792 h 7.188509 V 31.431963 Z"
id="path63-2" />
<path
id="rect63"
style="fill:#fdffff;fill-opacity:1;fill-rule:nonzero;stroke:#e1e1e1;stroke-width:0.529167;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 8.7183307,8.3901854 c -2.467802,0 -4.4545084,1.9867066 -4.4545084,4.4545086 v 5.357812 c 0,2.467802 1.9867064,4.454509 4.4545084,4.454509 0,0 6.6668103,0 10.2365843,0 1.184667,0 1.8523,1.573101 2.973462,1.946651 1.827305,0.608823 5.758305,0.474906 5.758305,0.474906 0,0 -1.204106,-0.984163 -2.336589,-2.042039 -0.307888,-0.287603 -0.182877,-1.488944 0.134028,-1.896921 0.674916,-0.868872 0.918918,-1.680982 0.918918,-2.937106 v -5.357812 c 0,-2.467802 -1.986706,-4.4545086 -4.454508,-4.4545086 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

20
assets/img/return.svg Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="128"
height="127.99999"
viewBox="0 0 33.866667 33.866664"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<path
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.294159;stroke-linejoin:round;stroke-opacity:1"
d="m 0.65443061,23.377766 10.20536539,-7.351693 -0.06427,3.749871 c 0,0 6.384296,0.115304 9.607291,0.115304 1.824632,0 4.010317,0.117998 5.361398,-1.065432 0.797635,-0.698657 1.041209,-1.943308 0.954461,-2.97946 -0.118465,-1.414973 -0.417733,-2.830021 -1.492491,-3.799298 -0.964892,-0.870193 -2.338758,-1.449022 -3.659301,-1.465666 -1.347581,-0.01698 -4.311633,-0.01476 -4.311633,-0.01476 l 0.02678,-6.0468658 c 0,0 5.209174,-0.1269759 7.836958,-0.1269759 2.090574,0 3.730335,0.2813604 5.374446,1.5274776 1.165995,0.8837399 2.874757,2.1212447 2.874757,3.8971142 0,3.5737799 -0.03341,6.8479459 -0.03341,10.6502529 0,3.155641 -2.159421,4.126978 -3.036427,4.888332 -1.783594,1.548393 -4.069763,1.553329 -6.468859,1.553329 -4.709142,0 -13.295569,0 -13.295569,0 v 3.244296 z"
id="path1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="127.99999"
height="127.99999"
viewBox="0 0 33.866664 33.866664"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<path
id="path11"
style="fill:#e6e6e6;fill-opacity:1;stroke:#b3b3b3;stroke-width:0.792427;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 9.8376422,3.4995279 c -0.413454,0.04422 -0.9249931,0.2733392 -1.5859497,0.7720459 -2.15303,1.6245101 -1.0645387,2.7213368 -0.2780192,3.6576578 0.9643744,1.1480481 0.5272069,2.4297214 -0.2444295,3.5413854 -1.0846469,1.562605 -1.8094411,2.689497 -3.1812011,2.618445 -1.7185528,-0.08902 -3.2061983,0.950419 -3.1817179,2.618962 0.020151,1.373449 0.960489,2.763844 2.9987833,2.826184 1.3729571,0.04199 2.35275,1.613748 2.9993001,2.825667 0.6465497,1.211919 1.3490594,3.417259 0.6769613,4.064868 -0.989134,0.953094 -1.312826,2.263816 0.6769612,4.064868 1.2829023,1.161218 2.6693573,0.639536 3.7666953,-0.946195 1.179214,-1.70405 2.337505,-1.523233 4.127396,-1.422652 1.371435,0.07707 2.886284,-0.325958 3.858679,1.446423 0.660704,1.204262 1.722209,2.911554 3.85868,1.445906 1.132687,-0.77704 2.468135,-2.085182 0.947745,-4.010091 -0.610564,-0.773014 -0.532201,-2.186383 0.948263,-4.010607 1.255317,-1.546798 1.98091,-2.751433 3.181201,-2.618962 1.365308,0.150686 2.84107,-0.224934 3.181718,-2.618445 0.366273,-2.57357 -1.941907,-2.695322 -3.312976,-2.778641 C 28.1324,14.906856 28.128014,13.488893 26.590624,12.103137 25.053235,10.717381 25.023658,9.341954 26.190649,8.2930337 27.35764,7.2441129 27.633021,6.3291961 25.719877,4.5222045 24.596532,3.461188 23.254965,3.1719841 21.630204,4.9759236 20.911774,5.7735833 19.336054,7.1894888 17.196366,7.1086099 15.002057,7.0256665 13.838949,7.4611865 12.285555,5.8053303 11.434493,4.8981315 11.078004,3.3668691 9.8376422,3.4995279 Z m 6.7892498,6.5215661 a 7.4172139,7.0961218 0 0 1 7.417118,7.096207 7.4172139,7.0961218 0 0 1 -7.417118,7.096208 7.4172139,7.0961218 0 0 1 -7.4171184,-7.096208 7.4172139,7.0961218 0 0 1 7.4171184,-7.096207 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

24
assets/img/user.svg Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="127.99999"
height="127.99999"
viewBox="0 0 33.866664 33.866664"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<path
style="fill:#000080;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.465;stroke-linejoin:round;stroke-dasharray:none"
d="M 9.3745975,32.81176 H 25.632941 c 0,0 0.08996,-6.044498 -0.03778,-10.988966 0.0321,-3.524476 -1.327193,-6.290541 -8.363226,-6.155275 -7.036034,0.135265 -7.7455423,2.805955 -7.7693511,5.331649 -0.034594,3.669826 -0.087981,11.812592 -0.087981,11.812592 z"
id="path7" />
<path
id="path8"
style="fill:#000080;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.965;stroke-linejoin:round;stroke-dasharray:none"
d="M 17.963761,1.3260172 A 11.612947,6.192831 0 0 0 6.3510334,7.5189208 11.612947,6.192831 0 0 0 17.963761,13.711824 11.612947,6.192831 0 0 0 29.576489,7.5189208 11.612947,6.192831 0 0 0 17.963761,1.3260172 Z m -3.947046,2.876827 a 1.5186517,3.4351659 0 0 1 1.518771,3.4349323 1.5186517,3.4351659 0 0 1 -1.518771,3.4354495 1.5186517,3.4351659 0 0 1 -1.518253,-3.4354495 1.5186517,3.4351659 0 0 1 1.518253,-3.4349323 z m 6.47299,0.2160074 a 1.5186517,3.4351659 0 0 1 1.518254,3.4349324 1.5186517,3.4351659 0 0 1 -1.518254,3.435449 1.5186517,3.4351659 0 0 1 -1.51877,-3.435449 1.5186517,3.4351659 0 0 1 1.51877,-3.4349324 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,188 @@
let LocalHistoryId = 0;
function genSentBase(){
return {
'chatUpdReq': {
'LocalHistoryId': LocalHistoryId,
'chatId': openedchat.id
}
};
}
let members = new Map();
let memberBoxes = new Map();
let myRoleHere = null; // Dung local state updates should be updated first
let userDeletionWinStoredUId = -1;
function shouldShowDeleteButton(memberSt){
return userinfo.uid !== memberSt.userId && myRoleHere === userChatRoleAdmin && memberSt.roleHere !== userChatRoleDeleted;
}
function updateBoxWithSt(box, memberSt){
let ID = memberSt.userId;
let roleP = box.querySelector(".CM-member-box-role");
roleP.innerText = memberSt.roleHere;
box.style.backgroundColor = roleToColor(memberSt.roleHere);
box.querySelector(".CM-member-box-leave-btn").style.display =
(shouldShowDeleteButton(memberSt) ? "block" : "none");
}
function convertMemberStToBox(memberSt){
let ID = memberSt.userId;
let userProfileURI = "/user/" + memberSt.nickname;
let box = document.createElement("div");
box.className = "dynamic-block-list-el CM-member-box";
box.style.backgroundColor = roleToColor(memberSt.roleHere);
let inBoxNickname = document.createElement("a");
box.appendChild(inBoxNickname);
inBoxNickname.className = "entity-nickname-txt CM-member-box-nickname";
inBoxNickname.innerText = memberSt.nickname;
inBoxNickname.href = userProfileURI;
let inBoxName = document.createElement("a");
box.appendChild(inBoxName);
inBoxName.className = "entity-reg-field-txt CM-member-box-name";
inBoxName.innerText = memberSt.name;
inBoxName.href = userProfileURI;
let inBoxUserRoleHere = document.createElement("p");
box.appendChild(inBoxUserRoleHere);
inBoxUserRoleHere.className = "entity-reg-field-txt CM-member-box-role";
inBoxUserRoleHere.innerText = memberSt.roleHere;
let inBoxLeaveBtn = document.createElement("img");
box.appendChild(inBoxLeaveBtn);
inBoxLeaveBtn.className = "CM-member-box-leave-btn";
inBoxLeaveBtn.src = "/assets/img/delete.svg";
inBoxLeaveBtn.onclick = function (ev) {
if (ev.button !== 0)
return;
userDeletionWinStoredUId = ID;
document.getElementById("user-deletion-win-title").innerText =
"Do you really want to kick user " + memberSt.nickname + "?";
activatePopupWindowById("user-deletion-win");
};
box.querySelector(".CM-member-box-leave-btn").style.display =
(shouldShowDeleteButton(memberSt) ? "block" : "none");
return box;
}
function updateLocalStateFromChatUpdResp(chatUpdResp){
LocalHistoryId = chatUpdResp.HistoryId;
// 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
for (let memberSt of chatUpdResp.members){
console.log([memberSt, userinfo.uid, myRoleHere]);
if (memberSt.userId === userinfo.uid && myRoleHere !== memberSt.roleHere){
myRoleHere = memberSt.roleHere;
for (let [id, memberSt] of members){
let box = memberBoxes.get(id);
updateBoxWithSt(box, memberSt);
}
document.getElementById("CM-btn-add").style.display =
(memberSt.roleHere === userChatRoleAdmin ? "block" : "none");
console.log("DEBUG " + (memberSt.roleHere === userChatRoleAdmin ? "block" : "none"));
break;
}
}
for (let memberSt of chatUpdResp.members){
let id = memberSt.userId;
if (members.has(id)){
updateBoxWithSt(memberBoxes.get(id), memberSt);
} else {
if (memberSt.roleHere !== userChatRoleDeleted){
members.set(id, memberSt);
let box = convertMemberStToBox(memberSt);
memberBoxes.set(id, box);
literalMemberList.appendChild(box);
}
}
}
}
function updateLocalStateFromRecv(Recv){
updateLocalStateFromChatUpdResp(Recv.chatUpdResp);
}
function configureSummonUserInterface(){
document.getElementById("user-summoning-yes").onclick = function(ev ){
if (ev.button !==0)
return;
let nickname = String(document.getElementById("summoned-user-nickname").value);
let isReadOnly = document.getElementById("summoned-user-is-read-only").checked;
deactivateActivePopup();
let Sent = genSentBase();
Sent.nickname = nickname;
Sent.makeReadOnly = Boolean(isReadOnly);
apiRequest("addMemberToChat", Sent).
then((Recv) => {
updateLocalStateFromRecv(Recv);
}).catch((e) => {
console.log(e);
alert("Failed to add user to chat");
});
};
document.getElementById("user-summoning-no").onclick = function (ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
};
document.getElementById("CM-btn-add").onclick = function(ev) {
if (ev.button !== 0)
return;
document.getElementById("summoned-user-nickname").value = "";
// read-only flag persists throughout user summoning sessions, and IT IS NOT A BUG
activatePopupWindowById("user-summoning-win");
};
}
/* Popup activation button is configured for each box separately */
function configureKickUserInterfaceWinPart(){
document.getElementById("user-deletion-yes").onclick = function (ev){
if (ev.button !== 0)
return;
deactivateActivePopup();
if (userDeletionWinStoredUId < 0)
throw new Error("Karaul");
let Sent = genSentBase();
Sent.userId = userDeletionWinStoredUId;
apiRequest("removeMemberFromChat", Sent).
then((Recv) => {
updateLocalStateFromRecv(Recv);
}).catch((e) => {
console.log(e);
alert("Failed to kick user from chat");
});
}
document.getElementById("user-deletion-no").onclick = function (ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
};
}
__mainloopDelayMS = 5000;
__guestMainloopPollerAction = function (){
console.log("Hello, world");
apiRequest("chatPollEvents", genSentBase()).
then((Recv) => {
console.log(Recv);
updateLocalStateFromRecv(Recv);
});
}
window.onload = function(){
console.log("Page loaded");
configureSummonUserInterface();
configureKickUserInterfaceWinPart();
updateLocalStateFromChatUpdResp(initial_chatUpdResp);
mainloopPoller();
}

View File

@ -1,162 +1,449 @@
let members = [ let LocalHistoryId = 0;
{ username: 'Адель', nickname: 'cold_siemens52', avatar: 'https://sun9-59.userapi.com/impg/t8GhZ7FkynVifY1FQCnaf31tGprbV_rfauZzgg/fSq4lyc6V0U.jpg?size=1280x1280&quality=96&sign=e3c309a125cb570d2e18465eba65f940&type=album' },
{ username: 'Антон', nickname: 'antyak_01', avatar: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png' },
{ username: 'Владимир', nickname: 'kkrkk2006', avatar: 'https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png' }
];
let currentHistoryId = 0;
let currentChatID = null;
function renderMembersList() {
const membersListBody = document.getElementById('members-list-body');
membersListBody.innerHTML = '';
members.forEach((member, index) => { let members = new Map();
const memberItem = document.createElement('li');
memberItem.innerHTML = `
<img src="${member.avatar}" alt="${member.username}">
<a href="profile.html">${member.username}</a>
<button class="delete-member" onclick="deleteMember(${index})">Удалить из чата</button>
`;
membersListBody.appendChild(memberItem);
});
}
function deleteMember(index) { let loadedMessages = new Map(); // messageSt objects
members.splice(index, 1); /*
renderMembersList(); container: EL, box: EL, offset: number (msgPres) */
} let visibleMessages = new Map(); // HTMLElement objects
async function sendMessage() { let anchoredMsg = -1;
const chatMessages = document.getElementById('chat-messages'); let visibleMsgSegStart = -1;
const chatInput = document.getElementById('chat-input'); let visibleMsgSegEnd = -2;
const message = chatInput.value; let offsetOfAnchor = 500;
let highestPoint = null;
let lowestPoint = null;
if (message.trim() !== '') { let lastMsgId = -1;
const request = { let myRoleHere = null; // Dung local state updates should be updated first
'chatId': currentChatID,
'LocalHistoryId': currentHistoryId,
'content': {
'text': message
}
};
const response = await fetch("/internalapi/sendMessage", { // Would start with true if opened `/chat/<>`
method: 'POST', let bumpedAtTheBottom = false;
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
const res = await response.json(); // Hidden variable. When deletion window popup is active
// Persists from popup activation until popup deactivation
let storeHiddenMsgIdForDeletionWin = -1;
if (res.update) { // Positive in production, negative for debug
const update = res.update[0]; let softZoneSz = -150;
currentHistoryId = update.HistoryId; let chatPadding = 300;
const messageElement = document.createElement('div'); function genSentBase(){
messageElement.classList.add('chat-message'); return {
'chatUpdReq': {
const avatarElement = document.createElement('div'); 'LocalHistoryId': LocalHistoryId,
avatarElement.classList.add('avatar'); 'chatId': openedchat.id
const avatarImage = document.createElement('img');
avatarImage.src = 'https://sun9-59.userapi.com/impg/t8GhZ7FkynVifY1FQCnaf31tGprbV_rfauZzgg/fSq4lyc6V0U.jpg?size=1280x1280&quality=96&sign=e3c309a125cb570d2e18465eba65f940&type=album';
avatarElement.appendChild(avatarImage);
const messageContentElement = document.createElement('div');
messageContentElement.classList.add('message-content');
const usernameElement = document.createElement('div');
usernameElement.classList.add('username');
usernameElement.textContent = await getUserName();
const textElement = document.createElement('div');
textElement.classList.add('text');
textElement.textContent = message;
messageContentElement.appendChild(usernameElement);
messageContentElement.appendChild(textElement);
messageElement.appendChild(avatarElement);
messageElement.appendChild(messageContentElement);
chatMessages.appendChild(messageElement);
chatInput.value = '';
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}
}
function openMembersList() {
renderMembersList();
document.getElementById("members-list").style.display = "block";
document.getElementById("overlay").style.display = "flex";
}
function closeMembersList() {
document.getElementById("members-list").style.display = "none";
document.getElementById("overlay").style.display = "none";
}
document.getElementById('chat-input').addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
sendMessage();
}
});
async function getUserID() {
const response = await fetch('/internalapi/mirror', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
const res = await response.json();
return res.id;
}
async function getChatID() {
const chatNickname = window.location.pathname.split('/').pop();
const response = await fetch('/internalapi/getChatList', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
});
const res = await response.json();
for (const chat of res.chats) {
if (chat.content.nickname === chatNickname) {
return chat.id;
}
}
return -1;
}
async function editMessage(new_message) {
const req = {
'chatId': currentChatID,
'LocalHistoryId': currentHistoryId,
'id': getUserID(),
'content': {
'text': new_message
} }
}; };
const res = await fetch('/internalapi/editMessage', { }
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(req)
});
const response = await res.json(); function genSentBaseGMN(){
if (response.update) { let Sent = genSentBase();
currentHistoryId = response.update[0].HistoryId; Sent.amount = 1;
return Sent;
}
function getChatWgSz(){
let chatWg = document.getElementById("chat-widget");
return [chatWg.offsetWidth, chatWg.offsetHeight];
}
function elSetOffsetInChat(el, offset){
el.style.bottom = String(offset) + "px";
}
function isMissingPrimaryMsgHeap(){
return lastMsgId >= 0 && anchoredMsg < 0;
}
function isMissingTopMsgHeap(){
let [W, H] = getChatWgSz();
return anchoredMsg >= 0 && (highestPoint < H + softZoneSz && visibleMsgSegStart > 0);
}
function isMissingBottomMsgHeap(){
return anchoredMsg >= 0 && (lowestPoint > - softZoneSz && visibleMsgSegEnd < lastMsgId);
}
function updateOffsetOfVisibleMsg(msgId, offset){
visibleMessages.get(msgId).container.style.bottom = String(offset) + "px";
}
function updateOffsetsUpToTop(){
let offset = offsetOfAnchor;
for (let curMsg = anchoredMsg; curMsg >= visibleMsgSegStart; curMsg--){
updateOffsetOfVisibleMsg(curMsg, offset);
let height = visibleMessages.get(curMsg).container.offsetHeight;
offset += height + 5;
}
return offset;
}
function updateOffsetsDown(){
let offset = offsetOfAnchor;
for (let curMsg = anchoredMsg + 1; curMsg <= visibleMsgSegEnd; curMsg++){
let height = visibleMessages.get(curMsg).container.offsetHeight;
offset -= (height + 5);
updateOffsetOfVisibleMsg(curMsg, offset);
}
return offset;
}
function updateOffsetsSane(){
if (anchoredMsg < 0)
return;
highestPoint = updateOffsetsUpToTop();
lowestPoint = updateOffsetsDown();
}
function heightOfPreloadGhost(){
let [W, H] = getChatWgSz();
return Math.min(H * 0.9, Math.max(H * 0.69, 30));
}
function updateOffsets(){
let spinnerTop = document.getElementById("top-loading");
let spinnerBottom = document.getElementById("bottom-loading");
let SbH = spinnerBottom.offsetHeight;
if (anchoredMsg < 0){
hideHTMLElement(spinnerBottom);
elSetOffsetInChat(spinnerTop, chatPadding);
setElementVisibility(spinnerTop, isMissingPrimaryMsgHeap());
} else {
updateOffsetsSane();
let [W, H] = getChatWgSz();
let lowestLowestPoint = isMissingBottomMsgHeap() ? lowestPoint - heightOfPreloadGhost(): lowestPoint;
let highestHighestPoint = isMissingTopMsgHeap() ? highestPoint + heightOfPreloadGhost() : highestPoint;
if (lowestLowestPoint > chatPadding || (highestHighestPoint - lowestLowestPoint) <= H - chatPadding * 2) {
bumpedAtTheBottom = true;
offsetOfAnchor += (-lowestLowestPoint + chatPadding);
updateOffsetsSane();
} else if (highestHighestPoint < H - chatPadding) {
offsetOfAnchor += (-highestHighestPoint + (H - chatPadding));
updateOffsetsSane();
}
/* Messages weere updated (and only them). They were talking with ghosts.
Now we are trying to show spinners of ghosts */
elSetOffsetInChat(spinnerTop, highestPoint);
setElementVisibility(spinnerTop, isMissingTopMsgHeap());
elSetOffsetInChat(spinnerBottom, lowestPoint - SbH);
setElementVisibility(spinnerBottom, isMissingBottomMsgHeap());
} }
} }
document.addEventListener("DOMContentLoaded", async function() {
currentChatID = await getChatID(); function shouldShowDeleteMesgBtn(messageSt){
}); return !messageSt.isSystem && messageSt.exists && (messageSt.myRoleHere !== userChatRoleReadOnly) &&(
messageSt.myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid);
}
// todo: fix messageboxes
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
* Supercontainer.container is persistent, Supercontainer.box can change it's class */
function updateMessageSupercontainer(supercontainer, messageSt){
let box = supercontainer.box;
setElementVisibility(box.querySelector(".message-box-button-delete"), shouldShowDeleteMesgBtn(messageSt), "inline");
box.className = getMsgFullTypeClassName(messageSt);
// Notice, that no check of previous state is performed. Double loading is a rare event, 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 convertMessageStToSupercontainer(messageSt){
let container = document.createElement("div");
container.className = "message-supercontainer";
let box = document.createElement("div");
container.appendChild(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";
inTopPartSenderName.innerText = senderMemberSt.name;
inTopPartSenderName.href = senderProfileURI;
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 message-box-button-delete";
inTopPartButtonDelete.src = "/assets/img/delete.svg";
inTopPartButtonDelete.onclick = (ev) => {
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");
};
setElementVisibility(inTopPartButtonDelete, shouldShowDeleteMesgBtn(messageSt), "inline");
let inTopPartButtonGetLink = document.createElement("img");
topPart.appendChild(inTopPartButtonGetLink);
inTopPartButtonGetLink.className = "message-box-button";
inTopPartButtonGetLink.src = "/assets/img/link.svg";
inTopPartButtonGetLink.onclick = (ev) => {
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";
if (messageSt.exists){
if (messageSt.isSystem)
msgPart.innerText = decodeSystemMessage(messageSt.text);
else
msgPart.innerText = messageSt.text;
} else
msgPart.innerText = msgErased;
return {'container': container, 'box': box, 'offset': 0};
}
function makeVisible(msgId){
let supercontainer = convertMessageStToSupercontainer(loadedMessages.get(msgId));
const chatWin = document.getElementById("chat-widget");
chatWin.appendChild(supercontainer.container);
visibleMessages.set(msgId, supercontainer);
}
function opaNewMessageSt(messageSt){
let msgId = messageSt.id;
if (loadedMessages.has(msgId)){
loadedMessages.set(msgId, messageSt);
if (visibleMessages.has(msgId)){
updateMessageSupercontainer(visibleMessages.get(msgId), messageSt);
}
} else {
loadedMessages.set(msgId, messageSt);
if (anchoredMsg < 0){
anchoredMsg = msgId;
visibleMsgSegStart = msgId;
visibleMsgSegEnd = msgId;
makeVisible(msgId);
} else if (msgId + 1 === visibleMsgSegStart) {
visibleMsgSegStart--;
makeVisible(msgId);
while (loadedMessages.has(visibleMsgSegStart - 1)){
visibleMsgSegStart--;
makeVisible(visibleMsgSegStart);
}
} else if (msgId - 1 === visibleMsgSegEnd){
visibleMsgSegEnd++;
makeVisible(msgId);
while (loadedMessages.has(visibleMsgSegEnd + 1)){
visibleMsgSegEnd++;
makeVisible(visibleMsgSegEnd);
}
}
}
}
function canISendMessages(){
return myRoleHere === userChatRoleRegular || myRoleHere === userChatRoleAdmin;
}
function updateLocalStateFromChatUpdRespBlind(chatUpdResp){
console.log(anchoredMsg, offsetOfAnchor, 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, sc] of visibleMessages){
updateMessageSupercontainer(sc, loadedMessages.get(msgId));
}
setElementVisibility(document.getElementById("message-input"), canISendMessages());
}
}
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();
}
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(){
return isMissingPrimaryMsgHeap() || isMissingTopMsgHeap() || isMissingBottomMsgHeap();
}
async function tryLoadWhitespaceSingle(){
console.log('tryLoadWhitespaceSingle');
if (isMissingPrimaryMsgHeap()){
await requestMessageNeighbours(-1, "backward");
} else if (isMissingTopMsgHeap()){
await requestMessageNeighbours(visibleMsgSegStart, "backward");
} else if (isMissingBottomMsgHeap()){
await requestMessageNeighbours(visibleMsgSegEnd, "forward");
}
}
async function loadWhitespaceMultitry(){
if (needToLoadWhitespace()){
cancelMainloopTimeout();
do {
try {
await tryLoadWhitespaceSingle();
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 {
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("Page was loaded");
document.body.addEventListener("wheel", function (event) {
// event.preventDefault();
bumpedAtTheBottom = false;
offsetOfAnchor += event.deltaY / 3;
updateOffsets();
loadWhitespaceMultitry().then(dopDopYesYes);
});
document.getElementById("message-input").addEventListener("keyup", function (event) {
if (event.ctrlKey && event.key === 'Enter'){
let textarea = document.getElementById("message-input");
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;
elSetOffsetInChat(document.getElementById("debug-line-lowest"), -softZoneSz);
elSetOffsetInChat(document.getElementById("debug-line-highest"), H + softZoneSz);
elSetOffsetInChat(document.getElementById("debug-line-top-padding"), H - chatPadding);
elSetOffsetInChat(document.getElementById("debug-line-bottom-padding"), chatPadding)
};
window.addEventListener("resize", chatWgDebugLinesFnc);
chatWgDebugLinesFnc();
configureMsgDeletionPopupButtons();
updateLocalStateFromChatUpdRespBlind(initial_chatUpdResp);
setMainloopTimeout();
loadWhitespaceMultitry();
}

26
assets/js/common-popup.js Normal file
View File

@ -0,0 +1,26 @@
let activePopupWinId = "";
function activatePopupWindow__(el){
let veil = document.createElement("div");
veil.id = "popup-overlay-veil-OBJ"
veil.className = "popup-overlay-veil";
veil.style.display = "block";
document.body.appendChild(veil);
el.style.display = "block";
}
function activatePopupWindowById(id){
if (activePopupWinId !== "")
return;
/* Lmao, this thing is just... SO unsafe */
activePopupWinId = id;
activatePopupWindow__(document.getElementById(id))
}
function deactivateActivePopup(){
if (activePopupWinId === "")
return
document.getElementById("popup-overlay-veil-OBJ").remove();
document.getElementById(activePopupWinId).style.display = "none";
activePopupWinId = "";
}

73
assets/js/common.js Normal file
View File

@ -0,0 +1,73 @@
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)});
let B = await A.json();
if (B.status !== 0)
throw Error("Server returned non-zero status");
return B;
}
/* Framework for pages with mainloop (it can be npt only polling, but also literally anything else */
let __mainloopDelayMs = 3000;
let mainloopTimeout = null;
let __guestMainloopPollerAction = null;
function setMainloopTimeout(){
mainloopTimeout = setTimeout(mainloopPoller, __mainloopDelayMs);
}
function cancelMainloopTimeout(){
clearTimeout(mainloopTimeout);
mainloopTimeout = null;
}
function mainloopPoller(){
try {
if (__guestMainloopPollerAction)
__guestMainloopPollerAction();
} catch (error){
console.log(error)
}
setMainloopTimeout();
}
// 1
const userChatRoleAdmin = "admin";
// 2
const userChatRoleRegular = "regular";
// 3
const userChatRoleReadOnly = "read-only";
// 4
const userChatRoleDeleted = "not-a-member";
function roleToColor(role) {
if (role === userChatRoleAdmin) {
return "#aafff3";
} else if (role === userChatRoleRegular){
return "#ffffff";
} else if (role === userChatRoleReadOnly){
return "#bfb2b2";
} else if (role === userChatRoleDeleted) {
return "#fb4a4a";
}
return "#286500" // Bug
}
// todo: replace it with translation
const msgErased = "[ ERASED ]";
function hideHTMLElement(el){
el.style.display = "none";
}
function showHTMLElement(el){
el.style.display = "block";
}
function setElementVisibility(el, isVisible, howVisible = "block"){
el.style.display = isVisible ? howVisible : "none";
}

View File

@ -1,186 +1,188 @@
let rooms = {}; let LocalHistoryId = 0;
let roomToDelete = null;
let currentRoom = null;
let currentHistoryId = 0;
function openRoom(currentRoom) { function genSentBase(){
alert('Вы вошли в комнату: ' + currentRoom); return {
} 'chatListUpdReq': {
'LocalHistoryId': LocalHistoryId
function closeAdd() {
document.getElementById('add_members').style.display = 'none';
}
function openAdd() {
document.getElementById('add_members').style.display = 'flex';
}
function openConfirm(roomNickname) {
roomToDelete = roomNickname;
document.getElementById("delete-chat").style.display = "flex";
}
function closeConfirm() {
roomToDelete = null;
document.getElementById("delete-chat").style.display = "none";
}
function deleteChat() {
if (roomToDelete && rooms[roomToDelete]) {
delete rooms[roomToDelete];
removeRoomFromList(roomToDelete);
closeConfirm();
} else {
alert("Не удалось найти выбранную комнату.");
}
}
function addMember() {
const login = document.getElementById('newMemberLogin').value;
if (login) {
alert(`Участник с никнеймом '${login}' добавлен`);
closeAdd();
} else {
alert('Пожалуйста, введите логин участника');
}
}
function openCreateRoomModal() {
document.getElementById('createRoomModal').style.display = 'block';
}
function closeCreateRoomModal() {
document.getElementById('createRoomModal').style.display = 'none';
}
async function createRoom() {
const errorElement = document.getElementById('error');
const roomName = document.getElementById('newRoomName').value.trim();
const roomNickname = document.getElementById('newRoomNickname').value.trim();
errorElement.style.display = 'none';
errorElement.textContent = '';
if (roomName === '' || roomNickname === '') {
errorElement.textContent = 'Пожалуйста, заполните все поля';
errorElement.style.display = 'block';
return;
}
const request = {
LocalHistoryId: currentHistoryId,
content: {
name: roomName,
nickname: roomNickname
} }
}; };
}
try { let myChats = new Map();
const response = await fetch('/internalapi/createChat', { let chatBoxes = new Map();
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
const res = await response.json(); /* Generate text that is displayed on the right side of chat intro box */
function youAreXHere(myRoleHere){
return "You are " + myRoleHere + " here";
}
if (res.status === 0) {
addRoomToList(roomName, roomNickname); let chatRenunciationWinStoredId = -1;
rooms[roomNickname] = true;
closeCreateRoomModal(); function shouldShowDeleteButton(myMembershipSt){
currentHistoryId = res.update.LocalHistoryId; return myMembershipSt.myRoleHere === userChatRoleDeleted;
window.location.href = '/chat/' + roomNickname; }
/* Updating chat html box after myMembershipSt in it was updated */
function updateBoxWithNewSt(box, myMembershipSt){
let ID = myMembershipSt.chatId;
let roleP = box.querySelector(".CL-my-chat-box-my-role");
roleP.innerText = youAreXHere(myMembershipSt.myRoleHere);
box.style.backgroundColor = roleToColor(myMembershipSt.myRoleHere);
box.querySelector(".CL-my-chat-box-leave-btn").style.display =
(shouldShowDeleteButton(myMembershipSt) ? "none" : "block");
}
function convertMyMembershipStToBox(myMembershipSt){
let chatURI = "/chat/" + myMembershipSt.chatNickname;
let ID = myMembershipSt.chatId;
let box = document.createElement("div");
box.className = "dynamic-block-list-el CL-my-chat-box";
box.style.backgroundColor = roleToColor(myMembershipSt.myRoleHere);
let inBoxNickname = document.createElement("a");
box.appendChild(inBoxNickname);
inBoxNickname.className = "entity-nickname-txt CL-my-chat-box-nickname";
inBoxNickname.innerText = myMembershipSt.chatNickname;
inBoxNickname.href = chatURI;
let inBoxName = document.createElement("a");
box.appendChild(inBoxName);
inBoxName.className = "entity-reg-field-txt CL-my-chat-box-name";
inBoxName.innerText = myMembershipSt.chatName;
inBoxName.href = chatURI;
let inBoxMyRoleHere = document.createElement("p");
box.appendChild(inBoxMyRoleHere);
inBoxMyRoleHere.className = "entity-reg-field-txt CL-my-chat-box-my-role";
inBoxMyRoleHere.innerText = youAreXHere(myMembershipSt.myRoleHere);
let inBoxLeaveBtn = document.createElement("img");
box.appendChild(inBoxLeaveBtn);
inBoxLeaveBtn.className = "CL-my-chat-box-leave-btn";
inBoxLeaveBtn.src = "/assets/img/delete.svg";
inBoxLeaveBtn.onclick = function (ev) {
if (ev.button !== 0)
return;
chatRenunciationWinStoredId = ID;
document.getElementById("chat-renunciation-win-title").innerText =
"Do you really want to leave chat " + myMembershipSt.chatNickname + "?";
activatePopupWindowById("chat-renunciation-win");
};
box.querySelector(".CL-my-chat-box-leave-btn").style.display =
(shouldShowDeleteButton(myMembershipSt) ? "none" : "block");
return box;
}
function updateLocalStateFromChatListUpdResp(chatListUpdResp){
LocalHistoryId = chatListUpdResp.HistoryId;
let literalChatList = document.getElementById("CL-dblec");
for (let myMembershipSt of chatListUpdResp.myChats){
let chatId = myMembershipSt.chatId;
console.log(myMembershipSt);
if (myChats.has(chatId)){
myChats.set(chatId, myMembershipSt);
updateBoxWithNewSt(chatBoxes.get(chatId), myMembershipSt);
} else { } else {
throw new Error(res.error || 'Ошибка'); if (myMembershipSt.myRoleHere === userChatRoleDeleted)
continue;
myChats.set(chatId, myMembershipSt);
let box = convertMyMembershipStToBox(myMembershipSt)
chatBoxes.set(chatId, box);
literalChatList.appendChild(box);
} }
} catch (error) {
alert('Ошибка создания чата: ' + error.message);
} }
} }
function addRoomToList(roomName) { /* Use it ONLY if `Recv` reported success */
const roomList = document.querySelector('.room-list'); function updateLocalStateFromRecv(Recv){
const existingRoomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName); updateLocalStateFromChatListUpdResp(Recv.chatListUpdResp);
if (existingRoomItem) {
existingRoomItem.remove();
}
const roomItem = document.createElement('li');
roomItem.classList.add('room-item');
roomItem.innerHTML = `
<span class="room-name">${roomName}</span>
<button class="delete-chat-button" onclick="openConfirm('${roomNickname}')">Удалить чат</button>
<button class="add-members-button" onclick="openAdd()">Добавить участников</button>
<button class="join-button" onclick="window.location.href = '/chat/${roomNickname}'">Войти</button>
`;
roomList.appendChild(roomItem);
} }
function removeRoomFromList(roomName, roomNickname) { function configureChatCreationInterface(){
const roomList = document.querySelector('.room-list'); document.getElementById("chat-creation-win-yes").onclick = function (ev) {
const roomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName); if (ev.button !== 0)
if (roomItem) { return;
roomList.removeChild(roomItem); let chatNicknameInput = document.getElementById("chat-nickname-input");
} let chatNameInput = document.getElementById("chat-name-input");
} let nickname = String(chatNicknameInput.value);
let name = String(chatNameInput.value);
async function initializeRoomList() { deactivateActivePopup();
try { let Sent = genSentBase();
const response = await fetch('/internalapi/getChatList', { Sent.content = {};
method: 'POST', Sent.content.nickname = nickname;
headers: { Sent.content.name = name;
'Content-Type': 'application/json' apiRequest("createChat", Sent
}, ).then((Recv) => {
body: JSON.stringify({}) updateLocalStateFromRecv(Recv);
}).catch((e) => {
alert("Failed to create chat");
console.log(e);
}); });
};
const res = await response.json(); document.getElementById("chat-creation-win-no").onclick = function (ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
}
if (res.status === 0) { document.getElementById("CL-bacbe").onclick = function (ev){
res.chats.forEach(chat => { if (ev.button !== 0)
addRoomToList(chat.content.name, chat.content.nickname); return;
}); let chatNicknameInput = document.getElementById("chat-nickname-input");
} else { let chatNameInput = document.getElementById("chat-name-input");
throw new Error(res.error || 'Неизвестная ошибка'); chatNicknameInput.value = "";
} chatNameInput.value = "";
} catch (error) { activatePopupWindowById("chat-creation-win");
alert('Ошибка загрузки списка чатов: ' + error.message); console.log("Tried to show chat creation window");
};
}
function configureChatRenunciationInterfaceWinPart(){
document.getElementById("chat-renunciation-win-yes").onclick = function (ev){
if (ev.button !== 0)
return;
deactivateActivePopup();
if (chatRenunciationWinStoredId < 0)
throw new Error("chatRenunciationWinStoredId < 0");
let chatId = chatRenunciationWinStoredId;
let Sent = genSentBase();
Sent.chatId = chatId;
apiRequest("leaveChat", Sent
).then((Recv) => {
updateLocalStateFromRecv(Recv);
}).catch((e) => {
alert("Failed to leave chat");
console.log(e);
});
}
document.getElementById("chat-renunciation-win-no").onclick = function(ev) {
if (ev.button !== 0)
return;
deactivateActivePopup();
} }
} }
__mainloopDelayMs = 3000;
async function getChatID() { __guestMainloopPollerAction = function(){
const chatNickname = window.location.pathname.split('/').pop(); let Sent = genSentBase();
const response = await fetch('/internalapi/getChatList', { apiRequest("chatListPollEvents", Sent
method: 'POST', ).then((Recv) => {
headers: { console.log("Got a response");
'Content-Type': 'application/json' console.log(Recv);
}, updateLocalStateFromRecv(Recv);
body: JSON.stringify({})
}); });
const res = await response.json();
for (const chat of res.chats) {
if (chat.content.nickname === chatNickname) {
return chat.id;
}
}
return -1;
}
window.onclick = function(event) {
if (event.target === document.getElementById('createRoomModal')) {
closeCreateRoomModal();
}
} }
document.getElementById('newRoomName').addEventListener('keydown', function(event) { window.onload = function () {
if (event.key === 'Enter') { console.log("Loading complete");
createRoom(); updateLocalStateFromChatListUpdResp(initial_chatListUpdResp);
} configureChatCreationInterface();
}); configureChatRenunciationInterfaceWinPart();
document.addEventListener('DOMContentLoaded', initializeRoomList); mainloopPoller();
};

View File

@ -112,6 +112,10 @@ namespace een9 {
return status; return status;
} }
res.body.reserve(std::min(100000ul, body_size)); res.body.reserve(std::min(100000ul, body_size));
if (body_size == 0) {
status = 1;
}
break;
} }
} }
if (!res.has_body) { if (!res.has_body) {

View File

@ -55,13 +55,16 @@ namespace nytl {
} }
char skip(ParsingContext& ctx) { char skip(ParsingContext& ctx) {
ASSERT(ctx.pos < ctx.text.size(), "Unexpected EOF"); if (ctx.pos >= ctx.text.size())
THROW("Unexpected EOF");
return advance(ctx); return advance(ctx);
} }
void skip(ParsingContext& ctx, char ch) { void skip(ParsingContext& ctx, char ch) {
ASSERT(ctx.pos < ctx.text.size(), "Unexpected EOF"); if (ctx.pos >= ctx.text.size())
ASSERT(ctx.text[ctx.pos] == ch, "Unexpected character"); THROW("Unexpected EOF");
if (ctx.text[ctx.pos] != ch)
THROW("Unexpected character");
advance(ctx); advance(ctx);
} }
@ -150,7 +153,8 @@ namespace nytl {
void parse_bare_file(const std::string& filename, const std::string& content, void parse_bare_file(const std::string& filename, const std::string& content,
global_elem_set_t& result) global_elem_set_t& result)
{ {
ASSERT(result.count(filename) == 0, "Repeated element " + filename); if (result.count(filename) != 0)
THROW("Repeated element " + filename);
std::string txt = clement_lstrip(content); std::string txt = clement_lstrip(content);
rstrip(txt); rstrip(txt);
size_t cut = 9999999999999; size_t cut = 9999999999999;
@ -170,13 +174,15 @@ namespace nytl {
uptr<TPFrame> toMe(bool returned, ParsingContext& ctx) { uptr<TPFrame> toMe(bool returned, ParsingContext& ctx) {
if (!returned) { if (!returned) {
std::string nm = readName(ctx); std::string nm = readName(ctx);
ASSERT(!nm.empty(), "Type specification expected"); if (nm.empty())
THROW("Type specification expected");
nm = make_uppercase(nm); nm = make_uppercase(nm);
if (nm == "JSON") { if (nm == "JSON") {
result = json::JSON(true); result = json::JSON(true);
return NULL; return NULL;
} }
ASSERT(nm == "EL", "Type of argument variable is either JSON or EL(...signature)") if (nm != "EL")
THROW("Type of argument variable is either JSON or EL(...signature)");
skip(ctx, '('); skip(ctx, '(');
result.asArray(); result.asArray();
assert(result.isArray()); assert(result.isArray());
@ -217,8 +223,10 @@ namespace nytl {
uptr<EPFrame> toMe(bool returned, ParsingContext& ctx, const arg_name_list_t& local_var_names) { uptr<EPFrame> toMe(bool returned, ParsingContext& ctx, const arg_name_list_t& local_var_names) {
if (!returned) { if (!returned) {
std::string first = readName(ctx); std::string first = readName(ctx);
ASSERT(!first.empty(), "Expression should start with 'root' name of global package or local variable"); if (first.empty())
ASSERT(first != "_", "_ ??? ARE YOU KIDDING???"); THROW("Expression should start with 'root' name of global package or local variable");
if (first == "_")
THROW("Expression root can't be _");
if (local_var_names.count(first) == 1) { if (local_var_names.count(first) == 1) {
result["V"].asInteger() = json::Integer((int64_t)local_var_names.at(first)); result["V"].asInteger() = json::Integer((int64_t)local_var_names.at(first));
} else { } else {
@ -243,7 +251,8 @@ namespace nytl {
t = readUint(ctx); t = readUint(ctx);
if (!t.empty()) { if (!t.empty()) {
size_t v = std::stoul(t); size_t v = std::stoul(t);
ASSERT(v < INT64_MAX, "Index is too big"); if (v >= INT64_MAX)
THROW("Index is too big");
chain.back() = json::JSON((int64_t)v); chain.back() = json::JSON((int64_t)v);
continue; continue;
} }
@ -352,7 +361,8 @@ namespace nytl {
ElementPart::when_for_put_S& P = result.parts.back().when_for_put; ElementPart::when_for_put_S& P = result.parts.back().when_for_put;
skipWhitespace(ctx); skipWhitespace(ctx);
std::string V1 = readName(ctx); std::string V1 = readName(ctx);
ASSERT(!V1.empty(), "Expected variable name"); if (V1.empty())
THROW("Expected variable name");
skipWhitespace(ctx); skipWhitespace(ctx);
bool have_colon_and_2 = false; bool have_colon_and_2 = false;
std::string V2; std::string V2;
@ -364,7 +374,8 @@ namespace nytl {
skipWhitespace(ctx); skipWhitespace(ctx);
} }
op = make_uppercase(readName(ctx)); op = make_uppercase(readName(ctx));
ASSERT(op == "IN", "Expected IN"); if (op != "IN")
THROW("Expected IN");
skipWhitespace(ctx); skipWhitespace(ctx);
P.ref_over = parse_expression(ctx, local_var_names); P.ref_over = parse_expression(ctx, local_var_names);
P.internal_element = el_name + ".~" + std::to_string(free_hidden++); P.internal_element = el_name + ".~" + std::to_string(free_hidden++);
@ -372,13 +383,15 @@ namespace nytl {
newborn.is_hidden = true; newborn.is_hidden = true;
arg_name_list_t local_var_names_of_nxt = local_var_names; arg_name_list_t local_var_names_of_nxt = local_var_names;
if (V1 != "_") { if (V1 != "_") {
ASSERT(local_var_names_of_nxt.count(V1) == 0, "Repeated local variable"); if (local_var_names_of_nxt.count(V1) != 0)
THROW("Repeated local variable");
size_t k = local_var_names_of_nxt.size(); size_t k = local_var_names_of_nxt.size();
local_var_names_of_nxt.emplace(V1, k); local_var_names_of_nxt.emplace(V1, k);
(have_colon_and_2 ? P.where_key_var : P.where_value_var) = (ssize_t)k; (have_colon_and_2 ? P.where_key_var : P.where_value_var) = (ssize_t)k;
} }
if (have_colon_and_2 && V2 != "_") { if (have_colon_and_2 && V2 != "_") {
ASSERT(local_var_names_of_nxt.count(V2) == 0, "Repeated local variable"); if (local_var_names_of_nxt.count(V2) != 0)
THROW("Repeated local variable");
size_t k = local_var_names_of_nxt.size(); size_t k = local_var_names_of_nxt.size();
local_var_names_of_nxt.emplace(V2, k); local_var_names_of_nxt.emplace(V2, k);
P.where_value_var = (ssize_t)k; P.where_value_var = (ssize_t)k;
@ -395,11 +408,12 @@ namespace nytl {
ElementPart::when_ref_put_S& P = result.parts.back().when_ref_put; ElementPart::when_ref_put_S& P = result.parts.back().when_ref_put;
skipWhitespace(ctx); skipWhitespace(ctx);
std::string Vn = readName(ctx); std::string Vn = readName(ctx);
ASSERT(!Vn.empty(), "Expected variable name"); if (Vn.empty() || Vn == "_")
ASSERT(Vn != "_", "Are you kidding???"); THROW("REF: expected variable name");
skipWhitespace(ctx); skipWhitespace(ctx);
op = make_uppercase(readName(ctx)); op = make_uppercase(readName(ctx));
ASSERT(op == "AS", "Expected AS"); if (op != "AS")
THROW("Expected AS");
skipWhitespace(ctx); skipWhitespace(ctx);
P.ref_over = parse_expression(ctx, local_var_names); P.ref_over = parse_expression(ctx, local_var_names);
P.internal_element = el_name + ".~" + std::to_string(free_hidden++); P.internal_element = el_name + ".~" + std::to_string(free_hidden++);
@ -467,13 +481,15 @@ namespace nytl {
} }
}; };
if (op == "ENDELDEF") { if (op == "ENDELDEF") {
ASSERT(myself == gone_for_nothing, "Unexpected end of element"); if (myself != gone_for_nothing)
THROW("Unexpected ENDELDEF");
skip_magic_block_end(ctx, syntax); skip_magic_block_end(ctx, syntax);
prepare_to_depart_parts(); prepare_to_depart_parts();
return NULL; return NULL;
} }
if (op == "ENDFOR") { if (op == "ENDFOR") {
ASSERT(myself == gone_for_for, "Unexpected end of for cycle"); if (myself != gone_for_for)
THROW("Unexpected ENDFOR");
skipWhitespace(ctx); skipWhitespace(ctx);
/* Here I am using ret_data_int to return info about NOLF(1)/LF(2) decision */ /* Here I am using ret_data_int to return info about NOLF(1)/LF(2) decision */
ret_data_int = 2; // Default is to do LF ret_data_int = 2; // Default is to do LF
@ -491,7 +507,8 @@ namespace nytl {
return NULL; return NULL;
} }
if (op == "ENDREF") { if (op == "ENDREF") {
assert(myself == gone_for_ref); if (myself != gone_for_ref)
THROW("Unexpected ENDREF");
skip_magic_block_end(ctx, syntax); skip_magic_block_end(ctx, syntax);
prepare_to_depart_parts(); prepare_to_depart_parts();
return NULL; return NULL;
@ -525,12 +542,15 @@ namespace nytl {
if (peep(ctx) == EOFVAL) if (peep(ctx) == EOFVAL)
break; break;
skip_magic_block_start(ctx, syntax); skip_magic_block_start(ctx, syntax);
ASSERT(make_uppercase(readName(ctx)) == "ELDEF", "Expected ELDEF"); if (make_uppercase(readName(ctx)) != "ELDEF")
THROW("Expected ELDEF");
skipWhitespace(ctx); skipWhitespace(ctx);
std::string elname_postfix = readName(ctx); std::string elname_postfix = readName(ctx);
ASSERT(elname_postfix != "_", "please don't"); if (elname_postfix == "_")
THROW("Can't use _ as element name");
std::string fullname = elname_postfix == "main" ? filename : filename + "." + elname_postfix; std::string fullname = elname_postfix == "main" ? filename : filename + "." + elname_postfix;
ASSERT(result.count(fullname) == 0, "Element " + fullname + " has been already defined"); if (result.count(fullname) != 0)
THROW("Element " + fullname + " has been already defined");
Element& newborn = result[fullname]; Element& newborn = result[fullname];
arg_name_list_t arglist; arg_name_list_t arglist;
while (true) { while (true) {
@ -540,9 +560,11 @@ namespace nytl {
newborn.arguments.push_back(parse_type(ctx)); newborn.arguments.push_back(parse_type(ctx));
skipWhitespace(ctx); skipWhitespace(ctx);
std::string argname = readName(ctx); std::string argname = readName(ctx);
ASSERT(!argname.empty(), "Expected argument name"); if (argname.empty())
THROW("Expected argument name");
if (argname != "_") { if (argname != "_") {
ASSERT(arglist.count(argname) == 0, "Repeated argument (" + argname + ")"); if (arglist.count(argname) != 0)
THROW("Repeated argument (" + argname + ")");
size_t k = arglist.size(); size_t k = arglist.size();
arglist[argname] = k; arglist[argname] = k;
} }

View File

@ -212,7 +212,7 @@ namespace nytl {
const std::function<std::string(std::string)> &escape) { const std::function<std::string(std::string)> &escape) {
if (!returned) if (!returned)
if (elem_ns.count(name) != 1) if (elem_ns.count(name) != 1)
THROW("No such element"); THROW("No such element (" + name + ")");
const Element& el = elem_ns.at(name); const Element& el = elem_ns.at(name);
if (!returned) { if (!returned) {
/* Continue to do checks */ /* Continue to do checks */
@ -227,7 +227,9 @@ namespace nytl {
// If not json is expected, element must be expected // If not json is expected, element must be expected
assert(el.arguments[i].isArray()); assert(el.arguments[i].isArray());
ASSERT(!passed_args[i].is_json, "Expected element element arguemnt, got json"); ASSERT(!passed_args[i].is_json, "Expected element element arguemnt, got json");
ASSERT(elem_ns.count(passed_args[i].EL_name), "No such element, can't compare signatures of argument value"); const std::string& passed_el_as_arg = passed_args[i].EL_name;
if (elem_ns.count(passed_el_as_arg) != 1)
THROW("No such element, can't compare signatures of argument value (" + passed_el_as_arg + ")");
const Element& arg_element = elem_ns.at(passed_args[i].EL_name); const Element& arg_element = elem_ns.at(passed_args[i].EL_name);
// ASSERT(passed_args); // ASSERT(passed_args);
if(el.arguments[i].asArray() != arg_element.arguments) if(el.arguments[i].asArray() != arg_element.arguments)

View File

@ -3,16 +3,32 @@
#include <assert.h> #include <assert.h>
namespace iu9cawebchat { namespace iu9cawebchat {
void make_her_a_member_of_the_midnight_crew(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role) { bool is_membership_row_present(SqliteConnection& conn, int64_t chatId, int64_t alienUserId) {
assert(role != user_chat_role_deleted); SqliteStatement req(conn,
"SELECT EXISTS(SELECT 1 FROM `user_chat_membership` WHERE `chatId` = ?1 AND `userId` = ?2)",
{{1, chatId}, {2, alienUserId}}, {});
fsql_integer_or_null r{true, 0};
int status = sqlite_stmt_step(req, {{0, &r}}, {});
return (bool)r.value;
}
void alter_user_chat_role(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role) {
int64_t chat_HistoryId_BEFORE_EV = get_current_history_id_of_chat(conn, chatId); int64_t chat_HistoryId_BEFORE_EV = get_current_history_id_of_chat(conn, chatId);
int64_t alien_chatlist_HistoryId_BEFORE_EV = get_current_history_id_of_user_chatList(conn, alienUserId); int64_t alien_chatlist_HistoryId_BEFORE_EV = get_current_history_id_of_user_chatList(conn, alienUserId);
sqlite_nooutput(conn, if (!is_membership_row_present(conn, chatId, alienUserId)) {
"INSERT INTO `user_chat_membership` (`userId`, `chatId`, `user_chatList_IncHistoryId`," sqlite_nooutput(conn,
"`chat_IncHistoryId`, `role`) VALUES (?1, ?2, ?3, ?4, ?5)", "INSERT INTO `user_chat_membership` (`userId`, `chatId`, `user_chatList_IncHistoryId`,"
{{1, alienUserId}, {2, chatId}, {3, alien_chatlist_HistoryId_BEFORE_EV + 1}, "`chat_IncHistoryId`, `role`) VALUES (?1, ?2, ?3, ?4, ?5)",
{4, chat_HistoryId_BEFORE_EV + 1}, {5, role}}, {}); {{1, alienUserId}, {2, chatId}, {3, alien_chatlist_HistoryId_BEFORE_EV + 1},
{4, chat_HistoryId_BEFORE_EV + 1}, {5, role}}, {});
} else {
sqlite_nooutput(conn,
"UPDATE `user_chat_membership` SET `user_chatList_IncHistoryId` = ?3,`chat_IncHistoryId` = ?4,"
"`role` = ?5 WHERE `userId` = ?1 AND `chatId` = ?2",
{{1, alienUserId}, {2, chatId}, {3, alien_chatlist_HistoryId_BEFORE_EV + 1},
{4, chat_HistoryId_BEFORE_EV + 1}, {5, role}}, {});
}
sqlite_nooutput(conn, sqlite_nooutput(conn,
"UPDATE `chat` SET `it_HistoryId` = ?1 WHERE `id` = ?2", {{1, chat_HistoryId_BEFORE_EV + 1}, "UPDATE `chat` SET `it_HistoryId` = ?1 WHERE `id` = ?2", {{1, chat_HistoryId_BEFORE_EV + 1},
{2, chatId}}, {}); {2, chatId}}, {});
@ -22,6 +38,12 @@ namespace iu9cawebchat {
{{1, alien_chatlist_HistoryId_BEFORE_EV + 1}, {2, alienUserId}}, {}); {{1, alien_chatlist_HistoryId_BEFORE_EV + 1}, {2, alienUserId}}, {});
} }
void make_her_a_member_of_the_midnight_crew(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role) {
assert(role != user_chat_role_deleted);
// int64_t old_role = get_role_of_user_in_chat(conn, alienUserId, chatId);
alter_user_chat_role(conn, chatId, alienUserId, role);
}
json::JSON internalapi_addMemberToChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { json::JSON internalapi_addMemberToChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int(); 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);
@ -33,12 +55,17 @@ namespace iu9cawebchat {
try { try {
alien = lookup_user_content_by_nickname(conn, alien_nickname); alien = lookup_user_content_by_nickname(conn, alien_nickname);
} catch (std::exception& e) { } catch (std::exception& e) {
return json::JSON(json::jdict{{"status", json::JSON(-1l)}}); return at_api_error_gen_bad_recv(-1l);
} }
bool makeReadOnly = Sent["makeReadOnly"].toBool();
int64_t aliens_old_role = get_role_of_user_in_chat(conn, alien.id, chatId); int64_t aliens_old_role = get_role_of_user_in_chat(conn, alien.id, chatId);
if (aliens_old_role == user_chat_role_deleted) { if (aliens_old_role == user_chat_role_deleted) {
make_her_a_member_of_the_midnight_crew(conn, chatId, alien.id, user_chat_role_regular); make_her_a_member_of_the_midnight_crew(conn, chatId, alien.id,
makeReadOnly ? user_chat_role_read_only : user_chat_role_regular);
} else {
return at_api_error_gen_bad_recv(-2l);
} }
json::JSON Recv; json::JSON Recv;

View File

@ -7,13 +7,16 @@ namespace iu9cawebchat {
std::string new_chat_name = Sent["content"]["name"].asString(); std::string new_chat_name = Sent["content"]["name"].asString();
std::string new_chat_nickname = Sent["content"]["nickname"].asString(); std::string new_chat_nickname = Sent["content"]["nickname"].asString();
if (!check_nickname(new_chat_nickname) || !check_name(new_chat_name)) if (!check_nickname(new_chat_nickname) || !check_name(new_chat_name))
return json::JSON(json::jdict{{"status", json::JSON(-1l)}}); return at_api_error_gen_bad_recv(-1l);
if (is_nickname_taken(conn, new_chat_nickname)) if (is_nickname_taken(conn, new_chat_nickname))
return json::JSON(json::jdict{{"status", json::JSON(-2l)}}); return at_api_error_gen_bad_recv(-2l);
if (is_nickname_taken(conn, new_chat_nickname))
return at_api_error_gen_bad_recv(-3l);
reserve_nickname(conn, new_chat_nickname); reserve_nickname(conn, new_chat_nickname);
sqlite_nooutput(conn, sqlite_nooutput(conn,
"INSERT INTO `chat` (`nickname`, `name`, `it_HistoryId`, `lastMsgId`) VALUES (?1, ?2, 0, -1)"); "INSERT INTO `chat` (`nickname`, `name`, `it_HistoryId`, `lastMsgId`) VALUES (?1, ?2, 0, -1)",
{}, {{1, new_chat_nickname}, {2, new_chat_name}});
int64_t CHAT_ID = sqlite_trsess_last_insert_rowid(conn); int64_t CHAT_ID = sqlite_trsess_last_insert_rowid(conn);

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

@ -6,7 +6,7 @@ namespace iu9cawebchat {
int64_t chatId = Sent["chatId"].asInteger().get_int(); int64_t chatId = Sent["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("Not a member"); een9_THROW("Not a member");
kick_from_chat(conn, uid, chatId); kick_from_chat(conn, chatId, uid);
json::JSON Recv; json::JSON Recv;
poll_update_chat_list(conn, uid, Sent, Recv); poll_update_chat_list(conn, uid, Sent, Recv);
return Recv; return Recv;

View File

@ -2,34 +2,17 @@
#include <engine_engine_number_9/baza_throw.h> #include <engine_engine_number_9/baza_throw.h>
namespace iu9cawebchat { namespace iu9cawebchat {
void kick_from_chat(SqliteConnection& conn, int64_t alienUserId, int64_t chatId) { void kick_from_chat(SqliteConnection& conn, int64_t chatId, int64_t alienUserId) {
if (get_role_of_user_in_chat(conn, alienUserId, chatId) == user_chat_role_deleted) alter_user_chat_role(conn, chatId, alienUserId, user_chat_role_deleted);
een9_THROW("Can't delete a deleted member");
int64_t chat_HistoryId_BEFORE_EV = get_current_history_id_of_chat(conn, chatId);
int64_t alien_chatlist_HistoryId_BEFORE_EV = get_current_history_id_of_user_chatList(conn, alienUserId);
sqlite_nooutput(conn,
"UPDATE `user_chat_membership` SET `user_chatList_IncHistoryId` = ?3,"
"`chat_IncHistoryId` = ?4, `role` = ?5 WHERE `userId` = ?1 AND `chatId` = ?2",
{{1, alienUserId}, {2, chatId}, {3, alien_chatlist_HistoryId_BEFORE_EV + 1},
{4, chat_HistoryId_BEFORE_EV + 1}, {5, user_chat_role_deleted}}, {});
sqlite_nooutput(conn,
"UPDATE `chat` SET `it_HistoryId` = ?1 WHERE `id` = ?2", {{1, chat_HistoryId_BEFORE_EV + 1},
{2, chatId}}, {});
sqlite_nooutput(conn,
"UPDATE `user` SET `chatList_HistoryId` = ?1 WHERE `id` = ?2",
{{1, alien_chatlist_HistoryId_BEFORE_EV + 1}, {2, alienUserId}}, {});
} }
json::JSON internalapi_removeMemberFromChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) { json::JSON internalapi_removeMemberFromChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int(); 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_admin)
een9_THROW("Only admin can delete members of chat"); een9_THROW("Only admin can delete members of chat");
int64_t badAlienId = Sent["chatUpdReq"]["userId"].asInteger().get_int(); int64_t badAlienId = Sent["userId"].asInteger().get_int();
kick_from_chat(conn, badAlienId, chatId); kick_from_chat(conn, chatId, badAlienId);
json::JSON Recv; json::JSON Recv;
poll_update_chat(conn, Sent, Recv); poll_update_chat(conn, Sent, Recv);
return Recv; return Recv;

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,14 +1,16 @@
#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) {
printf("Userid: %ld\n", userId);
json::JSON chatListUpdResp; json::JSON chatListUpdResp;
SqliteStatement my_membership_changes(conn, SqliteStatement my_membership_changes(conn,
"SELECT `chatId`, `role` FROM `user_chat_membership` WHERE `userId` = ?1 " "SELECT `chatId`, `role` FROM `user_chat_membership` WHERE `userId` = ?1 "
"AND `user_chatList_IncHistoryId` > ?2", {{1, userId}, {2, LocalHistoryId}}); "AND `user_chatList_IncHistoryId` > ?2", {{1, userId}, {2, LocalHistoryId}});
json::jarr myChats = chatListUpdResp["myChats"].asArray(); json::jarr& myChats = chatListUpdResp["myChats"].asArray();
while (true) { while (true) {
fsql_integer_or_null ev_chatId, usersRoleHere; fsql_integer_or_null ev_chatId, usersRoleHere;
int status = sqlite_stmt_step(my_membership_changes, {{0, &ev_chatId}, {1, &usersRoleHere}}, {}); int status = sqlite_stmt_step(my_membership_changes, {{0, &ev_chatId}, {1, &usersRoleHere}}, {});
@ -32,7 +34,7 @@ namespace iu9cawebchat {
void poll_update_chat_list(SqliteConnection& conn, int64_t userId, const json::JSON& Sent, json::JSON& Recv) { void poll_update_chat_list(SqliteConnection& conn, int64_t userId, const json::JSON& Sent, json::JSON& Recv) {
Recv["status"].asInteger() = json::Integer(0l); Recv["status"].asInteger() = json::Integer(0l);
// todo: in libjsonincpp: get rid of Integer // todo: in libjsonincpp: get rid of Integer
poll_update_chat_list_resp(conn, userId, Sent["chatListUpdReq"]["LocalHistoryId"].asInteger().get_int()); Recv["chatListUpdResp"] = poll_update_chat_list_resp(conn, userId, Sent["chatListUpdReq"]["LocalHistoryId"].asInteger().get_int());
} }
json::JSON make_messageSt_obj(int64_t id, int64_t senderUserId, bool exists, bool isSystem, const std::string& text) { json::JSON make_messageSt_obj(int64_t id, int64_t senderUserId, bool exists, bool isSystem, const std::string& text) {
@ -52,7 +54,7 @@ namespace iu9cawebchat {
json::jarr messages; json::jarr messages;
SqliteStatement messages_changes(conn, SqliteStatement messages_changes(conn,
"SELECT `id`, `senderUserId`, `exists`, `isSystem`, `text` FROM `messages` " "SELECT `id`, `senderUserId`, `exists`, `isSystem`, `text` FROM `message` "
"WHERE `chatId` = ?1 AND ( `chat_IncHistoryId` > ?2 OR ( ?3 <= `id` AND `id` <= ?4 ) )", "WHERE `chatId` = ?1 AND ( `chat_IncHistoryId` > ?2 OR ( ?3 <= `id` AND `id` <= ?4 ) )",
{{1, chatId}, {2, LocalHistoryId}, {3, QSEG_A}, {4, QSEG_B}}, {}); {{1, chatId}, {2, LocalHistoryId}, {3, QSEG_A}, {4, QSEG_B}}, {});
while (true) { while (true) {
@ -73,7 +75,7 @@ namespace iu9cawebchat {
json::jarr members; json::jarr members;
SqliteStatement membership_changes(conn, SqliteStatement membership_changes(conn,
"SELECT `userId`, `role`, FROM `user_chat_membership` WHERE `chatId` = ?1 " "SELECT `userId`, `role` FROM `user_chat_membership` WHERE `chatId` = ?1 "
"AND `chat_IncHistoryId` > ?2", {{1, chatId}, {2, LocalHistoryId}}, {}); "AND `chat_IncHistoryId` > ?2", {{1, chatId}, {2, LocalHistoryId}}, {});
while (true) { while (true) {
fsql_integer_or_null alienUserId; fsql_integer_or_null alienUserId;
@ -101,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));
@ -137,7 +139,7 @@ namespace iu9cawebchat {
int64_t QSEG_A, int64_t QSEG_B) { int64_t QSEG_A, int64_t QSEG_B) {
Recv["status"].asInteger() = json::Integer(0l); Recv["status"].asInteger() = json::Integer(0l);
Recv["charUpdResp"] = poll_update_chat_important_segment_resp(conn, Recv["chatUpdResp"] = poll_update_chat_important_segment_resp(conn,
Sent["chatUpdReq"]["chatId"].asInteger().get_int(), Sent["chatUpdReq"]["chatId"].asInteger().get_int(),
Sent["chatUpdReq"]["LocalHistoryId"].asInteger().get_int(), QSEG_A, QSEG_B); Sent["chatUpdReq"]["LocalHistoryId"].asInteger().get_int(), QSEG_A, QSEG_B);
} }
@ -164,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);
@ -191,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

@ -5,6 +5,10 @@
#include "../str_fields.h" #include "../str_fields.h"
namespace iu9cawebchat { namespace iu9cawebchat {
json::JSON at_api_error_gen_bad_recv(int64_t code) {
return json::JSON(json::jdict{{"status", json::JSON(code)}});
}
const char* stringify_user_chat_role(int64_t role) { const char* stringify_user_chat_role(int64_t role) {
if (role == user_chat_role_admin) if (role == user_chat_role_admin)
return "admin"; return "admin";
@ -109,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};
} }
@ -144,7 +146,6 @@ namespace iu9cawebchat {
/* All the api calls processing is done in dedicated files. /* All the api calls processing is done in dedicated files.
* All functions related to polling are defined in api_pollevents.cpp */ * All functions related to polling are defined in api_pollevents.cpp */
// todo: move everything to dedicated files
int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId) { int64_t get_current_history_id_of_chat(SqliteConnection& conn, int64_t chatId) {
SqliteStatement req(conn, "SELECT `it_HistoryId` FROM `chat` WHERE `id` = ?1", {{1, chatId}}, {}); SqliteStatement req(conn, "SELECT `it_HistoryId` FROM `chat` WHERE `id` = ?1", {{1, chatId}}, {});
fsql_integer_or_null HistoryId; fsql_integer_or_null HistoryId;

View File

@ -8,6 +8,8 @@
#include <jsonincpp/string_representation.h> #include <jsonincpp/string_representation.h>
namespace iu9cawebchat { namespace iu9cawebchat {
json::JSON at_api_error_gen_bad_recv(int64_t code = -1);
constexpr int64_t user_chat_role_admin = 1; constexpr int64_t user_chat_role_admin = 1;
constexpr int64_t user_chat_role_regular = 2; constexpr int64_t user_chat_role_regular = 2;
constexpr int64_t user_chat_role_read_only = 3; constexpr int64_t user_chat_role_read_only = 3;
@ -61,8 +63,9 @@ namespace iu9cawebchat {
json::JSON poll_update_chat_ONE_MSG_resp(SqliteConnection& conn, int64_t chatId, int64_t selectedMsg); json::JSON poll_update_chat_ONE_MSG_resp(SqliteConnection& conn, int64_t chatId, int64_t selectedMsg);
void poll_update_chat(SqliteConnection& conn, const json::JSON& Sent, json::JSON& Recv); void poll_update_chat(SqliteConnection& conn, const json::JSON& Sent, json::JSON& Recv);
void alter_user_chat_role(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role);
void make_her_a_member_of_the_midnight_crew(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role); void make_her_a_member_of_the_midnight_crew(SqliteConnection& conn, int64_t chatId, int64_t alienUserId, int64_t role);
void kick_from_chat(SqliteConnection& conn, int64_t alienUserId, int64_t chatId); void kick_from_chat(SqliteConnection& conn, int64_t chatId, int64_t alienUserId);
bool is_nickname_taken(SqliteConnection& conn, const std::string& nickname); bool is_nickname_taken(SqliteConnection& conn, const std::string& nickname);
void reserve_nickname(SqliteConnection& conn, const std::string& nickname); void reserve_nickname(SqliteConnection& conn, const std::string& nickname);

View File

@ -7,8 +7,10 @@ namespace iu9cawebchat {
std::string when_page_chat(WorkerGuestData& wgd, const json::JSON& config_presentation, std::string when_page_chat(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const json::JSON& userinfo) { const een9::ClientRequest& req, const json::JSON& userinfo) {
std::vector<std::string> path_segs = {""}; std::vector<std::string> path_segs = {};
path_segs.reserve(4); path_segs.reserve(4);
if (req.uri_path.empty() || req.uri_path[0] != '/')
return page_E404(wgd);
for (char ch: req.uri_path) { for (char ch: req.uri_path) {
if (ch == '/') if (ch == '/')
path_segs.emplace_back(); path_segs.emplace_back();
@ -23,16 +25,17 @@ 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())
return een9::form_http_server_response_303("/");
RowChat_Content chatInfo; RowChat_Content chatInfo;
try { try {
@ -40,7 +43,7 @@ namespace iu9cawebchat {
} catch (const std::exception& e) { } catch (const std::exception& e) {
return page_E404(wgd); return page_E404(wgd);
} }
if (get_role_of_user_in_chat(*wgd.db, userinfo["id"].asInteger().get_int(), chatInfo.id) == user_chat_role_deleted) { if (get_role_of_user_in_chat(*wgd.db, userinfo["uid"].asInteger().get_int(), chatInfo.id) == user_chat_role_deleted) {
return page_E404(wgd); return page_E404(wgd);
} }
@ -51,8 +54,8 @@ 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", 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

@ -8,6 +8,7 @@ namespace iu9cawebchat {
} }
json::JSON initial_chatListUpdResp = poll_update_chat_list_resp(*wgd.db, json::JSON initial_chatListUpdResp = poll_update_chat_list_resp(*wgd.db,
userinfo["uid"].asInteger().get_int(), 0); userinfo["uid"].asInteger().get_int(), 0);
printf("%s\n", json::generate_str(initial_chatListUpdResp, json::print_pretty).c_str());
return http_R200("list-rooms", wgd, {&config_presentation, &userinfo, &initial_chatListUpdResp}); return http_R200("list-rooms", wgd, {&config_presentation, &userinfo, &initial_chatListUpdResp});
} }
} }

View File

@ -18,8 +18,10 @@ namespace iu9cawebchat {
if (cmp.first == "password") if (cmp.first == "password")
password = cmp.second; password = cmp.second;
} }
een9_ASSERT(check_nickname(nickname), "/login/accpet-data rejected impossible nickname"); if (!check_nickname(nickname))
een9_ASSERT(check_password(password), "/login/accpet-data rejected impossible password"); een9_THROW("/login/accpet-data rejected impossible nickname");
if (!check_password(password))
een9_THROW("/login/accpet-data rejected impossible password");
uid = find_user_by_credentials(*wgd.db, nickname, password); uid = find_user_by_credentials(*wgd.db, nickname, password);
} catch(const std::exception& e){} } catch(const std::exception& e){}

View File

@ -39,6 +39,8 @@ namespace iu9cawebchat {
std::string when_page_user(WorkerGuestData& wgd, const json::JSON& config_presentation, std::string when_page_user(WorkerGuestData& wgd, const json::JSON& config_presentation,
const een9::ClientRequest& req, const std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo) { const een9::ClientRequest& req, const std::vector<LoginCookie>& login_cookies, const json::JSON& userinfo) {
if (userinfo.isNull())
return een9::form_http_server_response_303("/");
SqliteConnection& conn = *wgd.db; SqliteConnection& conn = *wgd.db;
if (!is_page_of_certain_user(req.uri_path)) if (!is_page_of_certain_user(req.uri_path))
return page_E404(wgd); return page_E404(wgd);
@ -104,7 +106,7 @@ namespace iu9cawebchat {
json::JSON msg_list = jsonify_html_message_list({{"", json::JSON msg_list = jsonify_html_message_list({{"",
config_presentation["phr"]["decl"]["incorrect-profile-data"].asString()}}); config_presentation["phr"]["decl"]["incorrect-profile-data"].asString()}});
json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien); json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien);
return http_R200("edit-profile", wgd, {&config_presentation, &alien_userprofile, &msg_list}); return http_R200("edit-profile", wgd, {&config_presentation, &userinfo, &alien_userprofile, &msg_list});
} }
return een9::form_http_server_response_303_spec_head("/user/" + alien_nickname, response_hlines); return een9::form_http_server_response_303_spec_head("/user/" + alien_nickname, response_hlines);
} }
@ -112,9 +114,9 @@ namespace iu9cawebchat {
json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien); json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien);
if (can_edit) { if (can_edit) {
json::JSON empty_msg_list = jsonify_html_message_list({}); json::JSON empty_msg_list = jsonify_html_message_list({});
return http_R200("edit-profile", wgd, {&config_presentation, &alien_userprofile, &empty_msg_list}); return http_R200("edit-profile", wgd, {&config_presentation, &userinfo, &alien_userprofile, &empty_msg_list});
} }
return http_R200("view-profile", wgd, {&config_presentation, &alien_userprofile}); return http_R200("view-profile", wgd, {&config_presentation, &userinfo, &alien_userprofile});
} }
een9_THROW("Bad method"); een9_THROW("Bad method");
} }

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) {

View File

@ -41,6 +41,7 @@ namespace iu9cawebchat {
samI.update({ samI.update({
een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} }, een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} },
een9::StaticAssetManagerRule{assets_dir + "/js", "/assets/js", {{".js", "text/javascript"}} }, een9::StaticAssetManagerRule{assets_dir + "/js", "/assets/js", {{".js", "text/javascript"}} },
een9::StaticAssetManagerRule{assets_dir + "/gif", "/assets/gif", {{".gif", "image/gif"}} },
een9::StaticAssetManagerRule{assets_dir + "/img", "/assets/img", { een9::StaticAssetManagerRule{assets_dir + "/img", "/assets/img", {
{".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"} {".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"}
} }, } },