Compare commits
	
		
			11 Commits
		
	
	
		
			632d4069ac
			...
			2219653f40
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2219653f40 | |||
| b9626aa860 | |||
| fc721d7f5c | |||
| 3a8bdb99ec | |||
| 9283590122 | |||
| 21a23be96e | |||
| 07711a93b0 | |||
| c009b848b5 | |||
| cc8aa516bb | |||
| 711e495f15 | |||
| 5062e1ab70 | 
@ -38,11 +38,11 @@ regexis024_build_system.sh
 | 
			
		||||
 | 
			
		||||
Помимо самого бинарника нужен файл с настройками сервиса. Формат настроек: JSON.
 | 
			
		||||
Комментарии не поддерживаются. Пример такого файла находится в example/config.json.
 | 
			
		||||
Вместе с бинарным фалом так же распространяются ассеты, необъходимые для работы сайта.
 | 
			
		||||
Вместе с бинарным фалом так же распространяются ассеты, необходимые для работы сайта.
 | 
			
		||||
Их можно найти в папке assets. В настроках (поле `["assets"]`) указывается путь до
 | 
			
		||||
папки с ассетами. Путь может быть как абсолютным, так и относительным к рабочей директории.
 | 
			
		||||
Поле настроек `["database"]` указывает как соединиться с базой данных.
 | 
			
		||||
Поддерживается только база данных sqlite. Поддерживается только хранение в файле.
 | 
			
		||||
Поддерживается только база данных sqlite3. Поддерживается только хранение в файле.
 | 
			
		||||
Поле `["database"]["file"]` указывает путь где хранится sqlite база данных.
 | 
			
		||||
 | 
			
		||||
Перед тем как использовать сервис нужно его проинициализировать (а точнее проинициализировать
 | 
			
		||||
 | 
			
		||||
@ -4,26 +4,56 @@
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>Веб-Чат Members</title>
 | 
			
		||||
    <link rel="stylesheet" href="/assets/css/chat.css">
 | 
			
		||||
    <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/chat-members.css">
 | 
			
		||||
    <title>Chat Members</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
{% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
 | 
			
		||||
<!--TODO: ADD SOMETHING WRITE SOMETHING AAAAAA -->
 | 
			
		||||
<div class="overlay" id="overlay">
 | 
			
		||||
    <div class="members-list" id="members-list">
 | 
			
		||||
        <div class="members-list-header">
 | 
			
		||||
            <span class="close" onclick="closeMembersList()">×</span>
 | 
			
		||||
            <h2 class="all-members">Все участники</h2>
 | 
			
		||||
    {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
 | 
			
		||||
 | 
			
		||||
    <div id="user-summoning-win" class="popup-window">
 | 
			
		||||
        <h1 class="popup-window-msg">Nickname for summoned user</h1>
 | 
			
		||||
        <input class="one-line-input" id="summoned-user-nickname">
 | 
			
		||||
        <input type="checkbox" id="summoned-user-is-read-only">
 | 
			
		||||
        <label>Make read only</label><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 class="members-list-body">
 | 
			
		||||
            <ul id="members-list-body">
 | 
			
		||||
                <!-- Список участников будет добавлен динамически -->
 | 
			
		||||
            </ul>
 | 
			
		||||
 | 
			
		||||
        <div class="dynamic-block-list">
 | 
			
		||||
            <img id="CM-btn-add" class="button-add centered-block-el" alt="New chat" src="/assets/img/add.svg">
 | 
			
		||||
            <div class="dynamic-block-list-el-container" id="CM-list">
 | 
			
		||||
 | 
			
		||||
            </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>
 | 
			
		||||
</html>
 | 
			
		||||
{% ENDELDEF %}
 | 
			
		||||
 | 
			
		||||
@ -9,30 +9,60 @@
 | 
			
		||||
 | 
			
		||||
{% ELDEF main JSON pres JSON userinfo JSON openedchat JSON initial_chatUpdResp %}
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="ru">
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <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">
 | 
			
		||||
    <title>Chat</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
{% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
 | 
			
		||||
<!-- TODO AAAAA-->
 | 
			
		||||
<div class="chat-container">
 | 
			
		||||
    <div class="chat-header">
 | 
			
		||||
        <span class="room-name">Веб чат</span>
 | 
			
		||||
        <button class="members" onclick="openMembersList()">Показать участников</button>
 | 
			
		||||
    {% PUT chat.pass pres userinfo openedchat initial_chatUpdResp %}
 | 
			
		||||
 | 
			
		||||
    <div id="msg-deletion-win" class="popup-window">
 | 
			
		||||
        <h1 class="popup-window-msg">Are you sure you want to delete this message?</h1>
 | 
			
		||||
        <!-- message preview will be actually rewritten before each window activation-->
 | 
			
		||||
        <p class="message-in-popup-preview" id="win-deletion-msg-preview">Lorem ipsum dolor</p>
 | 
			
		||||
        <button class="popup-window-btn-yes" id="msg-deletion-yes">Yes, delete</button>
 | 
			
		||||
        <button class="popup-window-btn-no" id="msg-deletion-no">No, cancel</button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div 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 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>
 | 
			
		||||
</html>
 | 
			
		||||
{% ENDELDEF %}
 | 
			
		||||
 | 
			
		||||
@ -1,42 +1,69 @@
 | 
			
		||||
{% ELDEF main JSON pres JSON userprofile JSON errors %}
 | 
			
		||||
{% ELDEF main JSON pres JSON userinfo JSON alienprofile JSON errors %}
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <link rel="stylesheet" href="/assets/css/profile.css">
 | 
			
		||||
    <title>Профиль</title>
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <link rel="icon" type="image/png" href="/assets/img/favicon.png">
 | 
			
		||||
    <link rel="stylesheet" href="/assets/css/common.css">
 | 
			
		||||
    <link rel="stylesheet" href="/assets/css/edit-profile.css">
 | 
			
		||||
    <title>Edit user Profile</title>
 | 
			
		||||
</head>
 | 
			
		||||
<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 %}
 | 
			
		||||
            <div>
 | 
			
		||||
                <p>{% WRITE error.text %}</p>
 | 
			
		||||
            <div class="server-notif-error-msg-box">
 | 
			
		||||
                {% WRITE error.text %}
 | 
			
		||||
            </div>
 | 
			
		||||
        {% 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>
 | 
			
		||||
        <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>
 | 
			
		||||
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
{% ENDELDEF%}
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <link rel="icon" type="image/png" href="/assets/img/favicon.png">
 | 
			
		||||
    <title>Not found</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
 | 
			
		||||
@ -4,69 +4,71 @@
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <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">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<script>
 | 
			
		||||
    let pres = {% PUT jsinsert pres %};
 | 
			
		||||
    let userinfo = {% PUT jsinsert userinfo %};
 | 
			
		||||
    let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %};
 | 
			
		||||
</script>
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <h1 style="color: white;">Выберите Чат-Комнату</h1>
 | 
			
		||||
    <ul class="room-list">
 | 
			
		||||
        <!-- Здесь будет список комнат -->
 | 
			
		||||
    </ul>
 | 
			
		||||
    <button class="create-room-button" onclick="openCreateRoomModal()">Создать Комнату</button>
 | 
			
		||||
</div>
 | 
			
		||||
    <script>
 | 
			
		||||
        let pres = {% PUT jsinsert pres %};
 | 
			
		||||
        let userinfo = {% PUT jsinsert userinfo %};
 | 
			
		||||
        let initial_chatListUpdResp = {% PUT jsinsert initial_chatListUpdResp %};
 | 
			
		||||
    </script>
 | 
			
		||||
    <div id="chat-creation-win" class="popup-window">
 | 
			
		||||
        <h1 class="popup-window-msg">Input identifying information for your new chat</h1>
 | 
			
		||||
        <table class="id-str-input-table">
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td class="id-str-input-td1">
 | 
			
		||||
                    <label for="chat-nickname-input">Enter nickname for new chat:</label>
 | 
			
		||||
                </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="createRoomModal" class="modal">-->
 | 
			
		||||
<!--    <div class="modal-content">-->
 | 
			
		||||
<!--        <div class="modal-header">-->
 | 
			
		||||
<!--            <span class="close" onclick="closeCreateRoomModal()">×</span>-->
 | 
			
		||||
<!--            <h2>Создать Комнату</h2>-->
 | 
			
		||||
<!--        </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>-->
 | 
			
		||||
    <div id="chat-renunciation-win" class="popup-window">
 | 
			
		||||
        <!-- header will actually be rewritten before showing the window to include chat nickname -->
 | 
			
		||||
        <h1 id="chat-renunciation-win-title" class="popup-window-msg">Are you sure you want to leave chat?</h1>
 | 
			
		||||
        <button class="popup-window-btn-yes" id="chat-renunciation-win-yes">Yes, leave</button>
 | 
			
		||||
        <button class="popup-window-btn-no" id="chat-renunciation-win-no">No, cancel</button>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
<!--<!– Модальное окно для добавления участников –>-->
 | 
			
		||||
<!--<div class="overlay" id="add_members">-->
 | 
			
		||||
<!--    <div class="add-members">-->
 | 
			
		||||
<!--        <div class="add-members-header">-->
 | 
			
		||||
<!--            <span class="close" onclick="closeAdd()">×</span>-->
 | 
			
		||||
<!--            <h2>Добавить участников</h2>-->
 | 
			
		||||
<!--        </div>-->
 | 
			
		||||
<!--        <div class="add-members-body">-->
 | 
			
		||||
<!--            <input type="text" id="newMemberLogin" placeholder="Логин пользователя">-->
 | 
			
		||||
<!--        </div>-->
 | 
			
		||||
<!--        <div class="add-members-footer">-->
 | 
			
		||||
<!--            <button class="add-member-button" onclick="addMember()">Добавить</button>-->
 | 
			
		||||
<!--        </div>-->
 | 
			
		||||
<!--    </div>-->
 | 
			
		||||
<!--</div>-->
 | 
			
		||||
<!--<div class="overlay" id="delete-chat">-->
 | 
			
		||||
<!--    <div class="delete-chat">-->
 | 
			
		||||
<!--        <div class="delete-chat-header">-->
 | 
			
		||||
<!--            <span class="delete-close" onclick="closeConfirm()">×</span>-->
 | 
			
		||||
<!--            <h2>Вы уверены, что хотите удалить чат?</h2>-->
 | 
			
		||||
<!--        </div>-->
 | 
			
		||||
<!--        <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>
 | 
			
		||||
x
 | 
			
		||||
    <div class="document-container resp-container">
 | 
			
		||||
        <div id="navigation-panel" class="panel">
 | 
			
		||||
            <a href="/user/{% WRITE userinfo.nickname %}" id="go-to-my-profile" class="panel-thing">
 | 
			
		||||
                <img alt="Go to my profile" src="/assets/img/user.svg" width="32px">
 | 
			
		||||
            </a>
 | 
			
		||||
            <p class="panel-thing panel-header-txt">
 | 
			
		||||
                List of available rooms
 | 
			
		||||
            </p>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="dynamic-block-list">
 | 
			
		||||
            <img id="CL-bacbe" class="button-add centered-block-el" alt="New chat" src="/assets/img/add.svg">
 | 
			
		||||
            <div class="dynamic-block-list-el-container" id="CL-dblec">
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script src="/assets/js/common.js"></script>
 | 
			
		||||
    <script src="/assets/js/common-popup.js"></script>
 | 
			
		||||
    <script src="/assets/js/list-rooms.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
{% ENDELDEF %}
 | 
			
		||||
{% ENDELDEF %}
 | 
			
		||||
 | 
			
		||||
@ -1,29 +1,41 @@
 | 
			
		||||
{% ELDEF main JSON pres JSON userinfo JSON errors %}
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="{% WRITE pres.lang %}">
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <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">
 | 
			
		||||
    <title>Login Page</title>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
{% FOR msg IN errors %}
 | 
			
		||||
    <div class="error-msg">
 | 
			
		||||
        {% WRITE msg.text %}
 | 
			
		||||
    {% FOR error IN errors %}
 | 
			
		||||
        <div class="server-notif-error-msg-box">
 | 
			
		||||
            {% 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>
 | 
			
		||||
{% 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>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
@ -1,26 +1,36 @@
 | 
			
		||||
{% ELDEF main JSON pres JSON userprofile %}
 | 
			
		||||
{% ELDEF main JSON pres JSON userinfo JSON alienprofile %}
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <link rel="stylesheet" href="/assets/css/profile.css">
 | 
			
		||||
    <title>Профиль</title>
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <link rel="icon" type="image/png" href="/assets/img/favicon.png">
 | 
			
		||||
    <link rel="stylesheet" href="/assets/css/common.css">
 | 
			
		||||
    <!-- This page is so simple, that it does not even have it's separate css file -->
 | 
			
		||||
    <title>User Profile</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <div class="main-container">
 | 
			
		||||
        <div class="profile-header">
 | 
			
		||||
            <h1>Профиль пользователя</h1>
 | 
			
		||||
 | 
			
		||||
    <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>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="columns">
 | 
			
		||||
            <div class="column">
 | 
			
		||||
                <p>{% WRITE userprofile.name %} ( {% WRITE userprofile.nickname %} )</p>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="additional-info">
 | 
			
		||||
            <p>О себе</p>
 | 
			
		||||
            <p>{% WRITE userprofile.bio %}</p><br>
 | 
			
		||||
 | 
			
		||||
        <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>
 | 
			
		||||
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
{% ENDELDEF%}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								assets/css/chat-members.css
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
@ -1,202 +1,142 @@
 | 
			
		||||
body {
 | 
			
		||||
    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%;
 | 
			
		||||
body, html {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    object-fit: cover;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chat-message .message-content {
 | 
			
		||||
    max-width: 70%;
 | 
			
		||||
#chat-widget {
 | 
			
		||||
    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;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
 | 
			
		||||
    color: black;
 | 
			
		||||
    /*justify-self: flex-start;*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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;
 | 
			
		||||
    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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chat-footer {
 | 
			
		||||
    display: flex;
 | 
			
		||||
#input-panel {
 | 
			
		||||
    min-height: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#message-input {
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
    padding-left: 50px;
 | 
			
		||||
    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;
 | 
			
		||||
    height: auto;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    background-color: rgba(0, 0, 0, 0.5);
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    z-index: 1000;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    border: 1px solid #1000d0;
 | 
			
		||||
    border-radius : 7px;
 | 
			
		||||
    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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								assets/css/common-popup.css
									
									
									
									
									
										Normal 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
									
								
							
							
						
						@ -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
									
								
							
							
						
						@ -0,0 +1,8 @@
 | 
			
		||||
.chat-debug-rect{
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    opacity: 0.3;
 | 
			
		||||
    height: 3px;
 | 
			
		||||
    z-index: 2;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								assets/css/edit-profile.css
									
									
									
									
									
										Normal 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;
 | 
			
		||||
}
 | 
			
		||||
@ -1,253 +1,51 @@
 | 
			
		||||
body {
 | 
			
		||||
    font-family: Arial, sans-serif;
 | 
			
		||||
    background-color: #f0f0f0;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
#CL-bacbe {
 | 
			
		||||
    margin-top: 6px;
 | 
			
		||||
    margin-bottom: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.container {
 | 
			
		||||
    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 {
 | 
			
		||||
.CL-my-chat-box {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
    margin: 10px 0;
 | 
			
		||||
    background-color: #fafafa;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    transition: background-color 0.3s ease;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.room-item:hover {
 | 
			
		||||
    background-color: #eaeaea;
 | 
			
		||||
.CL-my-chat-box-nickname {
 | 
			
		||||
    margin-left: 8px;
 | 
			
		||||
    justify-self: flex-start;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.room-name {
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
    color: #555;
 | 
			
		||||
.CL-my-chat-box-name {
 | 
			
		||||
    margin-left: 14px;
 | 
			
		||||
    justify-self: flex-start;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.join-button {
 | 
			
		||||
    padding: 10px 15px;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    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;
 | 
			
		||||
.CL-my-chat-box-my-role {
 | 
			
		||||
    margin-left: auto;
 | 
			
		||||
    justify-self: flex-end;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.modal {
 | 
			
		||||
    display: none;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    top: 0;
 | 
			
		||||
.CL-my-chat-box-leave-btn {
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
    margin-right: 8px;
 | 
			
		||||
    justify-self: flex-end;
 | 
			
		||||
    width: 16px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* The morbid thing */
 | 
			
		||||
table.id-str-input-table {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
    background-color: rgba(0, 0, 0, 0.4);
 | 
			
		||||
    border-collapse: collapse; /* Combine borders */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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;
 | 
			
		||||
.id-str-input-td1, .id-str-input-td2 {
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    transition: background-color 0.3s ease;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.create-room-button:hover {
 | 
			
		||||
    background-color: #218838;
 | 
			
		||||
.id-str-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 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.overlay {
 | 
			
		||||
    display: none;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
.id-str-input-td2 {
 | 
			
		||||
    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;
 | 
			
		||||
}
 | 
			
		||||
@ -1,59 +1,34 @@
 | 
			
		||||
/* I have no idea what is going on here */
 | 
			
		||||
body {
 | 
			
		||||
    font-family: Arial, sans-serif;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    height: 100vh;
 | 
			
		||||
    height: 100vh; /* Full viewport height */
 | 
			
		||||
    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;
 | 
			
		||||
    background-color: #ffffff; /* Brighter box color */
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
 | 
			
		||||
    width: 50%; /* Set width of the form */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 {
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
    color: #2F4F4F;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input {
 | 
			
		||||
/* The morbid thing */
 | 
			
		||||
table.logins-input-table {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    background: #f7f7f7;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    border: 1px solid #ddd;
 | 
			
		||||
    border-radius: 20px;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    border-collapse: collapse; /* Combine borders */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
.logins-input-td1, .logins-input-td2 {
 | 
			
		||||
    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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.error-msg {
 | 
			
		||||
    color: red;
 | 
			
		||||
    background-color: #ffc0c0;
 | 
			
		||||
    border-color: red;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
}
 | 
			
		||||
.logins-input-td1 {
 | 
			
		||||
    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%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
@ -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
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 136 KiB  | 
							
								
								
									
										20
									
								
								assets/img/add.svg
									
									
									
									
									
										Normal 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  | 
							
								
								
									
										
											BIN
										
									
								
								assets/img/broken-clavicle.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 258 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/img/clavicle-transparent.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 288 KiB  | 
							
								
								
									
										18
									
								
								assets/img/delete.svg
									
									
									
									
									
										Normal 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
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/img/favicon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 824 B  | 
							
								
								
									
										30
									
								
								assets/img/link.svg
									
									
									
									
									
										Normal 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
									
								
							
							
						
						@ -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  | 
| 
		 Before Width: | Height: | Size: 9.7 KiB  | 
							
								
								
									
										20
									
								
								assets/img/return.svg
									
									
									
									
									
										Normal 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  | 
							
								
								
									
										20
									
								
								assets/img/settings-iron.svg
									
									
									
									
									
										Normal 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
									
								
							
							
						
						@ -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  | 
@ -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();
 | 
			
		||||
}
 | 
			
		||||
@ -1,162 +1,449 @@
 | 
			
		||||
let members = [
 | 
			
		||||
    { 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 = '';
 | 
			
		||||
let LocalHistoryId = 0;
 | 
			
		||||
 | 
			
		||||
    members.forEach((member, index) => {
 | 
			
		||||
        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);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
let members = new Map();
 | 
			
		||||
 | 
			
		||||
function deleteMember(index) {
 | 
			
		||||
    members.splice(index, 1);
 | 
			
		||||
    renderMembersList();
 | 
			
		||||
}
 | 
			
		||||
let loadedMessages = new Map();  // messageSt objects
 | 
			
		||||
/*
 | 
			
		||||
container: EL, box: EL, offset: number (msgPres) */
 | 
			
		||||
let visibleMessages = new Map();  // HTMLElement objects
 | 
			
		||||
 | 
			
		||||
async function sendMessage() {
 | 
			
		||||
    const chatMessages = document.getElementById('chat-messages');
 | 
			
		||||
    const chatInput = document.getElementById('chat-input');
 | 
			
		||||
    const message = chatInput.value;
 | 
			
		||||
let anchoredMsg = -1;
 | 
			
		||||
let visibleMsgSegStart = -1;
 | 
			
		||||
let visibleMsgSegEnd = -2;
 | 
			
		||||
let offsetOfAnchor = 500;
 | 
			
		||||
let highestPoint = null;
 | 
			
		||||
let lowestPoint = null;
 | 
			
		||||
 | 
			
		||||
    if (message.trim() !== '') {
 | 
			
		||||
        const request = {
 | 
			
		||||
            'chatId': currentChatID,
 | 
			
		||||
            'LocalHistoryId': currentHistoryId,
 | 
			
		||||
            'content': {
 | 
			
		||||
                'text': message
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
let lastMsgId = -1;
 | 
			
		||||
let myRoleHere = null;  // Dung local state updates should be updated first
 | 
			
		||||
 | 
			
		||||
        const response = await fetch("/internalapi/sendMessage", {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            headers: {
 | 
			
		||||
                'Content-Type': 'application/json'
 | 
			
		||||
            },
 | 
			
		||||
            body: JSON.stringify(request)
 | 
			
		||||
        });
 | 
			
		||||
// Would start with true if opened `/chat/<>`
 | 
			
		||||
let bumpedAtTheBottom = false;
 | 
			
		||||
 | 
			
		||||
        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) {
 | 
			
		||||
            const update = res.update[0];
 | 
			
		||||
            currentHistoryId = update.HistoryId;
 | 
			
		||||
// Positive in production, negative for debug
 | 
			
		||||
let softZoneSz = -150;
 | 
			
		||||
let chatPadding = 300;
 | 
			
		||||
 | 
			
		||||
            const messageElement = document.createElement('div');
 | 
			
		||||
            messageElement.classList.add('chat-message');
 | 
			
		||||
 | 
			
		||||
            const avatarElement = document.createElement('div');
 | 
			
		||||
            avatarElement.classList.add('avatar');
 | 
			
		||||
 | 
			
		||||
            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
 | 
			
		||||
function genSentBase(){
 | 
			
		||||
    return {
 | 
			
		||||
        'chatUpdReq': {
 | 
			
		||||
            'LocalHistoryId': LocalHistoryId,
 | 
			
		||||
            'chatId': openedchat.id
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    const res = await fetch('/internalapi/editMessage', {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
            'Content-Type': 'application/json'
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify(req)
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    const response = await res.json();
 | 
			
		||||
    if (response.update) {
 | 
			
		||||
        currentHistoryId = response.update[0].HistoryId;
 | 
			
		||||
function genSentBaseGMN(){
 | 
			
		||||
    let Sent = genSentBase();
 | 
			
		||||
    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
									
								
							
							
						
						@ -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
									
								
							
							
						
						@ -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";
 | 
			
		||||
}
 | 
			
		||||
@ -1,186 +1,188 @@
 | 
			
		||||
let rooms = {};
 | 
			
		||||
let roomToDelete = null;
 | 
			
		||||
let currentRoom = null;
 | 
			
		||||
let currentHistoryId = 0;
 | 
			
		||||
let LocalHistoryId = 0;
 | 
			
		||||
 | 
			
		||||
function openRoom(currentRoom) {
 | 
			
		||||
    alert('Вы вошли в комнату: ' + currentRoom);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
function genSentBase(){
 | 
			
		||||
    return {
 | 
			
		||||
        'chatListUpdReq': {
 | 
			
		||||
            'LocalHistoryId': LocalHistoryId
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        const response = await fetch('/internalapi/createChat', {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            headers: {
 | 
			
		||||
                'Content-Type': 'application/json'
 | 
			
		||||
            },
 | 
			
		||||
            body: JSON.stringify(request)
 | 
			
		||||
        });
 | 
			
		||||
let myChats = new Map();
 | 
			
		||||
let chatBoxes = new Map();
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
            rooms[roomNickname] = true;
 | 
			
		||||
            closeCreateRoomModal();
 | 
			
		||||
            currentHistoryId = res.update.LocalHistoryId;
 | 
			
		||||
            window.location.href = '/chat/' + roomNickname;
 | 
			
		||||
 | 
			
		||||
let chatRenunciationWinStoredId = -1;
 | 
			
		||||
 | 
			
		||||
function shouldShowDeleteButton(myMembershipSt){
 | 
			
		||||
    return myMembershipSt.myRoleHere === userChatRoleDeleted;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 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 {
 | 
			
		||||
            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) {
 | 
			
		||||
    const roomList = document.querySelector('.room-list');
 | 
			
		||||
    const existingRoomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName);
 | 
			
		||||
    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);
 | 
			
		||||
/* Use it ONLY if `Recv` reported success */
 | 
			
		||||
function updateLocalStateFromRecv(Recv){
 | 
			
		||||
    updateLocalStateFromChatListUpdResp(Recv.chatListUpdResp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function removeRoomFromList(roomName, roomNickname) {
 | 
			
		||||
    const roomList = document.querySelector('.room-list');
 | 
			
		||||
    const roomItem = Array.from(roomList.children).find(item => item.querySelector('.room-name').textContent === roomName);
 | 
			
		||||
    if (roomItem) {
 | 
			
		||||
        roomList.removeChild(roomItem);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function initializeRoomList() {
 | 
			
		||||
    try {
 | 
			
		||||
        const response = await fetch('/internalapi/getChatList', {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            headers: {
 | 
			
		||||
                'Content-Type': 'application/json'
 | 
			
		||||
            },
 | 
			
		||||
            body: JSON.stringify({})
 | 
			
		||||
function configureChatCreationInterface(){
 | 
			
		||||
    document.getElementById("chat-creation-win-yes").onclick = function (ev) {
 | 
			
		||||
        if (ev.button !== 0)
 | 
			
		||||
            return;
 | 
			
		||||
        let chatNicknameInput = document.getElementById("chat-nickname-input");
 | 
			
		||||
        let chatNameInput = document.getElementById("chat-name-input");
 | 
			
		||||
        let nickname = String(chatNicknameInput.value);
 | 
			
		||||
        let name = String(chatNameInput.value);
 | 
			
		||||
        deactivateActivePopup();
 | 
			
		||||
        let Sent = genSentBase();
 | 
			
		||||
        Sent.content = {};
 | 
			
		||||
        Sent.content.nickname = nickname;
 | 
			
		||||
        Sent.content.name = name;
 | 
			
		||||
        apiRequest("createChat", Sent
 | 
			
		||||
        ).then((Recv) => {
 | 
			
		||||
            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) {
 | 
			
		||||
            res.chats.forEach(chat => {
 | 
			
		||||
                addRoomToList(chat.content.name, chat.content.nickname);
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new Error(res.error || 'Неизвестная ошибка');
 | 
			
		||||
        }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        alert('Ошибка загрузки списка чатов: ' + error.message);
 | 
			
		||||
    document.getElementById("CL-bacbe").onclick = function (ev){
 | 
			
		||||
        if (ev.button !== 0)
 | 
			
		||||
            return;
 | 
			
		||||
        let chatNicknameInput = document.getElementById("chat-nickname-input");
 | 
			
		||||
        let chatNameInput = document.getElementById("chat-name-input");
 | 
			
		||||
        chatNicknameInput.value = "";
 | 
			
		||||
        chatNameInput.value = "";
 | 
			
		||||
        activatePopupWindowById("chat-creation-win");
 | 
			
		||||
        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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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({})
 | 
			
		||||
__mainloopDelayMs = 3000;
 | 
			
		||||
__guestMainloopPollerAction = function(){
 | 
			
		||||
    let Sent = genSentBase();
 | 
			
		||||
    apiRequest("chatListPollEvents", Sent
 | 
			
		||||
    ).then((Recv) => {
 | 
			
		||||
        console.log("Got a response");
 | 
			
		||||
        console.log(Recv);
 | 
			
		||||
        updateLocalStateFromRecv(Recv);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
    if (event.key === 'Enter') {
 | 
			
		||||
        createRoom();
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
document.addEventListener('DOMContentLoaded', initializeRoomList);
 | 
			
		||||
window.onload = function () {
 | 
			
		||||
    console.log("Loading complete");
 | 
			
		||||
    updateLocalStateFromChatListUpdResp(initial_chatListUpdResp);
 | 
			
		||||
    configureChatCreationInterface();
 | 
			
		||||
    configureChatRenunciationInterfaceWinPart();
 | 
			
		||||
    mainloopPoller();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -112,6 +112,10 @@ namespace een9 {
 | 
			
		||||
                            return status;
 | 
			
		||||
                        }
 | 
			
		||||
                        res.body.reserve(std::min(100000ul, body_size));
 | 
			
		||||
                        if (body_size == 0) {
 | 
			
		||||
                            status = 1;
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (!res.has_body) {
 | 
			
		||||
 | 
			
		||||
@ -55,13 +55,16 @@ namespace nytl {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    char skip(ParsingContext& ctx) {
 | 
			
		||||
        ASSERT(ctx.pos < ctx.text.size(), "Unexpected EOF");
 | 
			
		||||
        if (ctx.pos >= ctx.text.size())
 | 
			
		||||
            THROW("Unexpected EOF");
 | 
			
		||||
        return advance(ctx);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void skip(ParsingContext& ctx, char ch) {
 | 
			
		||||
        ASSERT(ctx.pos < ctx.text.size(), "Unexpected EOF");
 | 
			
		||||
        ASSERT(ctx.text[ctx.pos] == ch, "Unexpected character");
 | 
			
		||||
        if (ctx.pos >= ctx.text.size())
 | 
			
		||||
            THROW("Unexpected EOF");
 | 
			
		||||
        if (ctx.text[ctx.pos] != ch)
 | 
			
		||||
            THROW("Unexpected character");
 | 
			
		||||
        advance(ctx);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -150,7 +153,8 @@ namespace nytl {
 | 
			
		||||
    void parse_bare_file(const std::string& filename, const std::string& content,
 | 
			
		||||
                             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);
 | 
			
		||||
        rstrip(txt);
 | 
			
		||||
        size_t cut = 9999999999999;
 | 
			
		||||
@ -170,13 +174,15 @@ namespace nytl {
 | 
			
		||||
        uptr<TPFrame> toMe(bool returned, ParsingContext& ctx) {
 | 
			
		||||
            if (!returned) {
 | 
			
		||||
                std::string nm = readName(ctx);
 | 
			
		||||
                ASSERT(!nm.empty(), "Type specification expected");
 | 
			
		||||
                if (nm.empty())
 | 
			
		||||
                    THROW("Type specification expected");
 | 
			
		||||
                nm = make_uppercase(nm);
 | 
			
		||||
                if (nm == "JSON") {
 | 
			
		||||
                    result = json::JSON(true);
 | 
			
		||||
                    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, '(');
 | 
			
		||||
                result.asArray();
 | 
			
		||||
                assert(result.isArray());
 | 
			
		||||
@ -217,8 +223,10 @@ namespace nytl {
 | 
			
		||||
        uptr<EPFrame> toMe(bool returned, ParsingContext& ctx, const arg_name_list_t& local_var_names) {
 | 
			
		||||
            if (!returned) {
 | 
			
		||||
                std::string first = readName(ctx);
 | 
			
		||||
                ASSERT(!first.empty(), "Expression should start with 'root' name of global package or local variable");
 | 
			
		||||
                ASSERT(first != "_", "_ ??? ARE YOU KIDDING???");
 | 
			
		||||
                if (first.empty())
 | 
			
		||||
                    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) {
 | 
			
		||||
                    result["V"].asInteger() = json::Integer((int64_t)local_var_names.at(first));
 | 
			
		||||
                } else {
 | 
			
		||||
@ -243,7 +251,8 @@ namespace nytl {
 | 
			
		||||
                    t = readUint(ctx);
 | 
			
		||||
                    if (!t.empty()) {
 | 
			
		||||
                        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);
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
@ -352,7 +361,8 @@ namespace nytl {
 | 
			
		||||
                ElementPart::when_for_put_S& P = result.parts.back().when_for_put;
 | 
			
		||||
                skipWhitespace(ctx);
 | 
			
		||||
                std::string V1 = readName(ctx);
 | 
			
		||||
                ASSERT(!V1.empty(), "Expected variable name");
 | 
			
		||||
                if (V1.empty())
 | 
			
		||||
                    THROW("Expected variable name");
 | 
			
		||||
                skipWhitespace(ctx);
 | 
			
		||||
                bool have_colon_and_2 = false;
 | 
			
		||||
                std::string V2;
 | 
			
		||||
@ -364,7 +374,8 @@ namespace nytl {
 | 
			
		||||
                    skipWhitespace(ctx);
 | 
			
		||||
                }
 | 
			
		||||
                op = make_uppercase(readName(ctx));
 | 
			
		||||
                ASSERT(op == "IN", "Expected IN");
 | 
			
		||||
                if (op != "IN")
 | 
			
		||||
                    THROW("Expected IN");
 | 
			
		||||
                skipWhitespace(ctx);
 | 
			
		||||
                P.ref_over = parse_expression(ctx, local_var_names);
 | 
			
		||||
                P.internal_element = el_name + ".~" + std::to_string(free_hidden++);
 | 
			
		||||
@ -372,13 +383,15 @@ namespace nytl {
 | 
			
		||||
                newborn.is_hidden = true;
 | 
			
		||||
                arg_name_list_t local_var_names_of_nxt = local_var_names;
 | 
			
		||||
                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();
 | 
			
		||||
                    local_var_names_of_nxt.emplace(V1, k);
 | 
			
		||||
                    (have_colon_and_2 ? P.where_key_var : P.where_value_var) = (ssize_t)k;
 | 
			
		||||
                }
 | 
			
		||||
                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();
 | 
			
		||||
                    local_var_names_of_nxt.emplace(V2, 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;
 | 
			
		||||
                skipWhitespace(ctx);
 | 
			
		||||
                std::string Vn = readName(ctx);
 | 
			
		||||
                ASSERT(!Vn.empty(), "Expected variable name");
 | 
			
		||||
                ASSERT(Vn != "_", "Are you kidding???");
 | 
			
		||||
                if (Vn.empty() || Vn == "_")
 | 
			
		||||
                    THROW("REF: expected variable name");
 | 
			
		||||
                skipWhitespace(ctx);
 | 
			
		||||
                op = make_uppercase(readName(ctx));
 | 
			
		||||
                ASSERT(op == "AS", "Expected AS");
 | 
			
		||||
                if (op != "AS")
 | 
			
		||||
                    THROW("Expected AS");
 | 
			
		||||
                skipWhitespace(ctx);
 | 
			
		||||
                P.ref_over = parse_expression(ctx, local_var_names);
 | 
			
		||||
                P.internal_element = el_name + ".~" + std::to_string(free_hidden++);
 | 
			
		||||
@ -467,13 +481,15 @@ namespace nytl {
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            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);
 | 
			
		||||
                prepare_to_depart_parts();
 | 
			
		||||
                return NULL;
 | 
			
		||||
            }
 | 
			
		||||
            if (op == "ENDFOR") {
 | 
			
		||||
                ASSERT(myself == gone_for_for, "Unexpected end of for cycle");
 | 
			
		||||
                if (myself != gone_for_for)
 | 
			
		||||
                    THROW("Unexpected ENDFOR");
 | 
			
		||||
                skipWhitespace(ctx);
 | 
			
		||||
                /* 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
 | 
			
		||||
@ -491,7 +507,8 @@ namespace nytl {
 | 
			
		||||
                return NULL;
 | 
			
		||||
            }
 | 
			
		||||
            if (op == "ENDREF") {
 | 
			
		||||
                assert(myself == gone_for_ref);
 | 
			
		||||
                if (myself != gone_for_ref)
 | 
			
		||||
                    THROW("Unexpected ENDREF");
 | 
			
		||||
                skip_magic_block_end(ctx, syntax);
 | 
			
		||||
                prepare_to_depart_parts();
 | 
			
		||||
                return NULL;
 | 
			
		||||
@ -525,12 +542,15 @@ namespace nytl {
 | 
			
		||||
            if (peep(ctx) == EOFVAL)
 | 
			
		||||
                break;
 | 
			
		||||
            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);
 | 
			
		||||
            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;
 | 
			
		||||
            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];
 | 
			
		||||
            arg_name_list_t arglist;
 | 
			
		||||
            while (true) {
 | 
			
		||||
@ -540,9 +560,11 @@ namespace nytl {
 | 
			
		||||
                newborn.arguments.push_back(parse_type(ctx));
 | 
			
		||||
                skipWhitespace(ctx);
 | 
			
		||||
                std::string argname = readName(ctx);
 | 
			
		||||
                ASSERT(!argname.empty(), "Expected argument name");
 | 
			
		||||
                if (argname.empty())
 | 
			
		||||
                    THROW("Expected argument name");
 | 
			
		||||
                if (argname != "_") {
 | 
			
		||||
                    ASSERT(arglist.count(argname) == 0, "Repeated argument (" + argname + ")");
 | 
			
		||||
                    if (arglist.count(argname) != 0)
 | 
			
		||||
                        THROW("Repeated argument (" + argname + ")");
 | 
			
		||||
                    size_t k = arglist.size();
 | 
			
		||||
                    arglist[argname] = k;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -212,7 +212,7 @@ namespace nytl {
 | 
			
		||||
        const std::function<std::string(std::string)> &escape) {
 | 
			
		||||
        if (!returned)
 | 
			
		||||
            if (elem_ns.count(name) != 1)
 | 
			
		||||
                THROW("No such element");
 | 
			
		||||
                THROW("No such element (" + name + ")");
 | 
			
		||||
        const Element& el = elem_ns.at(name);
 | 
			
		||||
        if (!returned) {
 | 
			
		||||
            /* Continue to do checks */
 | 
			
		||||
@ -227,7 +227,9 @@ namespace nytl {
 | 
			
		||||
                        // If not json is expected, element must be expected
 | 
			
		||||
                        assert(el.arguments[i].isArray());
 | 
			
		||||
                        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);
 | 
			
		||||
                        // ASSERT(passed_args);
 | 
			
		||||
                        if(el.arguments[i].asArray() != arg_element.arguments)
 | 
			
		||||
 | 
			
		||||
@ -3,16 +3,32 @@
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
namespace iu9cawebchat {
 | 
			
		||||
    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);
 | 
			
		||||
    bool is_membership_row_present(SqliteConnection& conn, int64_t chatId, int64_t alienUserId) {
 | 
			
		||||
        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 alien_chatlist_HistoryId_BEFORE_EV = get_current_history_id_of_user_chatList(conn, alienUserId);
 | 
			
		||||
        sqlite_nooutput(conn,
 | 
			
		||||
            "INSERT INTO `user_chat_membership` (`userId`, `chatId`, `user_chatList_IncHistoryId`,"
 | 
			
		||||
            "`chat_IncHistoryId`, `role`) VALUES (?1, ?2, ?3, ?4, ?5)",
 | 
			
		||||
            {{1, alienUserId}, {2, chatId}, {3, alien_chatlist_HistoryId_BEFORE_EV + 1},
 | 
			
		||||
            {4, chat_HistoryId_BEFORE_EV + 1}, {5, role}}, {});
 | 
			
		||||
        if (!is_membership_row_present(conn, chatId, alienUserId)) {
 | 
			
		||||
            sqlite_nooutput(conn,
 | 
			
		||||
               "INSERT INTO `user_chat_membership` (`userId`, `chatId`, `user_chatList_IncHistoryId`,"
 | 
			
		||||
               "`chat_IncHistoryId`, `role`) VALUES (?1, ?2, ?3, ?4, ?5)",
 | 
			
		||||
               {{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,
 | 
			
		||||
            "UPDATE `chat` SET `it_HistoryId` = ?1 WHERE `id` = ?2", {{1, chat_HistoryId_BEFORE_EV + 1},
 | 
			
		||||
            {2, chatId}}, {});
 | 
			
		||||
@ -22,6 +38,12 @@ namespace iu9cawebchat {
 | 
			
		||||
            {{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) {
 | 
			
		||||
        int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
 | 
			
		||||
        int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
 | 
			
		||||
@ -33,12 +55,17 @@ namespace iu9cawebchat {
 | 
			
		||||
        try {
 | 
			
		||||
            alien = lookup_user_content_by_nickname(conn, alien_nickname);
 | 
			
		||||
        } 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);
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
@ -7,13 +7,16 @@ namespace iu9cawebchat {
 | 
			
		||||
        std::string new_chat_name = Sent["content"]["name"].asString();
 | 
			
		||||
        std::string new_chat_nickname = Sent["content"]["nickname"].asString();
 | 
			
		||||
        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))
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,11 @@
 | 
			
		||||
#include "server_data_interact.h"
 | 
			
		||||
#include <engine_engine_number_9/baza_throw.h>
 | 
			
		||||
#include "../debug.h"
 | 
			
		||||
 | 
			
		||||
namespace iu9cawebchat {
 | 
			
		||||
    json::JSON internalapi_deleteMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
 | 
			
		||||
        int64_t chatId = Sent["chatUpdReq"].asInteger().get_int();
 | 
			
		||||
        // debug_print_json(Sent);
 | 
			
		||||
        int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
 | 
			
		||||
        int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
 | 
			
		||||
        if (my_role_here == user_chat_role_deleted)
 | 
			
		||||
            een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ namespace iu9cawebchat {
 | 
			
		||||
        int64_t chatId = Sent["chatId"].asInteger().get_int();
 | 
			
		||||
        if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted)
 | 
			
		||||
            een9_THROW("Not a member");
 | 
			
		||||
        kick_from_chat(conn, uid, chatId);
 | 
			
		||||
        kick_from_chat(conn, chatId, uid);
 | 
			
		||||
        json::JSON Recv;
 | 
			
		||||
        poll_update_chat_list(conn, uid, Sent, Recv);
 | 
			
		||||
        return Recv;
 | 
			
		||||
 | 
			
		||||
@ -2,34 +2,17 @@
 | 
			
		||||
#include <engine_engine_number_9/baza_throw.h>
 | 
			
		||||
 | 
			
		||||
namespace iu9cawebchat {
 | 
			
		||||
    void kick_from_chat(SqliteConnection& conn, int64_t alienUserId, int64_t chatId) {
 | 
			
		||||
        if (get_role_of_user_in_chat(conn, alienUserId, chatId) == 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}}, {});
 | 
			
		||||
    void kick_from_chat(SqliteConnection& conn, int64_t chatId, int64_t alienUserId) {
 | 
			
		||||
        alter_user_chat_role(conn, chatId, alienUserId, user_chat_role_deleted);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    json::JSON internalapi_removeMemberFromChat(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
 | 
			
		||||
        int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
 | 
			
		||||
        int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
 | 
			
		||||
        if (my_role_here != user_chat_role_deleted)
 | 
			
		||||
        if (my_role_here != user_chat_role_admin)
 | 
			
		||||
            een9_THROW("Only admin can delete members of chat");
 | 
			
		||||
        int64_t badAlienId = Sent["chatUpdReq"]["userId"].asInteger().get_int();
 | 
			
		||||
        kick_from_chat(conn, badAlienId, chatId);
 | 
			
		||||
        int64_t badAlienId = Sent["userId"].asInteger().get_int();
 | 
			
		||||
        kick_from_chat(conn, chatId, badAlienId);
 | 
			
		||||
        json::JSON Recv;
 | 
			
		||||
        poll_update_chat(conn, Sent, Recv);
 | 
			
		||||
        return Recv;
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
#include "server_data_interact.h"
 | 
			
		||||
#include <engine_engine_number_9/baza_throw.h>
 | 
			
		||||
#include "../str_fields.h"
 | 
			
		||||
#include "../debug.h"
 | 
			
		||||
 | 
			
		||||
namespace iu9cawebchat {
 | 
			
		||||
    /* No authorization check is performed
 | 
			
		||||
@ -13,16 +14,19 @@ namespace iu9cawebchat {
 | 
			
		||||
        int64_t chat_lastMsgId = get_lastMsgId_of_chat(conn, chatId);
 | 
			
		||||
        SqliteStatement req(conn,
 | 
			
		||||
            "INSERT INTO `message` (`chatId`, `id`, `senderUserId`, `exists`, `isSystem`, `chat_IncHistoryId`, "
 | 
			
		||||
            "`text`) VALUES (?1, ?2, ?3 1, ?4, ?5, ?6)",
 | 
			
		||||
            "`text`) VALUES (?1, ?2, ?3, 1, ?4, ?5, ?6)",
 | 
			
		||||
            {{1, chatId}, {2, chat_lastMsgId + 1}, {4, (int64_t)isSystem}, {5, chat_HistoryId_BEFORE_MSG + 1}}, {{6, text}});
 | 
			
		||||
        if (!isSystem)
 | 
			
		||||
            sqlite_stmt_bind_int64(req, 3, uid);
 | 
			
		||||
        if (sqlite_stmt_step(req, {}, {}) != SQLITE_DONE)
 | 
			
		||||
            een9_THROW("There must be something wrong");
 | 
			
		||||
        sqlite_nooutput(conn, "UPDATE `chat` SET `lastMsgId` = ?1, `it_HistoryId` = ?2 WHERE `id` = ?3",
 | 
			
		||||
            {{1, chat_lastMsgId + 1}, {2, chat_HistoryId_BEFORE_MSG + 1}, {3, chatId}}, {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    json::JSON internalapi_sendMessage(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
 | 
			
		||||
        int64_t chatId = Sent["chatUpdReq"].asInteger().get_int();
 | 
			
		||||
        debug_print_json(Sent);
 | 
			
		||||
        int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
 | 
			
		||||
        int64_t my_role_here = get_role_of_user_in_chat(conn, uid, chatId);
 | 
			
		||||
        if (my_role_here == user_chat_role_deleted)
 | 
			
		||||
            een9_THROW("Unauthorized user tries to access internalapi_getChatInfo");
 | 
			
		||||
@ -36,6 +40,7 @@ namespace iu9cawebchat {
 | 
			
		||||
 | 
			
		||||
        json::JSON Recv;
 | 
			
		||||
        poll_update_chat(conn, Sent, Recv);
 | 
			
		||||
        debug_print_json(Recv);
 | 
			
		||||
        return Recv;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,16 @@
 | 
			
		||||
#include "server_data_interact.h"
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <engine_engine_number_9/baza_throw.h>
 | 
			
		||||
#include "../debug.h"
 | 
			
		||||
 | 
			
		||||
namespace iu9cawebchat {
 | 
			
		||||
    json::JSON poll_update_chat_list_resp(SqliteConnection& conn, int64_t userId, int64_t LocalHistoryId) {
 | 
			
		||||
        printf("Userid: %ld\n", userId);
 | 
			
		||||
        json::JSON chatListUpdResp;
 | 
			
		||||
        SqliteStatement my_membership_changes(conn,
 | 
			
		||||
           "SELECT `chatId`, `role` FROM `user_chat_membership` WHERE `userId` = ?1 "
 | 
			
		||||
           "AND `user_chatList_IncHistoryId` > ?2", {{1, userId}, {2, LocalHistoryId}});
 | 
			
		||||
        json::jarr myChats = chatListUpdResp["myChats"].asArray();
 | 
			
		||||
        json::jarr& myChats = chatListUpdResp["myChats"].asArray();
 | 
			
		||||
        while (true) {
 | 
			
		||||
            fsql_integer_or_null ev_chatId, 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) {
 | 
			
		||||
        Recv["status"].asInteger() = json::Integer(0l);
 | 
			
		||||
        // 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) {
 | 
			
		||||
@ -52,7 +54,7 @@ namespace iu9cawebchat {
 | 
			
		||||
 | 
			
		||||
        json::jarr messages;
 | 
			
		||||
        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 ) )",
 | 
			
		||||
            {{1, chatId}, {2, LocalHistoryId}, {3, QSEG_A}, {4, QSEG_B}}, {});
 | 
			
		||||
        while (true) {
 | 
			
		||||
@ -73,7 +75,7 @@ namespace iu9cawebchat {
 | 
			
		||||
        json::jarr members;
 | 
			
		||||
 | 
			
		||||
        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}}, {});
 | 
			
		||||
        while (true) {
 | 
			
		||||
            fsql_integer_or_null alienUserId;
 | 
			
		||||
@ -101,7 +103,7 @@ namespace iu9cawebchat {
 | 
			
		||||
        chatUpdResp["members"].asArray() = poll_update_chat_resp_members(conn, chatId, 0);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        json::jarr messages = chatUpdResp["messages"].asArray();
 | 
			
		||||
        json::jarr& messages = chatUpdResp["messages"].asArray();
 | 
			
		||||
        if (selectedMsg >= 0) {
 | 
			
		||||
            RowMessage_Content msg = lookup_message_content(conn, chatId, selectedMsg);
 | 
			
		||||
            messages.push_back(make_messageSt_obj(msg.id, msg.senderUserId, msg.exists, msg.isSystem, msg.text));
 | 
			
		||||
@ -137,7 +139,7 @@ namespace iu9cawebchat {
 | 
			
		||||
        int64_t QSEG_A, int64_t QSEG_B) {
 | 
			
		||||
 | 
			
		||||
        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"]["LocalHistoryId"].asInteger().get_int(), QSEG_A, QSEG_B);
 | 
			
		||||
    }
 | 
			
		||||
@ -164,7 +166,8 @@ namespace iu9cawebchat {
 | 
			
		||||
 | 
			
		||||
    /* Reznya */
 | 
			
		||||
    json::JSON internalapi_getMessageNeighbours(SqliteConnection& conn, int64_t uid, const json::JSON& Sent) {
 | 
			
		||||
        int64_t chatId = Sent["chatId"].asInteger().get_int();
 | 
			
		||||
        debug_print_json(Sent);
 | 
			
		||||
        int64_t chatId = Sent["chatUpdReq"]["chatId"].asInteger().get_int();
 | 
			
		||||
        if (get_role_of_user_in_chat(conn, uid, chatId) == user_chat_role_deleted)
 | 
			
		||||
            een9_THROW("Authentication failure");
 | 
			
		||||
        int64_t lastMsgId = get_lastMsgId_of_chat(conn, chatId);
 | 
			
		||||
@ -191,6 +194,7 @@ namespace iu9cawebchat {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        poll_update_chat_important_segment(conn, Sent, Recv, qBeg, qEnd);
 | 
			
		||||
        debug_print_json(Recv);
 | 
			
		||||
        return Recv;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,10 @@
 | 
			
		||||
#include "../str_fields.h"
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
        if (role == user_chat_role_admin)
 | 
			
		||||
            return "admin";
 | 
			
		||||
@ -109,8 +113,6 @@ namespace iu9cawebchat {
 | 
			
		||||
        int status = sqlite_stmt_step(req, {{0, &senderUserId}, {1, &exists}, {2, &isSystem}},
 | 
			
		||||
            {{3, &msg_text}});
 | 
			
		||||
        if (status == SQLITE_ROW) {
 | 
			
		||||
            if (!(bool)exists.value)
 | 
			
		||||
                een9_THROW("Message existed, but now it does not");
 | 
			
		||||
            return {msgId, senderUserId.exist ? senderUserId.value : -1, (bool)exists.value,
 | 
			
		||||
                (bool)isSystem.value, msg_text.value};
 | 
			
		||||
        }
 | 
			
		||||
@ -144,7 +146,6 @@ namespace iu9cawebchat {
 | 
			
		||||
    /* All the api calls processing is done in dedicated files.
 | 
			
		||||
     * 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) {
 | 
			
		||||
        SqliteStatement req(conn, "SELECT `it_HistoryId` FROM `chat` WHERE `id` = ?1", {{1, chatId}}, {});
 | 
			
		||||
        fsql_integer_or_null HistoryId;
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,8 @@
 | 
			
		||||
#include <jsonincpp/string_representation.h>
 | 
			
		||||
 | 
			
		||||
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_regular = 2;
 | 
			
		||||
    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);
 | 
			
		||||
    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 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);
 | 
			
		||||
    void reserve_nickname(SqliteConnection& conn, const std::string& nickname);
 | 
			
		||||
 | 
			
		||||
@ -7,8 +7,10 @@ namespace iu9cawebchat {
 | 
			
		||||
    std::string when_page_chat(WorkerGuestData& wgd, const json::JSON& config_presentation,
 | 
			
		||||
        const een9::ClientRequest& req, const json::JSON& userinfo) {
 | 
			
		||||
 | 
			
		||||
        std::vector<std::string> path_segs = {""};
 | 
			
		||||
        std::vector<std::string> path_segs = {};
 | 
			
		||||
        path_segs.reserve(4);
 | 
			
		||||
        if (req.uri_path.empty() || req.uri_path[0] != '/')
 | 
			
		||||
            return page_E404(wgd);
 | 
			
		||||
        for (char ch: req.uri_path) {
 | 
			
		||||
            if (ch == '/')
 | 
			
		||||
                path_segs.emplace_back();
 | 
			
		||||
@ -23,16 +25,17 @@ namespace iu9cawebchat {
 | 
			
		||||
        }
 | 
			
		||||
        if (!check_nickname(chat_nickname))
 | 
			
		||||
            return page_E404(wgd);
 | 
			
		||||
        if (path_segs.size() == 2) {
 | 
			
		||||
        } else if (path_segs.size() == 4) {
 | 
			
		||||
        bool show_chat_members = (path_segs[0] == "chat-members");
 | 
			
		||||
        if (path_segs.size() == 4 && !show_chat_members) {
 | 
			
		||||
            if (path_segs[2] != "m")
 | 
			
		||||
                return page_E404(wgd);
 | 
			
		||||
            selected_message_id = std::stoll(path_segs[3]);
 | 
			
		||||
        } else if (path_segs.size() != 2) {
 | 
			
		||||
            return page_E404(wgd);
 | 
			
		||||
        } else
 | 
			
		||||
            return page_E404(wgd);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool chat_members = (path_segs[0] == "chat-members");
 | 
			
		||||
        if (userinfo.isNull())
 | 
			
		||||
            return een9::form_http_server_response_303("/");
 | 
			
		||||
 | 
			
		||||
        RowChat_Content chatInfo;
 | 
			
		||||
        try {
 | 
			
		||||
@ -40,7 +43,7 @@ namespace iu9cawebchat {
 | 
			
		||||
        } catch (const std::exception& e) {
 | 
			
		||||
            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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -51,8 +54,8 @@ namespace iu9cawebchat {
 | 
			
		||||
        // -1 means that nothing was selected
 | 
			
		||||
        openedchat["selectedMessageId"].asInteger() = json::Integer(selected_message_id);
 | 
			
		||||
        json::JSON initial_chatUpdResp = poll_update_chat_ONE_MSG_resp(*wgd.db, chatInfo.id, selected_message_id);
 | 
			
		||||
        if (chat_members)
 | 
			
		||||
            return http_R200("chat", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp});
 | 
			
		||||
        if (show_chat_members)
 | 
			
		||||
            return http_R200("chat-members", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp});
 | 
			
		||||
        return http_R200("chat", wgd, {&config_presentation, &userinfo, &openedchat, &initial_chatUpdResp});
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ namespace iu9cawebchat {
 | 
			
		||||
        }
 | 
			
		||||
        json::JSON initial_chatListUpdResp = poll_update_chat_list_resp(*wgd.db,
 | 
			
		||||
            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});
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,8 +18,10 @@ namespace iu9cawebchat {
 | 
			
		||||
                    if (cmp.first == "password")
 | 
			
		||||
                        password = cmp.second;
 | 
			
		||||
                }
 | 
			
		||||
                een9_ASSERT(check_nickname(nickname), "/login/accpet-data rejected impossible nickname");
 | 
			
		||||
                een9_ASSERT(check_password(password), "/login/accpet-data rejected impossible password");
 | 
			
		||||
                if (!check_nickname(nickname))
 | 
			
		||||
                    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);
 | 
			
		||||
 | 
			
		||||
            } catch(const std::exception& e){}
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,8 @@ namespace iu9cawebchat {
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
        if (userinfo.isNull())
 | 
			
		||||
            return een9::form_http_server_response_303("/");
 | 
			
		||||
        SqliteConnection& conn = *wgd.db;
 | 
			
		||||
        if (!is_page_of_certain_user(req.uri_path))
 | 
			
		||||
            return page_E404(wgd);
 | 
			
		||||
@ -104,7 +106,7 @@ namespace iu9cawebchat {
 | 
			
		||||
                json::JSON msg_list = jsonify_html_message_list({{"",
 | 
			
		||||
                    config_presentation["phr"]["decl"]["incorrect-profile-data"].asString()}});
 | 
			
		||||
                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);
 | 
			
		||||
        }
 | 
			
		||||
@ -112,9 +114,9 @@ namespace iu9cawebchat {
 | 
			
		||||
            json::JSON alien_userprofile = user_row_to_userprofile_obj(conn, alien);
 | 
			
		||||
            if (can_edit) {
 | 
			
		||||
                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");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										0
									
								
								src/web_chat/iu9_ca_web_chat_lib/debug.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										6
									
								
								src/web_chat/iu9_ca_web_chat_lib/debug.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,6 @@
 | 
			
		||||
#ifndef IU9_CA_WEB_CHAT_LIB_DEBUG_H
 | 
			
		||||
#define IU9_CA_WEB_CHAT_LIB_DEBUG_H
 | 
			
		||||
 | 
			
		||||
#define debug_print_json(x) printf("%s\n", json::generate_str(x, json::print_pretty).c_str())
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@ -75,6 +75,9 @@ namespace iu9cawebchat {
 | 
			
		||||
                                             "`chat_IncHistoryId` INTEGER NOT NULL,"
 | 
			
		||||
                                             "PRIMARY KEY (`chatId`, `id`)"
 | 
			
		||||
                                             ")");
 | 
			
		||||
            std::vector<std::string> sus = {"unknown", "undefined", "null", "none", "None", "NaN"};
 | 
			
		||||
            for (auto& s: sus)
 | 
			
		||||
                reserve_nickname(conn, s);
 | 
			
		||||
            add_user(conn, "root", "Rootov Root Rootovich", root_pw, "One admin to rule them all", 0);
 | 
			
		||||
            sqlite_nooutput(conn, "END");
 | 
			
		||||
        } catch (const std::exception& e) {
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,7 @@ namespace iu9cawebchat {
 | 
			
		||||
        samI.update({
 | 
			
		||||
            een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} },
 | 
			
		||||
            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", {
 | 
			
		||||
                {".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"}
 | 
			
		||||
            } },
 | 
			
		||||
 | 
			
		||||