let { punycode } = await import(`./punycode.js?r=${revision}`); export class ui { constructor(parent) { this.parent = parent; this.inThePast = false; this.browser = this.getBrowser(); this.root = document.querySelector(':root'); this.css = getComputedStyle($("html")[0]); this.version = $("body").data("version"); this.updateAvailable = false; this.emojiCategories = { "Search": [], "People": ["Smileys & Emotion", "People & Body"], "Nature": ["Animals & Nature"], "Food": ["Food & Drink"], "Activities": ["Activities"], "Travel": ["Travel & Places"], "Objects": ["Objects"], "Symbols": ["Symbols"], "Flags": ["Flags"] } if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { $("body").addClass("desktop"); } this.preloadIcons(); } async gotoMessage(message) { let messageExists = $(`.messageRow[data-id=${message}]`).length; if (messageExists) { this.scrollToMessage(message); } else { let msg = await this.parent.getMessage(message); if (msg.message) { this.setInThePast(true); this.clear("messages"); this.messagesLoading(true); let options = { at: message } this.parent.getMessages(options); } else { return; } } setTimeout(() => { this.highlightMessage(message); }, 250); } setInThePast(bool) { if (bool) { this.inThePast = true; $("#jumpToPresent").removeClass("hidden"); } else { this.inThePast = false; $("#jumpToPresent").addClass("hidden"); } } backInPresent(body) { let lastMessage = $("#messages > .messageRow[data-id]").last(); let lastMessageID = lastMessage.data("id"); if (body.after && lastMessageID == body.latestMessage) { this.setInThePast(false); } } fetchNewMessages() { let lastMessage = $("#messages > .messageRow[data-id]").last(); let lastMessageID = lastMessage.data("id"); let lastMessageTime = lastMessage.data("time"); if (lastMessageID) { let options = { after: lastMessageID } this.parent.getMessages(options); } } getVariables() { const variables = Array.from(document.styleSheets) .filter(styleSheet => { try { return styleSheet.cssRules; } catch(e) { console.warn(e); } }) .map(styleSheet => Array.from(styleSheet.cssRules)) .flat() .filter(cssRule => cssRule.selectorText === ':root') .map(cssRule => cssRule.cssText.split('{')[1].split('}')[0].trim().split(';')) .flat() .filter(text => text !== "") .map(text => text.split(':')) .map(parts => ({key: parts[0].trim(), value: parts[1].trim() })) ; return variables; } preloadIcons() { let variables = this.getVariables(); $.each(variables, (k, data) => { let {key,value} = data; if (key.substring(key.length - 4) == "Icon") { let match = value.match(/url\((?.+)\)/); let asset = match.groups.asset.replaceAll("\\", ""); let link = `https://${this.parent.host}${asset}`; new Image().src = link; } }); } setData(e, attr, val) { $(e).data(attr, val); $(e).attr(`data-${attr}`, val); } setupSync() { let link = this.parent.syncLink(); let popover = $(".popover[data-name=syncSession]"); let qr = popover.find("#qrcode"); let input = popover.find("input[name=syncLink]"); qr.empty(); qr.html(''); input.val(link); if (!qr.find("canvas").length) { let qrcode = new QRCode(qr[0], { text: link, width: 250, height: 250, colorLight: this.css.getPropertyValue("--tertiaryBackground"), colorDark: this.css.getPropertyValue("--primaryForeground"), correctLevel: QRCode.CorrectLevel.L }); } qr.find("img").attr("draggable", "false"); } copyToClipboard(button) { let field = button.parent().find("input")[0]; field.select(); field.setSelectionRange(0, 99999); navigator.clipboard.writeText(field.value); field.setSelectionRange(0, 0); button.addClass("copied"); setTimeout(() => { button.removeClass("copied"); }, 1000); } sizeInput() { let input = $("textarea#message"); input.css("height", "1px"); let height = input[0].scrollHeight; if (height > 200) { height = 200; } input.css("height", height+"px"); } wordForPosition(text, position) { let index = text.indexOf(position); let preText = text.substr(0, position); if (preText.indexOf(" ") > 0) { let words = preText.split(" "); return (words.length - 1) } else { return 0; } } updateCompletions() { let options = []; let closeIfNeeded = false; let input = $("textarea#message"); let text = input.val(); let words = text.split(" "); let position = input[0].selectionStart; let word = words[this.wordForPosition(text, position)]; if (word.length > 1) { $("#completions .body .list").empty(); switch (word[0]) { case "@": $("#completions .title").html("Users"); let users = this.parent.usersForConversation(this.parent.conversation); options = users.filter(user => { let match = Array.from(this.toUnicode(user.domain)).slice(0, Array.from(word).length - 1).join("").toLowerCase(); let search = word.toLowerCase(); return !user.locked && "@"+match === search; }).slice(0, 10); $.each(options, (k, option) => { let row = $(`#users .users tr[data-id="${option.id}"]`).clone(); $("#completions .body .list").append(row); }); closeIfNeeded = true; break; case "#": $("#completions .title").html("Channels"); let channels = this.parent.channels; options = channels.filter(channel => { let match = Array.from(this.toUnicode(channel.name)).slice(0, Array.from(word).length - 1).join("").toLowerCase(); let search = word.toLowerCase(); return "#"+match === search; }).slice(0, 10); $.each(options, (k, option) => { let row = $(`#conversations .channels tr[data-id="${option.id}"]`).clone(); $("#completions .body .list").append(row); }); closeIfNeeded = true; break; } } else { closeIfNeeded = true; } if (options.length) { $("#completions .list tr").first().addClass("active"); this.popover("completions"); } else { closeIfNeeded = true; } if (closeIfNeeded) { this.close(); } } updateSelectedCompletion(key) { if (!$("#completions.shown").length) { return; } let selected = $("#completions tr.active"); selected.removeClass("active"); let select; let current = selected[0]; switch (key) { case 38: if (current.previousSibling) { select = current.previousSibling; } else { select = $("#completions .list").children().last(); } break; case 40: if (current.nextSibling) { select = current.nextSibling; } else { select = $("#completions .list").children().first(); } break; } $(select).addClass("active"); } domains(array) { let domains = this.parent.sorted(array, "domain"); switch (this.parent.page) { case "chat": $(".header .domains select").empty(); $.each(domains, (k, i) => { let verify = ""; if (i.locked) { verify = " (Unverified)"; } $(".header .domains select").append(``); }); $(".header .domains select").append(``); $(".header .domains select").append(``); $(".header .domains select").val(this.parent.domain); break; case "id": case "invite": if (!this.parent.varoLoaded() || this.parent.mobile) { $(".varo").addClass("hidden"); } $(".section#manageDomains .domains").empty(); $.each(domains, (k, i) => { let html = $(`
${this.toUnicode(i.domain)}
`); if (i.locked) { html.find(".actions").prepend(``); } $(".section#manageDomains .domains").append(html); }); if (domains.length) { $(".section#manageDomains #startChatting").removeClass("hidden"); } if (this.parent.page !== "invite") { this.showSection("manageDomains"); } break; } } removeDomain(id) { $(`.section#manageDomains .domains .domain[data-id=${id}]`).remove(); } stakedDomains(domains) { $(".section#addDomain select[name=tld]").empty(); if (this.parent.page == "invite") { $(".section[id=addDomain]").find(".button[data-action=addDomain]").addClass("hidden"); $(".section[id=addDomain]").find(".or").addClass("hidden"); let info = this.parent.stakedForName(this.parent.data); if (info) { $(".section#addDomain select[name=tld]").append(``); } else { $(".section#addDomain").empty(); $(".section#addDomain").append(`
This invite code isn't valid.
`); } this.showSection("addDomain"); } else { $.each(domains, (k, i) => { $(".section#addDomain select[name=tld]").append(``); }); } } showSection(section) { $(".section").find("input").val(''); $(".section").removeClass("shown"); $(`.section#${section}`).addClass("shown"); } updateDomainSelect() { $(".header .domains select").val(this.parent.domain); } avatarFallback(string) { let fallback; if (!this.parent.regex(/[a-zA-Z0-9]/g, string[0]).length) { $.each(sortedEmojis, (k, e) => { if (string.substring(0, e.emoji.length) == e.emoji) { fallback = e.emoji; return false; } }); } if (!fallback) { fallback = String.fromCodePoint(string.codePointAt(0)).toUpperCase(); } return fallback; } toUnicode(name) { let puny = punycode.ToUnicode(name); let zwj = nameToUnicode(puny); return zwj; } conversation(tab, data) { let name = data.name; let fallback = "#"; let user,domain; if (tab == "pms") { user = this.parent.otherUser(data.users); domain = this.parent.userForID(user); name = domain.domain; } name = this.toUnicode(name); if (tab == "pms") { fallback = this.avatarFallback(name); } let html = $(`
${fallback.toUpperCase()}
${name} `); if (tab == "pms") { let active = ""; if (domain.active) { active = " active"; } let avatar = $(`
`); html.find(".avatar").prepend(avatar); } $(`#conversations .${tab} table`).append(html); this.updateAvatars(); } updateConversations() { let domain = this.parent.userForID(this.parent.domain); $.each($("#conversations .sections tr"), (k, c) => { let id = $(c).data("id"); let data; if (this.parent.isChannel(id)) { data = this.parent.channelForID(id); if (!data.public && data.name !== domain.tld) { $(c).addClass("locked"); } } else { data = this.parent.pmForID(id); if (data.activity) { $(c).removeClass("hidden"); } else { $(c).addClass("hidden"); } let otherUser = this.parent.otherUser(data.users); let otherUserData = this.parent.userForID(otherUser); if (otherUserData.active) { $(c).find(".avatar .status").addClass("active"); } else { $(c).find(".avatar .status").removeClass("active"); } if (otherUserData.locked || otherUserData.deleted) { $(c).addClass("locked"); } else if ($(c).hasClass("locked")) { $(c).removeClass("locked"); if (this.parent.conversation == id) { this.parent.changeConversation(id); } } } if (data.activity >= this.parent.firstLaunch) { if (data.id !== this.parent.conversation) { this.markUnread(data.id, true); } } if (this.parent.seen[data.id]) { if (this.parent.seen[data.id] >= data.activity) { this.markUnread(data.id, false); } } }); } conversationStatus() { let domain = this.parent.userForID(this.parent.domain); if (domain.locked) { return "verify"; } else if ($(`#conversations tr[data-id=${this.parent.conversation}]`).hasClass("locked")) { if (this.parent.isChannel(this.parent.conversation)) { return "permissions"; } else { let data = this.parent.pmForID(this.parent.conversation); let otherUser = this.parent.otherUser(data.users); let otherUserData = this.parent.userForID(otherUser); if (otherUserData.locked || otherUserData.deleted) { return "user"; } } } return true; } updateInputBar() { $("body").removeClass("unverified"); let status = this.conversationStatus(); switch (status) { case "user": case "verify": case "permissions": $(".inputHolder").addClass("locked"); break; default: $(".inputHolder").removeClass("locked"); break; } switch (status) { case "user": $(".inputHolder .locked").html("This user isn't available."); break; case "verify": $("body").addClass("unverified"); $(".inputHolder .locked").html("Re-verify your name to chat."); break; case "permissions": $(".inputHolder .locked").html("You don't have permissions to chat here."); break; } } setConversationTab() { $("#conversations .tabs .tab").removeClass("active"); $(`#conversations .tabs .tab[data-tab=${this.parent.tab}]`).addClass("active"); $(`#conversations .sections .section`).removeClass("shown"); $(`#conversations .sections .section.${this.parent.tab}`).addClass("shown"); } setActiveConversation() { this.setData($("#holder"), "type", this.parent.tab); $("#conversations tr").removeClass("active"); $(`#conversations tr[data-id=${this.parent.conversation}]`).addClass("active"); $(".messageHeader table").empty(); $(".messageHeader table").append($(`#conversations tr.active`).clone()); $(".messageHeader table tr").removeClass("hidden"); this.setPinnedMessage(); this.updateAvatars(); } async setPinnedMessage() { $(".pinnedMessage .delete").addClass("hidden"); $(".pinnedMessage").removeClass("shown"); let output = new Promise(resolve => { if (!this.parent.isChannel(this.parent.conversation)) { resolve(); } let channelData = this.parent.channelForID(this.parent.conversation); if (channelData.pinned) { this.parent.getMessage(channelData.pinned).then(r => { if (r.message) { let message; let decoded = he.decode(he.decode(r.message)); try { let msg = JSON.parse(decoded); message = msg.message; } catch { message = decoded; } resolve(message); } else { resolve(); } }); } else { resolve(); } }); let message = await output; if (message && message.length) { $(".pinnedMessage .message").html(message); if (this.parent.isChannel(this.parent.conversation)) { let me = this.parent.userForID(this.parent.domain); let channel = this.parent.channelForID(this.parent.conversation); if (this.isAdmin(channel, me)) { $(".pinnedMessage .delete").removeClass("hidden"); } } this.linkify($(".pinnedMessage .message")); $(".pinnedMessage").addClass("shown"); } } isAdmin(channel, user) { if ((channel.tldadmin && channel.name == user.domain) || user.admin || channel.admins.includes(user.id)) { return true; } return false; } markUnread(conversation, bool) { if (bool) { if (!$(`#conversations tr[data-id=${conversation}]`).hasClass("locked") && conversation !== this.parent.conversation) { $(`#conversations tr[data-id=${conversation}]`).addClass("unread"); } } else { $(`#conversations tr[data-id=${conversation}]`).removeClass("unread"); } this.updateNotifications(); } markMention(conversation, bool) { if (bool) { if (!$(`#conversations tr[data-id=${conversation}]`).hasClass("locked") && conversation !== this.parent.conversation) { $(`#conversations tr[data-id=${conversation}]`).addClass("mentions"); } } else { $(`#conversations tr[data-id=${conversation}]`).removeClass("mentions"); } this.updateNotifications(); } updateNotifications() { if ($("#conversations .pms tr.unread").length) { $("#conversations .tab[data-tab=pms]").addClass("notification"); } else { $("#conversations .tab[data-tab=pms]").removeClass("notification"); } if ($("#conversations .channels tr.mentions").length) { $("#conversations .tab[data-tab=channels]").addClass("notification"); } else { $("#conversations .tab[data-tab=channels]").removeClass("notification"); } if ($("#conversations .tab.notification").length) { $(".header .left").addClass("notification"); } else { $(".header .left").removeClass("notification"); } } setLoading(bool) { if (bool) { $(".connecting").removeClass("hidden"); } else { $(".connecting").addClass("hidden"); } } clear(type) { switch (type) { case "channels": case "pms": $(`#conversations .${type} table`).empty(); break; case "messages": $("#messages").empty(); $(".needSLD").remove(); break; case "input": $("textarea#message").val(''); break; } } setGatedView(data) { let channel = this.parent.channelForID(this.parent.conversation); let message = `#${this.toUnicode(channel.name)} is a private community for owners of a .${this.toUnicode(channel.name)} only.`; let names = this.parent.domains.filter(d => { return d.tld == channel.name; }); let html = $(`
${message}
`); switch (data.resolution) { case "purchase": html.append($(`
Purchase a .${this.toUnicode(channel.name)}
`)); break; case "create": html.append($(`
Create a free .${this.toUnicode(channel.name)}
`)); break; default: break; } names.forEach(name => { html.append($(`
Switch to ${this.toUnicode(name.domain)}
`)); }); $("#messageHolder").append(html); } emptyUserList() { $("#users #count").html(''); $("#users .users table").empty(); } setUserList() { if (!this.parent.isChannel(this.parent.conversation)) { return; } if ($("#users .group.searching.shown").length) { return; } $("#users .users table").empty(); let users = this.parent.usersForConversation(this.parent.conversation); let sorted = this.parent.sorted(users, "domain"); let active = sorted.filter(u => { return u.active; }); let inactive = sorted.filter(u => { return !u.active; }); $.each(active, (k, user) => { this.addUserToUserlist(user); }); $.each(inactive, (k, user) => { this.addUserToUserlist(user); }); this.updateAvatars(); $("#users #count").html(users.length.toLocaleString("en-US")); } updateUserList() { if (!this.parent.isChannel(this.parent.conversation)) { return; } let users = this.parent.usersForConversation(this.parent.conversation); let sorted = this.parent.sorted(users, "domain"); let active = sorted.filter(u => { return u.active; }); let inactive = sorted.filter(u => { return !u.active; }); $.each(users, (k, user) => { let userEl = $(`#users .user[data-id=${user.id}]`); let isActive = !userEl.hasClass("inactive"); if (user.active) { if (!isActive) { let position = active.indexOf(user) - 1; let before = $($("#users .user").get(position)); userEl.remove(); userEl.removeClass("inactive"); userEl.find(".avatar .status").addClass("active"); if (position < 0) { $("#users table").prepend(userEl); } else { userEl.insertAfter(before); } } } else { if (isActive) { let position = inactive.indexOf(user) + active.length; let before = $($("#users .user").get(position)); userEl.remove(); userEl.addClass("inactive"); userEl.find(".avatar .status").removeClass("active"); if (position < 0) { $("#users table").prepend(userEl); } else { userEl.insertAfter(before); } } } }); } addUserToUserlist(user) { let name = this.toUnicode(user.domain); let html = $(` ${this.avatar("td", user.id, user.domain, true)} ${name} `); if (!user.active) { html.addClass("inactive"); } $("#users .users table").append(html); } replaceSpecialMessage(message) { if (message.substring(0, 12) == "ACTION ") { return message.substring(12); } return message; } messageSummary(message) { let decoded = message; try { decoded = JSON.parse(message); } catch {} if (decoded.payment) { return "sent a payment"; } else if (decoded.attachment) { return "sent an attachment"; } else if (decoded.action) { return decoded.action; } else if (decoded.message) { return decoded.message; } return decoded; } async insertMessages(data, decrypt=false) { let messages = data.messages; for (let i in messages) { let message = messages[i]; if ($(`.messageRow[data-id=${message.id}]`).length) { return; } if (decrypt) { await this.parent.decryptMessageIfNeeded(this.parent.conversation, message).then(decrypted => { message.message = decrypted[0]; if (message.p_message) { message.p_message = decrypted[1]; } }); } if (message.p_message) { message.p_message = this.messageSummary(message.p_message); } let user = this.parent.userForID(message.user).domain; let messageBody = he.decode(he.decode(message.message)); messageBody = he.encode(messageBody); let isAction = false; if (messageBody.substring(0, 12) == "ACTION ") { messageBody = messageBody.substring(12); isAction = true; } let isNotice = false; let hasEffect = false; let hasStyle = false; let html = $(`
${this.avatar("div", message.user, user)}
${this.toUnicode(user)}
`); let decoded = he.decode(messageBody); try { let json = JSON.parse(he.decode(decoded)); if (json.hnschat) { if (json.attachment) { let link; try { let url = new URL(json.attachment); switch (url.host) { case "media.tenor.com": case "api.zora.co": link = json.attachment; break; } } catch { link = `https://${window.location.host}/uploads/${json.attachment}`; } let image = $(` `); html.find(".message").addClass("image"); html.find(".message .body").empty(); html.find(".message .body").append(image); } else if (json.payment) { let link = `https://niami.io/tx/${json.payment}`; let image = $(`
${this.parent.rtrim(json.amount.toLocaleString("en-US", { minimumFractionDigits: 6 }), "0")}
`); html.find(".message").addClass("image payment"); html.find(".message .body").empty(); html.find(".message .body").append(image); } else if (json.action) { messageBody = he.encode(json.action); html.find(".message .body").html(messageBody); isAction = true; } else if (json.message) { messageBody = json.message.toString(); messageBody = he.encode(messageBody); html.find(".message .body").html(messageBody); } if (json.effect) { hasEffect = json.effect; } if (json.style) { hasStyle = json.style; } } } catch (e) { html.find(".message .body").html(messageBody); } if (isAction) { html.addClass("action"); } let firstThree = Array.from(messageBody.toString()).slice(0, 3); let isEmojis = true; $.each(firstThree, (k, char) => { if (!this.isCharEmoji(char)) { isEmojis = false; return false; } }); if (isEmojis) { html.addClass("emojis"); } let isDice = true; let chars = Array.from(messageBody.toString()); $.each(chars, (k, char) => { if (!["⚀","⚁","⚂","⚃","⚄","⚅"].includes(char)) { isDice = false; return false; } }); if (isDice) { html.addClass("emojis dice"); } if (hasStyle) { this.setData(html, "style", hasStyle); } let messageTime = new Date(message.time * 1000).format(this.parent.timeFormat); let actions = $(`
${messageTime}
`) if (message.user == this.parent.domain) { html.addClass("self"); html.find(".holder.msg").prepend(actions); } else { html.find(".holder.msg").append(actions); } if (this.parent.mentionsMe(message.message)) { html.addClass("mention"); } if (message.notice) { html.addClass("notice"); } if (message.replying) { let reply = $(`
`); if (message.p_user && message.p_message) { let messageReplyBody = ""; if (message.p_message.length) { messageReplyBody = he.encode(message.p_message); messageReplyBody = this.replaceSpecialMessage(messageReplyBody); } let p_user = this.parent.userForID(message.p_user).domain; this.setData(reply.find(".user"), "id", message.p_user); reply.find(".user").html(this.toUnicode(p_user)); reply.find(".body").html(messageReplyBody); if (message.p_user == this.parent.domain) { html.addClass("mention"); } } else { reply.find(".user").remove(); reply.find(".body").html("Original message was deleted."); } html.addClass("replying"); html.prepend(reply); } if (hasEffect) { let effect = $(``); html.find("> .contents").append(effect); } if (!message.reactions) { message.reactions = JSON.stringify({}); } if (data.before || data.at) { this.parent.messages.unshift(message); $("#messages").prepend(html); } else { this.parent.messages.push(message); $("#messages").append(html); this.fixScroll(); } if (!decrypt && hasEffect) { this.handleEffect(hasEffect); } this.updateReactions(message.id); this.stylizeMessage(html); this.updateAvatars(); } this.parent.loadingMessages = false; this.markEmptyIfNeeded(); } async handleEffect(effect) { let done = new Promise(resolve => { switch (effect) { case "confetti": startConfetti(); setTimeout(() => { stopConfetti(); resolve(); }, 3000); break; } }); return await done; } markEmptyIfNeeded() { if (!$(".messageRow").length && !$(".needSLD").length) { $("#messages").addClass("empty"); } else { $("#messages").removeClass("empty"); } } deleteMessage(id) { let message = $(`.messageRow[data-id=${id}]`); if (message.length) { let previous = message.prev(); let next = message.next(); message.remove(); if (previous.length) { this.stylizeMessage(previous); } if (next.length) { this.stylizeMessage(next); } if (this.parent.isChannel(this.parent.conversation)) { let channel = this.parent.channelForID(this.parent.conversation); if (id == channel.pinned) { channel.pinned = null; this.setPinnedMessage(); } } } } updateReactions(id) { let message = $(`.messageRow[data-id=${id}]`); message.find(".reactions").empty(); let reactions = this.parent.messages.filter(m => { return m.id == id; })[0].reactions; if (reactions) { let json = JSON.parse(reactions); if (Object.keys(json).length) { $.each(json, (r, u) => { let users = this.userString(u); let reaction = $(`
${r}
${u.length}
`); if (u.includes(this.parent.domain)) { reaction.addClass("self"); } message.find(".reactions").append(reaction); }); } } } moveConversationToTop(conversation) { let div = $(`#conversations tr[data-id=${conversation}]`); let parent = div.parent(); div.remove(); parent.prepend(div); } userString(array) { let output; let users = [...array]; $.each(users, (k, u) => { let name = this.parent.userForID(u).domain; users[k] = name; }); if (users.length == 1) { output = users[0]; } else { let last = users.pop(); let others = users.join(", "); output = `${others} and ${last}`; } return output; } isCharEmoji(char) { if (char == "\u200d") { return true; } let match = emojis.filter(emoji => { return emoji.emoji == char || emoji.emoji.replace("\ufe0f", "") == char; }); if (match.length) { return true; } return false; } stylizeMessage(message) { let messageHolder = $("#messages"); let firstMessage = $("#messages > .messageRow[data-id]").first(); let firstMessageID = firstMessage.data("id"); let lastMessage = $("#messages > .messageRow[data-id]").last(); let lastMessageID = lastMessage.data("id"); let messageID = message.data("id"); let messageTime = message.data("time"); let messageDate = new Date(messageTime * 1000).format(this.parent.dateFormat); let contents = message.find(".contents"); let messageSender = message.data("sender"); let messageUser; let previousMessage = message.prev(); let previousMessageTime = previousMessage.data("time"); let previousMessageDate = new Date(previousMessageTime * 1000).format(this.parent.dateFormat); let previousMessageSender = previousMessage.data("sender"); let nextMessage = message.next(); let nextMessageTime = nextMessage.data("time"); let nextMessageDate = new Date(nextMessageTime * 1000).format(this.parent.dateFormat); let nextMessageSender = nextMessage.data("sender"); let nextMessageUser; let isFirst = false; let isLast = false; let isReply = false; let isInformational = false; let isAction = false; let isDate = false; let before = false; let addUser = false; let addNextUser = false; let removeUser = false; let removeNextUser = false; let prependDate = false; let appendDate = false; if (firstMessageID == messageID) { isFirst = true; } if (lastMessageID == messageID) { isLast = true; } if (message.hasClass("replying")) { isReply = true; } if (message.hasClass("informational")) { isInformational = true; } if (message.hasClass("date")) { isDate = true; } if (message.hasClass("action")) { isAction = true; } if (nextMessage.length) { before = true; } if (isFirst || isReply) { addUser = true; } if (!before) { if (!isDate && previousMessage.length && messageDate !== previousMessageDate && !previousMessage.hasClass("date")) { prependDate = true; } } else { if (!isDate && nextMessage.length && messageDate !== nextMessageDate && !nextMessage.hasClass("date")) { appendDate = true; } } if (isDate) { previousMessage.addClass("last"); message.addClass("first last"); if (nextMessage.length) { addNextUser = true; } } else { let timeDifference; messageUser = this.parent.userForID(messageSender).domain; if (before) { message.addClass("first"); } else { message.addClass("last"); } if (isFirst || isReply) { message.addClass("first"); } if (isLast) { message.addClass("last"); } if (previousMessage.length) { timeDifference = messageTime - previousMessageTime; if (timeDifference >= 60 || previousMessageSender !== messageSender) { previousMessage.addClass("last"); message.addClass("first"); addUser = true; } else if (!isReply) { removeUser = true; } } if (timeDifference < 60 && previousMessageSender == messageSender && !message.hasClass("replying")) { previousMessage.removeClass("last"); } if (previousMessageSender !== messageSender) { previousMessage.addClass("last"); } if (nextMessage.length) { timeDifference = nextMessageTime - messageTime; if (timeDifference > 60) { nextMessage.addClass("first"); addNextUser = true; } if (nextMessage.hasClass("first")) { message.addClass("last"); } } if (timeDifference < 60 && nextMessageSender == messageSender && !nextMessage.hasClass("replying")) { removeNextUser = true; } if (addUser) { if (!contents.find(".user").length) { //let user = $('
'); //user.html(messageUser); //contents.prepend(user); //contents.prepend(messageAvatar(messageSender, messageUser)); } } if (removeUser) { message.removeClass("first"); //message.find(".contents .user").remove(); //message.find(".contents .avatar").remove(); } if (removeNextUser) { message.removeClass("last"); nextMessage.removeClass("first"); //nextMessage.find(".contents .user").remove(); //nextMessage.find(".contents .avatar").remove(); } if (prependDate) { let infoRow = $('
'); infoRow.html(messageDate); infoRow.insertBefore(message); this.stylizeMessage(infoRow); } if (appendDate) { let infoRow = $('
'); infoRow.html(nextMessageDate); infoRow.insertAfter(message); this.stylizeMessage(infoRow); } } if (addNextUser) { if (!nextMessage.find(".contents .user").length) { //nextMessageUser = this.parent.userForID(nextMessageSender).domain; //let user = $('
'); //user.html(nextMessageUser); //nextMessage.addClass("first"); //nextMessage.find(".contents").prepend(user); //nextMessage.find(".contents").prepend(messageAvatar(nextMessageSender, nextMessageUser)); } } let newLastMessage = $(".messageRow").last(); if (newLastMessage.hasClass("informational date")) { newLastMessage.remove(); } //this.codify(message.find(".contents .body")); this.linkify(message.find(".contents .body")); this.updateAvatars(); } codify(elements) { $.each(elements, (k, e) => { e = $(e); let output = e.html(); while (this.parent.regex(/\`{3}(?[.\s\S]+?)\`{3}/gm, output).length) { let matches = this.parent.regex(/\`{3}(?[.\s\S]+?)\`{3}/gm, output); if (matches.length) { let result = matches[0]; let full = result[0]; let code = result.groups.code; let start = result.index; let end = (start + full.length); let replace = `
${code}
`; output = this.parent.replaceRange(output, start, end, replace); } } while (this.parent.regex(/\`(?[.\s\S]+?)\`/gm, output).length) { let matches = this.parent.regex(/\`(?[.\s\S]+?)\`/gm, output); if (matches.length) { let result = matches[0]; let full = result[0]; let code = result.groups.code; let start = result.index; let end = (start + full.length); let replace = `${code}`; output = this.parent.replaceRange(output, start, end, replace); } } e.html(output); }); } linkify(elements) { $.each(elements, (k, e) => { e = $(e); let output = e.html(); let links = anchorme.list(output).reverse(); $.each(links, (k, link) => { let href = link.string; if (link.isEmail) { href = `mailto:${href}`; } else if (link.isURL && href.substring(0, 8) !== "https://" && href.substring(0, 7) !== "http://") { href = `http://${href}`; } let replace = `${link.string}` output = this.parent.replaceRange(output, link.start, link.end, replace); }); let mentions = this.usersInMessage(output); $.each(mentions, (k, mention) => { let id = mention.groups.name; if (id == this.parent.domain) { e.closest(".messageRow").addClass("mention"); } }); output = this.replaceIds(output); e.html(output); this.expandLinks(e); }); } expandLinks(message) { if (message.hasClass("expanded")) { return; } message.addClass("expanded"); let links = message.find("a.inline"); if (links.length) { if (!message.parent().hasClass("message")) { return; } let link = links[0].href; let embed = this.shouldInlineLink(link); if (embed) { let div; switch (embed) { case "image": div = $(`
`); break; case "video": div = $(`
`); break; } if (div) { message.closest(".messageRow").find(".linkHolder").append(div); this.fixScroll(); } } else { let data = { action: "getMetaTags", url: link } if (link.substring(0, 7) == "mailto:") { return; } this.parent.api(data).then(r => { let div = $(`
`); if (r.tags) { let t = r.tags; if (t.title) { div.find(".info").append(`
${t.title}
`); if (t.description) { div.find(".info").append(`
${t.description}
`); } /* if (t.video) { div.find(".preview").prepend(`