{extends "../@layout.xml"} {block title}{$correspondent->getCanonicalName()}{/block} {block header} <a href="/im">{_my_messages}</a> » <a href="{$correspondent->getURL()}">{$correspondent->getCanonicalName()}</a> <div n:if="($online = $correspondent->getOnline()->timestamp()) + 2505600 > time()" style="float: right;"> {var diff = date_diff(date_create(), date_create('@' . $online))} {if 5 >= $diff->i} <span><b>{_online}</b></span> {else} <span>{$correspondent->isFemale() ? "заходила" : "заходил"} {$correspondent->getOnline()}</span> {/if} </div> {/block} {block wrap} <div class="messenger-app"> <div class="messenger-app--messages" data-bind="event: { scroll: onMessagesScroll }"> <div data-bind="foreach: messages"> <div class="messenger-app--messages---message" data-bind="css: { unread: !read }"> <img class="ava" data-bind="attr: { src: sender.avatar, alt: sender.name }" /> <div class="_content"> <a href="#" data-bind="attr: { href: sender.link }"> <strong data-bind="text: sender.name"></strong> </a> <span class="text" data-bind="html: text"></span> <div data-bind="foreach: attachments" class="attachments"> <div class="msg-attach-j"> <div data-bind="if: type === 'photo'" class="msg-attach-j-photo"> <a data-bind="attr: { href: link }"> <img data-bind="attr: { src: photo.url, alt: photo.caption }" /> </a> </div> </div> </div> </div> <div class="time" align="right"> <span data-bind="text: timing.sent"></span> </div> </div> </div> </div> <div class="messenger-app--input"> {if $correspondent->getId() === $thisUser->getId() || $correspondent->getPrivacyPermission('messages.write', $thisUser)} <img class="ava" src="{$thisUser->getAvatarUrl()}" alt="{$thisUser->getCanonicalName()}" /> <div class="messenger-app--input---messagebox"> <textarea data-bind="value: messageContent, event: { keydown: onTextareaKeyPress }" name="message" placeholder="Введите сообщение"></textarea> <button class="button" data-bind="click: sendMessage">Отправить</button> </div> <img class="ava" src="{$correspondent->getAvatarUrl()}" alt="{$correspondent->getCanonicalName()}" /> {else} <div class="blocked" data-localized-text="Вы не можете писать сообщения {$correspondent->getCanonicalName()} из-за его настроек приватности."></div> {/if} </div> </div> <script src="https://knockoutjs.com/downloads/knockout-3.5.1.js"></script> <script> function MessengerViewModel(initialMessages = []) { window.messages = ko.observableArray(initialMessages); this.messages = window.messages; this.messageContent = ko.observable(""); this.sendMessage = model => { if(model.messageContent() === "") return false; window.Msg.sendMessage(model.messageContent()); model.messageContent(""); }; this.loadHistory = _ => { window.Msg._loadHistory(); }; this.onMessagesScroll = (model, e) => { if(e.target.scrollTop < 21) model.loadHistory(); }; this.onTextareaKeyPress = (model, e) => { if(e.which === 13) { if(!e.metaKey && !e.shiftKey) { let ta = u("textarea[name=message]").nodes[0]; ta.blur(); //Fix update model.sendMessage(model); ta.focus(); return false; } } return true; }; } class Messenger { constructor(messages = []) { this.ko = ko.applyBindings(new MessengerViewModel(messages)); this.appEl = document.querySelector(".messenger-app--messages"); this.offset = 0; this._loadHistory(); this._setupListener(); this.appEl.scrollTop = this.appEl.scrollHeight; } _loadHistory() { let xhr = new XMLHttpRequest(); xhr.open("GET", "/im/api/messages" + {$correspondent->getId()} + `/${ this.offset }.json`, false); xhr.send(); let messages = JSON.parse(xhr.responseText); let lastMessage = messages[messages.length - 1]; if(typeof lastMessage !== "undefined") this.offset = lastMessage.uuid; this.prependMessages(messages); } _setupListener() { let listenLongpool = () => { let xhr = new XMLHttpRequest(); xhr.open("GET", "/im12", true); xhr.onload = () => { let data = JSON.parse(xhr.responseText); data.forEach(event => { event = event.event; if(event.type !== "newMessage") return; else if(event.message.sender.id !== {$correspondent->getId()}) return; else if(this.offset >= event.message.uuid) return void(console.warn("Gay message recieved, skipping. [-WHeterosexual]")); this.addMessage(event.message); this.offset = event.message.uuid; }); listenLongpool(); }; xhr.send(); }; listenLongpool(); } appendMessages(messages) { messages.forEach(m => window.messages.push(m)); } prependMessages(messages) { messages.forEach(m => window.messages.unshift(m)); } addMessage(message, scroll = true) { this.appendMessages([message]); if(scroll) { this.appEl.scrollTop = this.appEl.scrollHeight; } } _patchMessage(tempId, message) { for(let i = window.messages().length - 1; i > -1; i--) { let msg = window.messages()[i]; if(typeof msg._tuid === "undefined") return; else if(msg._tuid !== tempId) return; window.messages.valueWillMutate(); window.messages()[i] = message; window.messages.valueHasMutated(); } } _newSelfMessage(content = "...") { return { "sender": { "link": {$thisUser->getURL()}, "avatar": {$thisUser->getAvatarUrl()}, "name": {$thisUser->getFullName()} }, "timing": { "sent": window.API.Service.getTime(), "edited": null }, "text": content, "read": false, "attachments": [], "_tuid": Math.ceil(performance.now()) }; } _newReplyMessage(content = "...") { return { "sender": { "link": {$correspondent->getURL()}, "avatar": {$correspondent->getAvatarUrl()}, "name": {$correspondent->getFullName()} }, "timing": { "sent": window.API.Service.getTime(), "edited": null }, "text": content, "read": true, "_tuid": Math.ceil(performance.now()) }; } newMessage(content) { let msg = this._newSelfMessage(content); this.addMessage(msg); return msg._tuid; } newReply(content) { let msg = this._newReplyMessage(content); this.addMessage(msg); return msg._tuid; } newReplies(replies) { replies.forEach(this.newReply); } sendMessage(content) { console.debug("New outcoming message. Pushing preview to local stack."); let tempId = this.newMessage(content); let msgData = new FormData(); msgData.set("content", content); msgData.set("hash", {$csrfToken}); let that = this; let xhr = new XMLHttpRequest(); xhr.open("POST", "/im/api/messages" + {$correspondent->getId()} + "/create.json", true); xhr.onreadystatechange = (function() { if(this.readyState !== 4) return; else if(this.status !== 202) { console.error("Message was not sent."); that._patchMessage(tempId, { sender: { avatar: "/assets/packages/static/openvk/img/oof.apng", id: -1, link: "/support/manpages/messages/not-delivered.html", name: "Сообщение не доставлено" }, text: "При отправке этого сообщения произошла ошибка общего характера...", timing: { edited: null, sent: "???" }, uuid: -4096 }); return; } console.debug("Message sent, updating view."); that._patchMessage(tempId, JSON.parse(xhr.responseText)); }); xhr.send(msgData); console.debug("Message sent, awaiting response."); } } window.Msg = new Messenger([]); </script> {/block}