diff --git a/README.md b/README.md index 0cdd05b..626b132 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ The source code may be updated past the latest released version, so don't be sur If there are any issues, questions, or feature requests at all, don't hesitate to create an issue or pull request here, or email me at contact@ianwelker.com. I may not run into all issues that could possibly come up, so I would really appreciate any issues you let me know about. ### Acknowledged issues: +- Creating new conversations does not work on iOS 14+. This is being worked on. - Although typing indicators do appear when the other party starts typing, they don't always disappear when they stop typing. This is also being worked on. ### To file an issue: @@ -86,7 +87,7 @@ Please include the following information: Also, if the app did not crash on startup, but rather crashed after it was already up and running, I would appreciate if you could do the following: - Install the package 'oslog' from your package manager - ssh into your device and run (as root): `oslog --debug | grep -i -e "SMServer_app" -e "mryipc"`; do not redirect the output into a file. - - Enable debug on the app, then kill the app in app switcher + - Enable debug on the app, then hit the purple 'refresh' button in the bottom left of the main view. - Start the app and let it reach the error point - Manually copy the output from the above command (as much as you can get) into a text file. - Email me the file at contact@ianwelker.com. This file may have sensitive information, such as contact phone numbers, so it wouldn't be smart to upload it to a public site. Feel free to filter out (with something like regex or by hand) the sensitive information. diff --git a/docs/API.md b/docs/API.md index fd93985..b0361f3 100644 --- a/docs/API.md +++ b/docs/API.md @@ -15,6 +15,7 @@ All requests to `/requests` return JSON information. Retrieves the most recent $num messages to or from $person, offset by $offset. - person: parameter is necessary, and value is consequential; must be chat_identifier of conversation. chat_identifier will be the email address or phone number of an individual, or 'chat' + $arbitrary_number for a group chat. chat_identifiers for group chats and email addresses must be exact, and phone numbers must be in the form of '+\\\'. e.g. "+16378269173". Using parentheses or dashes will mess it up and return nothing. +$\qquad$ As of version 0.5.4, you may also send multiple addresses to this parameter, separated by single commas, and it will return a merged text list with all of the texts from the listed addresses included. This can be useful if you'd like to treat multiple conversations as one, such as if you have multiple conversations for talking with one person. - num: Parameter is not necessary, but value is consequential. The value of this parameter must be an integer, and will be the number of most recent messages that are returned from the app. If it is 0, it will return all the messages to or from this person, and if it is not specified, it will use the default number of messages on the app, which is currently 100 at the time of writing this. @@ -23,10 +24,11 @@ Retrieves the most recent $num messages to or from $person, offset by $offset. - read: Parameter is not necessary, but value is consequential. The value of this parameter must be a string, either `true` or `false`. If it is `true`, or the parameter is not included but the 'mark conversation as read when viewed on web interface' option is checked in the app's settings, the conversation whose messages are being requested will be marked as read on the host device. Example queries: -- /requests?person=chat192370112946&num=500 +- /requests?person=chat192370112946281736&num=500 - /requests?person=+15202621138 - /requests?person=email@icloud.com&num=50&offset=100&read=false - /requests?person=person@gmail.com&offset=200 +- /requests?person=email@icloud.com,+15202621138,person@gmail.com&num=100&read=true ## `chat`, `num_chats`, `chats_offset` @@ -47,11 +49,12 @@ Example queries: Retrieves the contact name that accompanies chat_identifier $name -- name: Parameter is necessary, and value is consequential. Value must be the chat_identifier for the contact whose name you want. It can get the name if given an email address or phone number of an individual, but it cannot get a contact name for a group chat, since none such exist. Email must be given in the regular format, and phone number must be given in the format that the above 'person' section specifies. +- name: Parameter is necessary, and value is consequential. Value must be the chat_identifier for the contact whose name you want. It can get the name if given an email address or phone number of an individual, or the chat_identifier of a group chat. Email must be given in the regular format, and phone number must be given in the format that the above 'person' section specifies. If there is no name for the email address, phone number, or chat_identifier given, then it will return the given address (in the case of a phone number or email address) or list of recipients (in the case of a group chat chat_identifier) Example queries: - /requests?name=email@icloud.com - /requests?name=+12761938272 +- /requests?name=chat193827462058278283 ## `search`, `case_sensitive`, `bridge_gaps`, `group_by` diff --git a/docs/Changelog.md b/docs/Changelog.md index 81a484b..49f2616 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -2,16 +2,23 @@ 0.5.3 → 0.5.4 - Texts now have prettier bubble tails and no tails when sent close after each other - Texts also have proper spacing when sent far from each other to indicate the gaps in conversation - - Added experimental option to combine numbers that are assigned to the same contact as similar chats - Added option in API to get chats from multiple addresses as if they were one conversation - Revamped settings view + - Completely rewrote javascript that prints list of texts - Sending a text now creates a new time display when it's been more than an hour - Web interface is better at waiting for attachments to fully upload when sending a text before displaying the new text in the web interface - Web interface now only tries to mark a conversation as read when the window becomes focused if it was already unread + - Unread indicators look much cleaner now + - Texts wholly made of emojis now show without background and with a larger font - Fixed issue with z-indices on multiple overlayed reations - Fixed coloring issues with dark and light themes - Fixed spacing issues in text area + - Fixed issue where order of texts would be messed up when reactions were inserted - Fixed issue where conversations would not appear as selected if they were not the top of the list when you sent a text to them + - Fixed issue where textbox wouldn't properly resize when going from 2 lines to 1 + - Fixed issue where some memojis wouldn't load into the web interface + - Fixed issue where sometimes chats without a contact would return an empty name, thus causing more issues + - Fixed issue where sender name wouldn't appear in a group chat after a time display 0.5.2 → 0.5.3 - Added inline audio displays diff --git a/src/SMServer.xcworkspace/xcuserdata/ian.xcuserdatad/UserInterfaceState.xcuserstate b/src/SMServer.xcworkspace/xcuserdata/ian.xcuserdatad/UserInterfaceState.xcuserstate index f52ae4d..fb0c76c 100644 Binary files a/src/SMServer.xcworkspace/xcuserdata/ian.xcuserdatad/UserInterfaceState.xcuserstate and b/src/SMServer.xcworkspace/xcuserdata/ian.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/src/SMServer/ChatDelegate.swift b/src/SMServer/ChatDelegate.swift index b829973..c4f4648 100644 --- a/src/SMServer/ChatDelegate.swift +++ b/src/SMServer/ChatDelegate.swift @@ -9,7 +9,7 @@ final class ChatDelegate { let prefix: String = "SMServer_app: " static let addressBookAddress: String = "/private/var/mobile/Library/AddressBook/AddressBook.sqlitedb" - static let imageStoragePrefix: String = "/private/var/mobile/Library/SMS/Attachments/" + static let imageStoragePrefix: String = "/private/var/mobile/Library/SMS/" static let photoStoragePrefix: String = "/var/mobile/Media/" static let userHomeString: String = "/private/var/mobile/" @@ -147,7 +147,7 @@ final class ChatDelegate { var display_name_array = [[String:String]]() /// Support for group chats - if chat_id.prefix(4) == "chat" && !chat_id.contains("@") && chat_id.count > 20 { + if chat_id.prefix(4) == "chat" && !chat_id.contains("@") && chat_id.count >= 20 { let check_name = selectFromSql(db: sms_db, columns: ["display_name"], table: "chat", condition: "where chat_identifier is \"\(chat_id)\"", num_items: 1) if check_name.count == 0 || check_name[0]["display_name"]?.count == 0 { @@ -171,10 +171,10 @@ final class ChatDelegate { let unc = selectFromSql(db: sms_db, columns: ["uncanonicalized_id"], table: "handle", condition: "WHERE id is \"\(chat_id)\"") guard unc.count > 0, let ret = unc[0]["uncanonicalized_id"] else { - return "" + return chat_id } - return ret + return ret.count == 0 ? chat_id : ret } /// combine first name and last name @@ -276,7 +276,7 @@ final class ChatDelegate { self.log("Getting \(String(num_to_load)) chats") var pinned_chats: [String] = [""] - var combine = UserDefaults.standard.object(forKey: "combine_contacts") as? Bool ?? false + let combine = UserDefaults.standard.object(forKey: "combine_contacts") as? Bool ?? false var return_array = [[String:String]]() if offset == 0 && Float(UIDevice.current.systemVersion) ?? 13.0 >= 14.0 { @@ -297,40 +297,35 @@ final class ChatDelegate { let chats = selectFromSql(db: db, columns: ["m.ROWID", "m.is_read", "m.is_from_me", "m.text", "m.item_type", "m.date_read", "m.date", "m.cache_has_attachments", "c.chat_identifier", "c.display_name", "c.room_name"], table: "chat_message_join j", condition: "inner join message m on j.message_id = m.ROWID inner join chat c on c.ROWID = j.chat_id where j.message_date in (select max(j.message_date) from chat_message_join j inner join chat c on c.ROWID = j.chat_id group by c.chat_identifier) order by j.message_date desc", num_items: num_to_load, offset: offset) - inner: if combine { /// uhhh sketchy times. Seems to not impact performance that much but isn't perfect + inner: if combine { /// This `if` gets all the addresses associated with your contacts, parses them with regex to get all possible `chat_identifier`s, /// then puts them into a nested array. Later on, the conversations check themselves with the nested arrays to find the associated `chat_identifier`s. /// This section is probably not very optimized and probably also inaccurate. That's why it's optional. - addresses = selectFromSql(db: contacts_db, columns: ["c.c16Phone", "c.c17Email"], table: "ABPersonFullTextSearch_content c") - - /// If can reverse, using #"((?<= )[0-9]{5,}\+|(?<= )([0-9]{5,7})(?= )(?!.*\2))"# accurately removes duplicates but is less accurate in other ways - let pattern = #"(?<= )(\+|)[0-9]{5,}(?= )"# - var regex: NSRegularExpression - do { - regex = try NSRegularExpression(pattern: pattern, options: []) - } catch { - self.log("Unable to create regex; not merging contacts", warning: true) - combine = false - break inner - } + addresses = selectFromSql(db: contacts_db, columns: ["c16Phone", "c17Email"], table: "ABPersonFullTextSearch_content") + let identifier_array = selectFromSql(db: db, columns: ["chat_identifier"], table: "chat", condition: "where chat_identifier not regexp \"chat[0-9]{10,}\"") + let identifiers: [String] = identifier_array.map({$0["chat_identifier"] ?? ""}) for i in addresses { - var personal_addresses = i["c.c17Email"]?.split(separator: " ").map({String($0)}) ?? [String]() - let phone = i["c.c16Phone"] ?? "" - let range = NSRange(phone.startIndex..= 5 && (len == j.count || (len == j.count - 1 && j.first == "+"))) { + continue outerfor + } for l in checked_matches { if (l.contains(j)) { continue outerfor } } - checked_matches.append(j) + if identifiers.contains(j) { + checked_matches.append(j) + } } chat_identifiers.append(checked_matches) @@ -538,13 +533,13 @@ final class ChatDelegate { final func getAttachmentFromMessage(mid: String) -> [[String]] { /// This returns the file path for all attachments associated with a certain message with the message_id of $mid - self.log("Gettig attachment for mid \(mid)") + self.log("Getting attachment for mid \(mid)") /// create connection, get attachments information var db = createConnection() if db == nil { return [[String]]() } - let file = selectFromSql(db: db, columns: ["filename", "mime_type", "hide_attachment"], table: "attachment", condition: "WHERE ROWID in (SELECT attachment_id from message_attachment_join WHERE message_id is \(mid))") + let file = selectFromSql(db: db, columns: ["filename", "mime_type"], table: "attachment", condition: "WHERE ROWID in (SELECT attachment_id from message_attachment_join WHERE message_id is \(mid))") var return_val = [[String]]() self.log("attachment file length: \(String(file.count))") diff --git a/src/SMServer/ContentView.swift b/src/SMServer/ContentView.swift index f0341ea..b37cc0d 100644 --- a/src/SMServer/ContentView.swift +++ b/src/SMServer/ContentView.swift @@ -106,11 +106,6 @@ struct ContentView: View { self.log("GET main: " + ip) - /*if self.checkIfAuthenticated(ras: ip) { - res.send(self.main_page) - } else { - res.send(self.gatekeeper_page) - }*/ res.send(self.checkIfAuthenticated(ras: ip) ? self.main_page : self.gatekeeper_page) } diff --git a/src/SMServer/html/chats.html b/src/SMServer/html/chats.html index 07ccc8a..47087f2 100644 --- a/src/SMServer/html/chats.html +++ b/src/SMServer/html/chats.html @@ -48,10 +48,13 @@ socket_address = prefix.split("/")[2].split(":")[0]; } - function timeConverter(t, ts_only = false, apple = true) { + function timeConverter(t, ts_only = false, apple = true, rev = false) { if (apple) var ts = (t / 1000000000) + 978307200; else var ts = t; - if (ts_only) return ts; + if (ts_only) { + if (rev) return (t - 978307200) * 1000000000; + return ts; + } var a = new Date(ts * 1000); var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; @@ -81,8 +84,7 @@ async function fetchFromURL(url) { var ret = ""; await fetch(url).then(response => { - if (!response.ok) - throw new Error("HTTP error " + response.status); + if (!response.ok) throw new Error("HTTP error " + response.status); ret = response.json(); }) .catch(error => { @@ -118,8 +120,8 @@ var classes = "text"; classes += (text.is_from_me === "0" ? " is_from_them" : " is_from_me") + " " + text.service; - t.setAttribute("class", classes); - t.setAttribute("id", text.guid); + t.className = classes; + t.id = text.guid; t.setAttribute("title", timeConverter(text.date)); t.setAttribute("date_read", text.date_read); t.setAttribute("date", text.date); @@ -127,7 +129,7 @@ /// it's a rich link if (text.balloon_bundle_id === "com.apple.messages.URLBalloonProvider" && text.attachment_file !== undefined) { - t.setAttribute("class", t.getAttribute("class") + " noPadding richLink"); + t.className += " noPadding richLink"; var img = document.createElement("img"); var attachment_splits = text.attachment_file.split(":"); @@ -135,28 +137,28 @@ var under = document.createElement("a"); under.href = text.text; - under.setAttribute("class", "richLinkUnder"); + under.className = "richLinkUnder"; if (attachment_splits[1].length > 0) { t.appendChild(img); t.appendChild(document.createElement("br")); } else { under.appendChild(img); - under.setAttribute("class", under.getAttribute("class") + " richLinkSmall"); + under.classList.add("richLinkSmall"); } var desc = document.createElement("div"); - desc.setAttribute("class", "richLinkDescription"); + desc.className = "richLinkDescription"; var title = document.createElement("p"); title.innerText = text.link_title; - title.setAttribute("class", "richLinkTitle"); + title.className = "richLinkTitle"; desc.appendChild(title); if (attachment_splits[1].length > 0) { var subtitle = document.createElement("div"); subtitle.innerText = text.link_subtitle; - subtitle.setAttribute("class", "richLinkSubtitle"); + subtitle.className = "richLinkSubtitle"; desc.appendChild(subtitle); } @@ -164,7 +166,7 @@ if (text.link_type !== undefined && text.link_type.split(".")[0] === "music") { var icon = document.createElement("div"); - icon.setAttribute("class", "richLinkIcon"); + icon.className = "richLinkIcon"; var ii = document.createElement("img"); ii.src = "/data?path=" + attachment_splits[0]; @@ -197,7 +199,7 @@ var link = document.createElement("a"); link.href = prefix + "/data?path=" + files[l].replace(/ /g, "%20"); link.setAttribute("mime_type", types[l]); - link.setAttribute("class", "inlineAttachment"); + link.className = "inlineAttachment"; var icon = ""; @@ -222,7 +224,7 @@ c.innerHTML += link.outerHTML; } if (bigType === "image" || bigType === "video" || bigType === "audio") - c.setAttribute("class", c.getAttribute("class") + " noPadding fullAttachment"); + c.className += " noPadding fullAttachment"; rets.push(c); } } @@ -230,7 +232,7 @@ if (text.text.replace(//g, "").trim().length > 0 || text.subject.length > 0) { /// There's an invisible character between those first two quotes that displays on firefox. This just removes it var subject = document.createElement("div"); - subject.setAttribute("class", "subject"); + subject.className = "subject"; subject.innerText = text.subject; var inner = document.createElement("span"); @@ -240,6 +242,10 @@ t.appendChild(inner); } + var emoji_check = text.text.match(/\p{Extended_Pictographic}/gu); + if (text.subject === "" && emoji_check !== null && emoji_check.join("").length === text.text.length) + t.classList.add("allEmoji"); + rets.push(t); return rets; } else if (Number(text.associated_message_type) < 2006 && Number(text.associated_message_type) > 1999) { @@ -250,7 +256,7 @@ if (original === null || original === undefined) return document.createElement("span"); var reac = document.createElement("span"); - reac.setAttribute("class", "reaction"); + reac.className = "reaction"; reac.setAttribute("reactionType", text.associated_message_type); var icons = ["Love", "Thumbs up", "Thumbs down", "Haha", "!!", "??"]; @@ -261,11 +267,11 @@ reac.innerHTML = icons[Number(text.associated_message_type) - 2000]; var elder = original.parentNode; - if (elder.getAttribute("class").includes("text-area")) { + if (elder.className.includes("text-area")) { var ln = elder.getElementsByClassName("reaction").length; var mg = " -" + String(ln * 2) + "6px;"; mg += "z-index: " + String(9 < (ln + 2) ? 9 : (ln + 2)) + ";"; - if (elder.getAttribute("class").includes("from_them")) { + if (elder.className.includes("from_them")) { reac.setAttribute("style", "left:" + mg); elder.appendChild(reac); } else { @@ -275,14 +281,13 @@ } else { var next = original.nextElementSibling; var copy = original.cloneNode(true); - original.outerHTML = ""; var area = document.createElement("span"); var new_class = "text-area"; - new_class += copy.getAttribute("class").includes("is_from_them") ? " from_them" : " from_me"; - area.setAttribute("class", new_class); + new_class += copy.className.includes("is_from_them") ? " from_them" : " from_me"; + area.className = new_class; - if (copy.getAttribute("class").includes("is_from_them")) { + if (copy.className.includes("is_from_them")) { area.appendChild(copy); area.appendChild(reac); } else { @@ -290,10 +295,7 @@ area.appendChild(copy); } - if (next === null || next === undefined) - document.getElementById("text-content").insertBefore(area, next); - else - document.getElementById("text-content").appendChild(area); + original.outerHTML = area.outerHTML; } } else if (Number(text.associated_message_type) > 2999 && Number(text.associated_message_type) < 3006) { @@ -316,7 +318,7 @@ if (debug) console.log("Getting chat element for " + chat.chat_identifier + ", num: " + num_texts); var b = document.createElement("button"); - if (!searched) b.setAttribute("id", chat.chat_identifier); + if (!searched) b.id = chat.chat_identifier; else b.setAttribute("chat_id", chat.chat_identifier); var i = document.createElement("img"); @@ -325,17 +327,17 @@ var classes = chat.has_unread === "true" ? "unread" : ""; var n = document.createElement("div"); - n.setAttribute("class", "chat-name"); + n.className = "chat-name"; n.innerText = (chat.display_name.length === 0 ? chat.chat_identifier : chat.display_name); if (chat.pinned === "false") { b.appendChild(i); var nonpic = document.createElement("div"); - nonpic.setAttribute("class", "chat-nonpic"); + nonpic.className = "chat-nonpic"; var top = document.createElement("div"); - top.setAttribute("class", "chat-toprow"); + top.className = "chat-toprow"; if (chat.chat_identifier.substring(0, 4) !== "chat" && !chat.chat_identifier.includes("@") && chat.display_name.length !== 0) n.innerHTML += " (" + chat.chat_identifier + ")"; @@ -343,13 +345,13 @@ top.appendChild(n); var d = document.createElement("div"); - d.setAttribute("class", "chat-date"); + d.className = "chat-date"; d.innerText = chat.relative_time; top.appendChild(d); nonpic.appendChild(top); var t = document.createElement("div"); - t.setAttribute("class", "chat-text"); + t.className = "chat-text"; t.innerText = chat.latest_text.replace(//g, ""); nonpic.appendChild(t); b.appendChild(nonpic); @@ -357,12 +359,12 @@ classes += " unpinned"; } else { var t = document.createElement("div"); - t.setAttribute("class", "chat-top"); + t.className = "chat-top"; t.appendChild(i); if (chat.has_unread === "true") { var ct = document.createElement("div"); - ct.setAttribute("class", "chat-text"); + ct.className = "chat-text"; ct.innerText = chat.latest_text.replace(//g, ""); t.appendChild(ct); @@ -373,7 +375,7 @@ classes += " pinned"; } - b.setAttribute("class", classes); + b.className = classes; var get_texts_string = "getTexts(\"" + chat.chat_identifier + "\", " + num_texts + ");"; b.setAttribute("onclick", get_texts_string); @@ -381,65 +383,143 @@ return b; } - async function getLatestText(chat = current_chat_id, read = false) { - if (debug) console.log("Getting latest text for chat " + chat); - var text; + function printListOfTexts(texts, append = true, is_group = false) { + /// If append is true, then it places this list of texts under the existing ones. Else, it places them above the existing ones. + /// the Last element in `texts` will always be the farthest from the texts that were here when this was called + if (debug) console.log("printing List of chats " + String(chats.length) + ", appending: " + append ? "true" : "false"); - let url = prefix + "/requests?person=" + chat + "&num=1&read=" + (read ? "true" : "false"); + var tc = document.getElementById("text-content"); + var et = document.getElementsByClassName("text"); - let textPromise = await fetchFromURL(url); - var text = textPromise.texts[0]; - var content = document.getElementById("text-content"); + var spacer = document.createElement("div"); + spacer.className = "spacer"; - var is_group = chat.substring(0, 4) === "chat"; + for (var i = 0; i < texts.length; i++) { + var fts = getTextElement(texts[i]); + if (fts.length === undefined && fts.outerHTML === "") continue; + var ft = fts.length === undefined ? [fts] : fts; + if (ft.length === 0) continue; + + var lt = texts[i-1]; + var ld = i > 0 ? lt.date : 0; + var notLast = false; + var n = append ? i+1 : i-1; + + var sender_box = undefined; + var add_spacer = false; + var append_time_display = false; + var hourBreak; + var tenMinuteBreak; + + if (i === 0 && et.length > 0) { + if (append) { + tenMinuteBreak = texts[i].date - et[et.length-1].getAttribute("date") >= 600000000000; + var sameSender = (et[et.length-1].className.includes("from_me") && texts[i].is_from_me === "1") || (et[et.length-1].className.includes("from_them") && texts[i].is_from_me === "0"); + if (is_group && sameSender) { + var senders = document.getElementsByClassName("sender"); + sameSender = senders[senders.length-1].firstChild.textContent === texts[i].sender; + } + if (!tenMinuteBreak && sameSender) + et[et.length-1].classList.add("notLast"); + else if (texts[i].date - et[et.length-1].getAttribute("date") >= 3600000000000) + append_time_display = true; + } else { + var hourTillFirst = texts[i].date - et[0].getAttribute("date") >= 3600000000000; + if (!hourTillFirst) document.getElementsByClassName("time-display")[0].outerHTML = ""; + } + } - var senders = document.getElementsByClassName("sender"); + if ((append && i < texts.length - 1) || (!append && i > 0)) { + var brk = append ? 1 : -1; + /// Hopefully gets next text that is not a reaction + for (; (append ? n < texts.length : n >= 0) && texts[n].associated_message_type !== "0"; n += brk) {} - if (is_group && senders.length > 0 && text.is_from_me === "0" && text.sender !== senders[senders.length - 1].firstChild.innerText) { - var name = document.createElement("div"); + tenMinuteBreak = texts[n].date - texts[i].date >= 600000000000; + hourBreak = texts[n].date - texts[i].date >= 3600000000000; + if (!hourBreak && texts[i].is_from_me === texts[n].is_from_me && (!is_group || texts[i].sender === texts[n].sender)) { + if (tenMinuteBreak && !hourBreak) + add_spacer = true; + else + notLast = true; + } - name.innerHTML = "

" + text.sender + "

"; - name.setAttribute("class", "sender"); + if (is_group && texts[n].is_from_me === "0" && texts[i].sender !== texts[n].sender) { + sender_box = document.createElement("div"); + sender_box.className = "sender"; + sender_box.innerHTML = "

" + texts[n].sender + "

"; + } + } - content.appendChild(name); - } + if (append_time_display) + tc.innerHTML += "

" + timeConverter(texts[i].date) + "

"; - var fts = getTextElement(text); - if (fts.length === undefined && fts.outerHTML === "") return; - var ft = fts.length === undefined ? [fts] : fts; + for (var j = 0; j < ft.length; j++) { + if (ft[j].innerHTML === undefined || ft[j].innerHTML === "") continue; + if (notLast) ft[j].classList.add("notLast"); + if (append) tc.appendChild(ft[j]); + else tc.insertBefore(ft[j], document.getElementById("moretextsbutton").nextSibling); + } - var texts = document.getElementsByClassName("text"); - var text_areas = document.getElementsByClassName("text-area"); - var last_time = Math.max(texts.length > 0 ? texts[texts.length - 1].getAttribute("date") : 0, text_areas.length > 0 ? text_areas[text_areas.length - 1].getAttribute("date") : 0); - var last_is_area = text_areas.length > 0 && last_time === text_areas[text_areas.length - 1]; + if (add_spacer) { + if (append) tc.appendChild(spacer); + else tc.insertBefore(spacer, tc.firstChild.nextSibling); + } - var now = new Date(); - var time = now.getTime() / 1000; - if (time - 3600 > timeConverter(last_time, true)) { - var t = document.createElement("div"); - t.setAttribute("class", "time-display"); - t.innerHTML = "

" + timeConverter(time, false, false) + "

"; - document.getElementById("text-content").appendChild(t); - } else { - var last_item = last_is_area ? text_areas[text_areas.length - 1] : texts[texts.length - 1]; - if (last_item.getAttribute("class").includes("from_them")) { - if (time - 600 > timeConverter(last_time, true)) { - var spacer = document.createElement("div"); - spacer.setAttribute("class", "spacer"); - document.getElementById("text-content").appendChild(spacer); - } else { - last_item.setAttribute("class", last_item.getAttribute("class") + " notLast"); + if ((hourBreak && i < texts.length - 1) || (!append && i === texts.length - 1)) { + var td = document.createElement("div"); + td.className = "time-display"; + td.innerHTML = "

" + timeConverter(texts[n].date) + "

"; + if (append) tc.appendChild(td); + else tc.insertBefore(td, ft[ft.length - 1].nextSibling); + if (is_group && texts[n].is_from_me === "0") { + sender_box = document.createElement("div"); + sender_box.className = "sender"; + sender_box.innerHTML = "

" + texts[n].sender + "

"; } } + + if (sender_box !== undefined) { + if (append) tc.appendChild(sender_box); + else tc.insertBefore(sender_box, ft[ft.length - 1].nextSibling); + } } - for (var i = 0; i < ft.length; i++) { - var t = ft[i]; - if (t.innerHTML !== undefined && t.innerHTML.length > 0) { - content.appendChild(t); - t.scrollIntoView(); + if (append && !is_group) { + et = document.getElementsByClassName("text"); + + var rr = document.getElementsByClassName("readreceipt"); + if (rr.length > 0) rr[0].outerHTML = ""; + var lt = undefined; + + for (var i = et.length - 1; i >= 0 && lt === undefined; i--) + if (et[i].className.includes("from_me") && et[i].className.includes("iMessage") && et[i].getAttribute("date_read") !== "0") + lt = et[i]; + + if (lt !== undefined) { + var nrr = document.createElement("span"); + nrr.className = "readreceipt"; + nrr.innerHTML = "Read " + timeConverter(parseInt(lt.getAttribute("date_read"))); + + if (lt === tc.lastChild) tc.appendChild(nrr); + else tc.insertBefore(nrr, lt.nextSibling); } } + + if (append) tc.lastChild.scrollIntoView(); + } + + async function getLatestText(chat = current_chat_id, read = false) { + if (debug) console.log("Getting latest text for chat " + chat); + var text; + + let url = prefix + "/requests?person=" + chat + "&num=1&read=" + (read ? "true" : "false"); + + let textPromise = await fetchFromURL(url); + var text = textPromise.texts[0]; + + var is_group = chat.substring(0, 4) === "chat" && !chat.includes("@"); + + printListOfTexts([text], true, is_group); } function clearTextContent() { @@ -450,7 +530,7 @@ var sels = document.getElementsByClassName("selected"); for (var i = 0; i < sels.length; i++) - sels[i].setAttribute("class", sels[i].getAttribute("class").replace(/selected/g, "")); + sels[i].className = sels[i].className.replace(/selected/g, ""); current_chat_id = ""; } @@ -479,9 +559,9 @@ var b = getChatElement(items[i], num_texts); if (items[i].chat_identifier === selected_id) - b.setAttribute("class", b.getAttribute("class") + " selected"); + b.classList.add("selected"); - if (b.getAttribute("class").includes("unpinned")) { + if (b.className.includes("unpinned")) { doc.insertBefore(b, document.getElementById("morechatsbutton")); } else { var pinRows = document.getElementsByClassName("pinRow"); @@ -490,10 +570,10 @@ pinRows[pinRows.length - 1].firstChild.appendChild(b); } else { var row = document.createElement("div"); - row.setAttribute("class", "pinRow"); + row.className = "pinRow"; var inner = document.createElement("div"); - inner.setAttribute("class", "innerRow"); + inner.className = "innerRow"; inner.appendChild(b); row.appendChild(inner); @@ -534,17 +614,17 @@ var selected = document.getElementsByClassName("selected"); if (selected.length > 0) - selected[0].setAttribute("class", selected[0].getAttribute("class").replace(/selected/g, "")); + selected[0].className = selected[0].className.replace(/selected/g, ""); var hiddenchatbox = document.getElementById("hiddenchatbox"); hiddenchatbox.setAttribute("value", chat_id); var orig_btn = document.getElementById(chat_id); - orig_btn.setAttribute("class", orig_btn.getAttribute("class").replace(/unread/g, "") + " selected"); + orig_btn.className = orig_btn.className.replace(/unread/g, "") + " selected"; var unread_chats = document.getElementsByClassName("unread").length; document.title = "SMServer" + (unread_chats > 0 ? " (" + String(unread_chats) + ")" : ""); - if (!orig_btn.getAttribute("class").includes("unpinned") && orig_btn.getElementsByClassName("chat-text").length > 0) + if (!orig_btn.className.includes("unpinned") && orig_btn.getElementsByClassName("chat-text").length > 0) orig_btn.getElementsByClassName("chat-text")[0].outerHTML = ""; var chat_title = document.getElementById(chat_id).getElementsByClassName("chat-name")[0]; @@ -569,7 +649,7 @@ var more = document.createElement("button"); more.setAttribute("onclick", "getMoreTexts()"); - more.setAttribute("id", "moretextsbutton"); + more.id = "moretextsbutton"; more.innerHTML = "

+ More Texts

"; myNode.appendChild(more); @@ -577,88 +657,10 @@ texts = texts.reverse(); var is_group = chat_id.substring(0, 4) === "chat" && !chat_id.includes("@"); - var doc = document.getElementsByClassName("text-content")[0]; - - for (var i = 0; i < texts.length; i++) { - - var ft = getTextElement(texts[i]); - if (ft.length === undefined) { - if (ft.outerHTML === "") continue; - ft = [ft]; - } - t = ft[0]; - - if (t.innerHTML !== undefined && t.innerHTML.length > 0) { - var add_time = i === 0 || texts[i].date - texts[i - 1].date > 3600000000000; - if (add_time) { - var time = document.createElement("div"); - time.setAttribute("class", "time-display"); - - var formattedTime = timeConverter(texts[i].date); - - time.innerHTML = "

" + formattedTime + "

"; - doc.appendChild(time); - } - - if (is_group && texts[i].is_from_me === "0" && (i === 0 || texts[i].sender !== texts[i - 1].sender || add_time)) { - var from = document.createElement("div"); - - from.innerHTML = "

" + texts[i].sender + "

"; - from.setAttribute("class", "sender"); - - doc.appendChild(from); - } - } - - var notLast = (i < texts.length - 1 && texts[i].is_from_me == texts[i+1].is_from_me && texts[i+1].date - texts[i].date < 600000000000); - if (notLast && is_group) notLast = texts[i].sender === texts[i+1].sender; - - for (var j = 0; j < ft.length; j++) { - if (ft[j].innerHTML === undefined || ft[j].innerHTML.length === 0) continue; - if (notLast) ft[j].setAttribute("class", ft[j].getAttribute("class") + " notLast"); - doc.appendChild(ft[j]); - } - - if (i < texts.length - 1 && texts[i+1].date - texts[i].date >= 600000000000 && texts[i+1].date - texts[i].date <= 3600000000000) { - var spacer = document.createElement("div"); - spacer.setAttribute("class", "spacer"); - doc.appendChild(spacer); - } - } - - document.getElementById("sendbox").focus(); - - var my_texts = document.getElementsByClassName("is_from_me"); - - if (my_texts.length > 0) { - var my_last_text; - - for (var i = my_texts.length - 1; i > 0 && my_texts[0].getAttribute("date_read") !== "0"; i--) { - if (my_texts[i].getAttribute("date_read") !== "0") { - my_last_text = my_texts[i]; - break; - } - } - - if (my_last_text !== undefined) { - - var lc = document.getElementById("text-content").lastChild; - var isimsg = (lc.getAttribute("class").includes("text-area") ? lc.getElementsByClassName("text")[0] : lc).getAttribute("class").includes("iMessage"); - - if (!is_group && my_last_text.getAttribute("date_read") !== "0" && isimsg) { - var rr = document.createElement("span"); - rr.setAttribute("class", "readreceipt"); - rr.innerText = "Read " + timeConverter(parseInt(my_last_text.getAttribute("date_read"))); - - if (my_last_text === doc.lastChild) - doc.appendChild(rr); - else - doc.insertBefore(rr, my_last_text.nextSibling); - } - } - } + printListOfTexts(texts, true, is_group); document.getElementById("text-content").lastChild.scrollIntoView(); + document.getElementById("sendbox").focus(); } const getMoreTexts = async (num_texts = num_texts_to_load) => { @@ -672,53 +674,8 @@ printed_texts += texts.length; - var last_item = document.createElement("br"); - var first_item = document.getElementsByClassName("text-content")[0]; - var is_group = chat_id.substring(0, 4) === "chat"; - var doc = document.getElementsByClassName("text-content")[0]; - - for (var i = 0; i < texts.length; i++) { - - var fts = getTextElement(texts[i]); - if (fts.length === undefined && fts.outerHTML === "") return; - var ft = fts.length === undefined ? [fts] : fts; - - var add_time = i != 0 && texts[i].date - texts[i-1].date < -3600000000000; - if (add_time) { - var time = document.createElement("div"); - time.setAttribute("class", "time-display"); - - var formattedTime = timeConverter(texts[i - 1].date); - - time.innerHTML = "

" + formattedTime + "

"; - doc.insertBefore(time, document.getElementById("moretextsbutton").nextSibling); - } - - if (i > 0 && texts[i-1].date - texts[i].date >= 600000000000 && texts[i-1].date - texts[i].date <= 3600000000000) { - var spacer = document.createElement("div"); - spacer.setAttribute("class", "spacer"); - doc.insertBefore(spacer, document.getElementById("moretextsbutton").nextSibling); - } - - var notLast = i > 0 && texts[i].is_from_me === texts[i-1].is_from_me && texts[i-1].date - texts[i].date <= 600000000000; - if (notLast && is_group) notLast = texts[i].sender === texts[i+1].sender; - - for (var j = 0; j < ft.length; j += 1) { - if (ft[j].innerHTML === undefined || ft[j].innerHTML.length === 0) continue; - if (notLast) ft[j].setAttribute("class", ft[j].getAttribute("class") + " notLast"); - doc.insertBefore(ft[j], document.getElementById("moretextsbutton").nextSibling); - } - - if (is_group && texts[i].is_from_me === "0" && ((i !== texts.length - 1 && texts[i].sender !== texts[i + 1].sender) || (i === texts.length - 1) || add_time)) { - var from = document.createElement("div"); - - from.innerHTML = "

" + texts[i].sender + "

"; - from.setAttribute("class", "sender"); - - doc.insertBefore(from, document.getElementById("moretextsbutton").nextSibling); - } - } + printListOfTexts(texts, false, is_group); } function sleep(ms) { @@ -735,6 +692,8 @@ var tbox = document.getElementById("sendbox"); var oldval = tbox.value; + var gpval = document.getElementById("hiddenchatbox").value; + var is_group = gpval.substring(0, 4) === "chat" && !gpval.includes("@"); var photos = document.getElementById("hiddenphotobutton"); var selected = document.getElementsByClassName("selected-photo"); @@ -768,65 +727,38 @@ autoGrow(); - var texts = document.getElementsByClassName("text"); - var text_areas = document.getElementsByClassName("text-area"); - var last_time = Math.max(texts.length > 0 ? texts[texts.length - 1].getAttribute("date") : 0, text_areas.length > 0 ? text_areas[text_areas.length - 1].getAttribute("date") : 0); - var last_is_area = text_areas.length > 0 && last_time === text_areas[text_areas.length - 1]; - - var now = new Date(); - var time = now.getTime() / 1000; - if (time - 3600 > timeConverter(last_time, true)) { - var t = document.createElement("div"); - t.setAttribute("class", "time-display"); - t.innerHTML = "

" + timeConverter(time, false, false) + "

"; - document.getElementById("text-content").appendChild(t); - } else { - var last_item = last_is_area ? text_areas[text_areas.length - 1] : texts[texts.length - 1]; - if (last_item.getAttribute("class").includes("from_me")) { - if (time - 600 > timeConverter(last_time, true)) { - var spacer = document.createElement("div"); - spacer.setAttribute("class", "spacer"); - document.getElementById("text-content").appendChild(spacer); - } else { - last_item.setAttribute("class", last_item.getAttribute("class") + " notLast"); - } - } - } - if (!has_attachments) { - var nt = document.createElement("p"); - var im = true; + var now = new Date(); + var time = now.getTime() / 1000; + + var nt = { + 'associated_message_guid': "", + 'service': "iMessage", + 'cache_has_attachments': "0", + 'subject': subval, + 'date_read': "0", + 'is_from_me': "1", + 'date': timeConverter(time, true, false, true), + 'associated_message_type': "0", + 'text': oldval, + 'balloon_bundle_id': "0", + 'guid': "0", + } var ch = document.getElementById("text-content").children; for (var i = ch.length - 1; i >= 0; i -= 1) { - if (ch[i].getAttribute("class").includes("text")) { - im = ch[i].getAttribute("class").includes("iMessage"); + if (ch[i].className.includes("text")) { + nt.service = ch[i].className.includes("iMessage") ? "iMessage" : "SMS"; break; } } - nt.setAttribute("class", "text is_from_me " + (im ? "iMessage" : "SMS")); - var new_time = (time - 978307200) * 1000000000; - nt.setAttribute("date", String(new_time)); - - var topsub = document.createElement("div"); - topsub.setAttribute("class", "subject"); - topsub.innerText = subval; - - var bottomtext = document.createElement("span"); - bottomtext.innerText = oldval; - - nt.innerHTML = topsub.outerHTML + bottomtext.outerHTML; - - document.getElementById("text-content").appendChild(nt); - nt.scrollIntoView(); + printListOfTexts([nt], true, is_group); } - await sleep(has_attachments ? (total_size / 10000) : sleep_time); - - if (has_attachments) - getLatestText(current_chat_id, true); + await sleep(has_attachments && total_size > 1000000 ? (total_size / 6000) : sleep_time); + if (has_attachments) getLatestText(current_chat_id, true); setChatAsTop(current_chat_id, oldval); } } @@ -890,12 +822,13 @@ async function autoGrow() { var elem = document.getElementById("sendbox"); + var oldheight = elem.style.height.match(/\d/g); elem.style.height = getComputedStyle(document.documentElement).getPropertyValue("--send-button-size"); var scroll_height = elem.scrollHeight; elem.style.height = scroll_height + "px"; /// I don't know why this next section is necessary but it is - if (elem.value === "") { + if (elem.value === "" || (oldheight !== null && Number(oldheight.join("")) > scroll_height)) { var uhb = document.getElementById("unhiddenbutton"); var crb = document.getElementById("camerarollbutton"); @@ -905,8 +838,6 @@ await uhb.setAttribute("style", "margin: auto 0;"); await crb.setAttribute("style", "margin: auto 0;"); } - - document.getElementById("text-content").lastChild.scrollIntoView(); } function showPopup() { @@ -973,18 +904,18 @@ if (chat.length !== 0) { if (btn === undefined || btn === null || chat === "any") { printChats(); - } else if (chat !== document.getElementsByClassName("unpinned")[0].id && btn.getAttribute("class").includes("unpinned")) { + } else if (chat !== document.getElementsByClassName("unpinned")[0].id && btn.className.includes("unpinned")) { setChatAsTop(chat, new_text); } else if (btn !== undefined) { - if (chat !== current_chat_id || !document.hasFocus()) btn.setAttribute("class", btn.getAttribute("class") + " unread"); - if (btn.getAttribute("class").includes("unpinned") || btn.getElementsByClassName("chat-text")[0] !== undefined) { + if (chat !== current_chat_id || !document.hasFocus()) btn.classList.add("unread"); + if (btn.className.includes("unpinned") || btn.getElementsByClassName("chat-text")[0] !== undefined) { var text = btn.getElementsByClassName("chat-text")[0]; if (text !== undefined && new_text.length > 0) text.innerText = new_text; } else if (chat !== current_chat_id) { var text = document.createElement("div"); - text.setAttribute("class", "chat-text"); + text.className = "chat-text"; text.innerText = new_text; var tb = btn.getElementsByClassName("chat-top")[0]; @@ -1010,12 +941,12 @@ var unpins = document.getElementsByClassName("unpinned"); var current_box = document.getElementById(chat); - if ((current_box && !current_box.getAttribute("class").includes("unpinned")) || (unpins.length > 0 && chat === unpins[0].id)) { - if (new_text !== "" && current_box.getAttribute("class").includes("unpinned")) + if ((current_box && !current_box.className.includes("unpinned")) || (unpins.length > 0 && chat === unpins[0].id)) { + if (new_text !== "" && current_box.className.includes("unpinned")) current_box.getElementsByClassName("chat-text")[0].innerText = new_text; if (!document.hasFocus() || current_chat_id !== chat) - current_box.setAttribute("class", current_box.getAttribute("class") + " unread") + current_box.classList.add("unread"); } else { var last_text; var selected = false; @@ -1036,16 +967,16 @@ var existingButton = document.getElementById(chat); if (existingButton) { - new_chat.pinned = existingButton.getAttribute("class").includes("unpinned") ? "false" : "true"; + new_chat.pinned = existingButton.className.includes("unpinned") ? "false" : "true"; new_chat.display_name = existingButton.getElementsByClassName("chat-name")[0].firstChild.textContent; - selected = existingButton.getAttribute("class").includes("selected"); + selected = existingButton.className.includes("selected"); existingButton.outerHTML = ""; } else { new_chat.display_name = await getTextFromURL("/requests?name=" + chat); } var new_box = getChatElement(new_chat); - if (selected) new_box.setAttribute("class", new_box.getAttribute("class") + " selected"); + if (selected) new_box.classList += "selected"; var pinRows = document.getElementsByClassName("pinRow"); @@ -1060,10 +991,10 @@ function setSelectedPhoto(url) { if (debug) console.log("setting selected photo for " + url); var p = document.getElementById(url); - if (p.getAttribute("class").includes("selected-photo")) - p.setAttribute("class", "photo"); + if (p.className.includes("selected-photo")) + p.className = "photo"; else - p.setAttribute("class", p.getAttribute("class") + " selected-photo"); + p.classList.add("selected-photo"); } function getPhotoElement(photo) { @@ -1073,9 +1004,9 @@ var c = "photo"; if (photo.is_favorite === "true") c += " is_favorite"; - p.setAttribute("class" , c); + p.className = c; - p.setAttribute("id", photo.URL); + p.id = photo.URL; p.setAttribute("onclick", "setSelectedPhoto(\"" + photo.URL + "\");"); p.setAttribute("width", "160"); p.setAttribute("height", "160"); @@ -1084,9 +1015,9 @@ if (photo.is_favorite === "true") { var h = document.createElement("span"); - h.setAttribute("class", "favorite-heart"); + h.className = "favorite-heart"; h.innerHTML = faLoaded ? "" : "❤"; - if (faLoaded) h.setAttribute("style", "padding-right: 24px") /// It's annoyingly specific, 1px off from using unicode + if (faLoaded) h.setAttribute("style", "padding-right: 24px"); /// It's annoyingly specific, 1px off from using unicode p.appendChild(h); } return p; @@ -1141,9 +1072,8 @@ document.getElementById("camerarollpopup").style.display = "none"; var p = document.getElementsByClassName("selected-photo"); - for (var i = p.length - 1; i >= 0; --i) { - p[i].setAttribute("class", p[i].getAttribute("class").replace(/selected-photo/g, "")); - } + for (var i = p.length - 1; i >= 0; --i) + p[i].className = p[i].className.replace(/selected-photo/g, ""); document.getElementById("camerarollbutton").innerHTML = faLoaded ? "" : "📷"; } @@ -1196,11 +1126,11 @@ batt.innerHTML = String(level).split(".")[0] + "%" + bat_symbol.outerHTML; if (dbl > 35) - batt.setAttribute("class", "fullEnough"); + batt.className = "fullEnough"; else if (dbl > 20) - batt.setAttribute("class", "risky"); + batt.className = "risky"; else - batt.setAttribute("class", "low"); + batt.className = "low"; } } @@ -1208,8 +1138,8 @@ if (debug) console.log("setting " + chat + " as typing"); if (chat === current_chat_id) { var d = document.createElement("div"); - d.setAttribute("class", "text is_from_them iMessage"); - d.setAttribute("id", "typingIndicator"); + d.className = "text is_from_them iMessage"; + d.id = "typingIndicator"; d.innerHTML = ""; @@ -1223,9 +1153,9 @@ clearInterval(typing); return; } - i.children[c].setAttribute("class", "dot"); + i.children[c].className = "dot"; c = (c + 1) % 3; - i.children[c].setAttribute("class", "dot flashing"); + i.children[c].className = "dot flashing"; }, 400); } } @@ -1233,15 +1163,15 @@ function enableSubject() { var tbox = document.getElementById("sendbox"); var ntbox = tbox.cloneNode(false); - ntbox.setAttribute("class", "subjectInput"); + ntbox.className = "subjectInput"; ntbox.setAttribute("placeholder", "body"); var con = document.createElement("div"); - con.setAttribute("class", "subjectTextarea"); + con.className = "subjectTextarea"; var sbox = document.createElement("textarea"); - sbox.setAttribute("class", "subjectSubject"); - sbox.setAttribute("id", "subjectBox"); + sbox.className = "subjectSubject"; + sbox.id = "subjectBox"; sbox.setAttribute("name", "subject"); sbox.setAttribute("oninput", tbox.getAttribute("oninput")); sbox.setAttribute("onkeydown", tbox.getAttribute("onkeydown")); @@ -1253,7 +1183,7 @@ document.documentElement.style.setProperty("--messages-send-height", "64px"); /// hardcoded. not a fan. should fix document.documentElement.style.setProperty("--send-box-size", "calc((var(--messages-send-height) / 2) - 6px)"); - document.getElementById("sendbutton").setAttribute("class", "hasSubject"); + document.getElementById("sendbutton").className = "hasSubject"; document.getElementById("camerarollbutton").style.margin = "auto 0"; document.getElementById("unhiddenbutton").style.margin = "auto 0"; @@ -1267,8 +1197,8 @@ window.addEventListener("focus", function(event) { if (current_chat_id.length === 0) return; var bt = document.getElementById(current_chat_id); - if (bt === undefined || bt === null || !bt.getAttribute("class").includes("unread")) return; - bt.setAttribute("class", bt.getAttribute("class").replace(/unread/g, "")); + if (bt === undefined || bt === null || !bt.className.includes("unread")) return; + bt.className = bt.className.replace(/unread/g, ""); var _ = fetchFromURL(prefix + "/requests?person=" + current_chat_id + "&num=1"); var unreads = document.getElementsByClassName("unread").length; diff --git a/src/SMServer/html/nord_theme.css b/src/SMServer/html/nord_theme.css index 24a1814..0e3f898 100644 --- a/src/SMServer/html/nord_theme.css +++ b/src/SMServer/html/nord_theme.css @@ -19,7 +19,7 @@ } #moretextsbutton { border: none; - box-shadow: 0px 0px 10px #2224; + box-shadow: 0px 0px 10px #2229; } #camerarollbutton, #unhiddenbutton { background-color: #4c566a; diff --git a/src/SMServer/html/style.css b/src/SMServer/html/style.css index 0c02293..b86c758 100644 --- a/src/SMServer/html/style.css +++ b/src/SMServer/html/style.css @@ -207,6 +207,7 @@ i#chargingSymbol { transition: all linear 0.1s; align-items: center; height: var(--chats-button-height); + position: relative; } .chats button.unpinned img { max-height: calc(var(--chats-button-height) * 0.76); @@ -228,6 +229,7 @@ i#chargingSymbol { border-radius: 8px; max-width: calc(var(--chats-width) * 0.25); width: min-content; + position: relative; } .chats button.pinned .chat-top { display: grid; @@ -253,10 +255,24 @@ i#chargingSymbol { grid-column: 1; height: 26px; } +.chats button.pinned.unread .chat-name:before, +.unread .chat-nonpic:before { + content: ''; + background-color: #369cff; + position: absolute; + border-radius: 100%; +} +.chats button.pinned.unread .chat-name:before { + width: calc(7px + 0.3vw); + height: calc(7px + 0.3vw); + left: -10px; + bottom: calc(14px - 5%); +} .pinned .chat-name { font-size: calc(8px + 0.5vw); margin: auto; padding: 0; + max-width: 100%; width: max-content; } .chat-nonpic { @@ -300,8 +316,12 @@ i#chargingSymbol { color: dodgerblue; margin: auto; } -.unread img { - box-shadow: 0 0 10px dodgerblue; +.unread .chat-nonpic:before { + height: 15px; + width: 15px; + top: 9px; + left: 44px; + box-shadow: 0 0 5px #0007; } .text-content p img { max-width: 100%; @@ -343,8 +363,7 @@ i#chargingSymbol { padding: 7px 12px; line-height: 21px; /* Specific but that's what it is */ } -.text:before, .text:after, -.text-area:before, .text-area:after { +.text:before, .text:after { content: ''; position: absolute; bottom: -2px; @@ -355,6 +374,7 @@ i#chargingSymbol { } .text span { white-space: pre-wrap; + word-break: break-word; } .is_from_them a, .is_from_them a span { color: dodgerblue; @@ -362,6 +382,9 @@ i#chargingSymbol { .is_from_me a, .is_from_me a span { color: lightblue; } +.from_them + .sender { + margin-top: 10px; +} .sender { margin-left: 4px; margin-bottom: -14px; @@ -370,6 +393,7 @@ i#chargingSymbol { font-size: 14px; font-weight: lighter; margin: 0 0 16px 0; + color: #bbb; } .is_from_me { position: relative; @@ -415,9 +439,7 @@ i#chargingSymbol { transform: translate(-30px, -2px); } .is_from_me + .is_from_me, -.from_me + .from_me, -.is_from_them + .is_from_them, -.from_them + .from_them { +.is_from_them + .is_from_them { margin-top: -10px; } .is_from_me.iMessage { @@ -432,14 +454,22 @@ i#chargingSymbol { max-width: 100%; border-radius: 10px; } -.notLast:before, .notLast:after { +.notLast:before, .notLast:after, +.allEmoji:before, .allEmoji:after { display: none; } .notLast { border-radius: 20px; } -.notLast { - border-radius: 20px; +.text.allEmoji { + background-color: transparent; + font-size: 50px; +} +.allEmoji.is_from_me { + padding: 20px 0 20px 20px; +} +.allEmoji.is_from_them { + padding: 20px 20px 20px 0; } .spacer { height: 10px; @@ -449,15 +479,16 @@ i#chargingSymbol { } #moretextsbutton { border: 1px solid black; - border-radius: 20px; + border-radius: 8px; background-color: cornflowerblue; align-self: center; margin: 20px 0; box-shadow: 0px 0px 10px #222; transition: linear 0.2s; + padding: 6px; } #moretextsbutton p { - margin: 10px 0 + margin: 0; } #moretextsbutton:hover { box-shadow: none; @@ -712,12 +743,12 @@ i#chargingSymbol { top: -20px; } .readreceipt { - color: #a6a6a6; font-size: 14px; - float: right; - padding-right: 10px; align-self: flex-end; } +.readreceipt, .readreceipt strong { + color: #a6a6a6; +} .text + .readreceipt { margin-top: -8px; } @@ -728,6 +759,8 @@ i#chargingSymbol { z-index: 2; box-shadow: 0px 0px 6px #000; height: max-content; + backdrop-filter: blur(1.5px); + -webkit-backdrop-filter: blur(1.5px); } .from_them .reaction { left: -6px; @@ -736,7 +769,6 @@ i#chargingSymbol { } .text-area { margin-bottom: -10px; - margin-top: -6px; display: flex; } .text-area.from_me { @@ -768,6 +800,7 @@ p.text.richLink { } .richLinkTitle { font-size: 22px; + margin: 2px; } .richLinkSubtitle { padding: 5px;