diff --git a/android/app/src/main/assets/public/blah/en-cat.json b/android/app/src/main/assets/public/blah/en-cat.json
index 75fcf968..11bc708c 100644
--- a/android/app/src/main/assets/public/blah/en-cat.json
+++ b/android/app/src/main/assets/public/blah/en-cat.json
@@ -120,7 +120,6 @@
"title.back.to.register": "back to registration",
"placeholder.username": "cat name",
"placeholder.captcha.code": "captcha code",
- "placeholder.message.input": "meow...",
"desc.messages.loading": "loading meows...",
"desc.no.dms": "no direct meowchats :c",
"action.dm.opening": "opening meowchat...",
@@ -128,5 +127,18 @@
"keys.local.server.mismatch": "this device has different nametags than server :c use the device u first logged in on",
"keys.server.decrypt.failed": "wrong password for ur secret nametag bundle :c",
"dm.messages.fetch.failed": "failed to fetch meows :c",
- "messages.decrypt.failed": "failed to decrypt meow :c"
+ "messages.decrypt.failed": "failed to decrypt meow :c",
+ "title.reply.message": "meow back",
+ "title.react.message": "purr",
+ "title.delete.message": "hiss away",
+ "title.replied.to": "meowed back to",
+ "action.message.deleting": "hissing message away...",
+ "action.message.sending": "sending meow...",
+ "action.message.reacting": "adding purr...",
+ "error:message.delete.failed": "failed to hiss away meow",
+ "error:message.send.failed": "failed to send meow",
+ "error:message.react.failed": "failed to add purr",
+ "info.sending.message": "sending...",
+ "placeholder.message.input": "meow...",
+ "larp.redacted": "this meow was hissed away."
}
diff --git a/android/app/src/main/assets/public/blah/en-us.json b/android/app/src/main/assets/public/blah/en-us.json
index 104354bb..6b1710ad 100644
--- a/android/app/src/main/assets/public/blah/en-us.json
+++ b/android/app/src/main/assets/public/blah/en-us.json
@@ -127,5 +127,17 @@
"keys.local.server.mismatch": "This device has different encryption keys than the server. Use the device where you first logged in, or restore server keys from backup.",
"keys.server.decrypt.failed": "Could not decrypt your account keys with this password.",
"dm.messages.fetch.failed": "Failed to fetch messages",
- "messages.decrypt.failed": "Failed to decrypt message"
+ "messages.decrypt.failed": "Failed to decrypt message",
+ "title.reply.message": "Reply",
+ "title.react.message": "React",
+ "title.delete.message": "Delete",
+ "title.replied.to": "Replied to",
+ "action.message.deleting": "Deleting message...",
+ "action.message.sending": "Sending message...",
+ "action.message.reacting": "Adding reaction...",
+ "error:message.delete.failed": "Failed to delete message",
+ "error:message.send.failed": "Failed to send message",
+ "error:message.react.failed": "Failed to add reaction",
+ "info.sending.message": "Sending...",
+ "larp.redacted": "This message was deleted."
}
diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js
index 27f760e6..a1bc9de9 100644
--- a/android/app/src/main/assets/public/main.js
+++ b/android/app/src/main/assets/public/main.js
@@ -1,18 +1,16 @@
var prot = window.location.protocol;
-async function updateProtocolAndUrl(host)
-{
+async function updateProtocolAndUrl(host) {
prot = window.location.protocol == "miarven:" ? "http:" : window.location.protocol;
if (prot == "http:") {
try {
JSON.parse(await fetchAsync(`${prot}//${host}/_larpix/serverinfo`));
- }
- catch (error) {
+ } catch (error) {
try {
JSON.parse(await fetchAsync(`https://${host}/_larpix/serverinfo`));
prot = "https:";
+ } catch (error) {
}
- catch (error) {}
}
}
url = `${prot}//${host}/_larpix`;
@@ -26,7 +24,7 @@ var params = new URLSearchParams(window.location.search);
try {
var loadingScreen = document.querySelector("loading");
var mainScreen = document.querySelector("main");
-
+
var loadingStatus = document.getElementById("loadingstatus");
var addDmBtn = document.getElementById("add-dm-btn");
@@ -59,17 +57,17 @@ try {
var sidebarProfile = document.getElementById("sidebar-profile");
var sidebarProfileButton = sidebarProfile.children.item(1);
var sidebarProfileIndicator = sidebarProfile.children.item(0);
-
+
var sidebarPfp = sidebarProfileButton.children.item(0);
-
-
+
+
var sidebarInbox = document.getElementById("sidebar-inbox");
var sidebarInboxButton = sidebarInbox.children.item(1);
var sidebarInboxIndicator = sidebarInbox.children.item(0);
var fixedContextMenu = document.getElementById("fixed-context-menu");
-
+
} catch (e) {
}
@@ -148,7 +146,7 @@ async function deriveAesGcmKeyFromPassword(passwordPlain, salt, iterations) {
const keyMaterial = await crypto.subtle.importKey(
"raw",
enc.encode(passwordPlain),
- { name: "PBKDF2" },
+ {name: "PBKDF2"},
false,
["deriveKey"]
);
@@ -160,7 +158,7 @@ async function deriveAesGcmKeyFromPassword(passwordPlain, salt, iterations) {
hash: "SHA-256"
},
keyMaterial,
- { name: "AES-GCM", length: 256 },
+ {name: "AES-GCM", length: 256},
false,
["encrypt", "decrypt"]
);
@@ -168,11 +166,11 @@ async function deriveAesGcmKeyFromPassword(passwordPlain, salt, iterations) {
async function encryptJsonWithPassword(jsonObj, passwordPlain) {
const salt = crypto.getRandomValues(new Uint8Array(16));
- const iv = crypto.getRandomValues(new Uint8Array(12));
+ const iv = crypto.getRandomValues(new Uint8Array(12));
const iterations = 310000;
const key = await deriveAesGcmKeyFromPassword(passwordPlain, salt, iterations);
const plaintext = new TextEncoder().encode(JSON.stringify(jsonObj));
- const ciphertextBuf = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, plaintext);
+ const ciphertextBuf = await crypto.subtle.encrypt({name: "AES-GCM", iv}, key, plaintext);
const ciphertext = new Uint8Array(ciphertextBuf);
return JSON.stringify({
@@ -195,7 +193,7 @@ async function decryptJsonWithPassword(envelopeJson, passwordPlain) {
const iv = base64ToUint8(env.iv);
const ct = base64ToUint8(env.ct);
const key = await deriveAesGcmKeyFromPassword(passwordPlain, salt, env.iter);
- const plainBuf = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ct);
+ const plainBuf = await crypto.subtle.decrypt({name: "AES-GCM", iv}, key, ct);
return JSON.parse(new TextDecoder().decode(plainBuf));
}
@@ -209,7 +207,7 @@ async function hkdfSha256Bytes(ikmBytes, infoString, saltBytes = null, length =
const salt = saltBytes ?? new Uint8Array(32);
const keyMaterial = await crypto.subtle.importKey("raw", ikmBytes, "HKDF", false, ["deriveBits"]);
const bits = await crypto.subtle.deriveBits(
- { name: "HKDF", hash: "SHA-256", salt, info },
+ {name: "HKDF", hash: "SHA-256", salt, info},
keyMaterial,
length * 8
);
@@ -217,14 +215,14 @@ async function hkdfSha256Bytes(ikmBytes, infoString, saltBytes = null, length =
}
async function importAesGcmKey(keyBytes) {
- return crypto.subtle.importKey("raw", keyBytes, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
+ return crypto.subtle.importKey("raw", keyBytes, {name: "AES-GCM"}, false, ["encrypt", "decrypt"]);
}
async function encryptAesGcmToBase64(plainText, keyBytes) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await importAesGcmKey(keyBytes);
const pt = new TextEncoder().encode(plainText);
- const ctBuf = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, pt);
+ const ctBuf = await crypto.subtle.encrypt({name: "AES-GCM", iv}, key, pt);
const combined = new Uint8Array(iv.length + ctBuf.byteLength);
combined.set(iv, 0);
combined.set(new Uint8Array(ctBuf), iv.length);
@@ -236,7 +234,7 @@ async function decryptAesGcmFromBase64(cipherBase64, keyBytes) {
const iv = combined.slice(0, 12);
const ct = combined.slice(12);
const key = await importAesGcmKey(keyBytes);
- const ptBuf = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ct);
+ const ptBuf = await crypto.subtle.decrypt({name: "AES-GCM", iv}, key, ct);
return new TextDecoder().decode(ptBuf);
}
@@ -704,7 +702,7 @@ function clearAction(actionid) {
notif.addEventListener('transitionend', async () => {
notif.remove();
- }, { once: true });
+ }, {once: true});
setTimeout(() => notif.remove(), 300);
});
}
@@ -744,6 +742,7 @@ function getInitials(name) {
//1 word at least 2 letters
return word.substring(0, 2).toUpperCase();
}
+
function stringToColor(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
@@ -755,6 +754,7 @@ function stringToColor(str) {
return `hsl(${h}, ${s}%, ${l}%)`;
}
+
function createAvatarSvg(name, size = 512) {
const initials = getInitials(name);
const color = stringToColor(name);
@@ -777,17 +777,14 @@ function createAvatarSvg(name, size = 512) {
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
}
-async function getAvatarUrl(id, username)
-{
+async function getAvatarUrl(id, username) {
try {
let pfpUrl = `${url}/user/storage/public/getentry?id=${id}&e=larp.profile.pfp`;
- if ((await fetchAsync(pfpUrl)) == "")
- {
+ if ((await fetchAsync(pfpUrl)) == "") {
throw Error();
}
return pfpUrl;
- }
- catch (e) {
+ } catch (e) {
return createAvatarSvg(username);
}
}
@@ -797,10 +794,10 @@ function updateLoadingStatus(message) {
}
async function loadingFadeOut() {
-
+
mainScreen.style.transform = "scale(0.85)";
mainScreen.style.opacity = "0";
-
+
loadingScreen.style.transform = "scale(0.85)";
loadingScreen.style.opacity = "0";
await delay(200);
@@ -810,6 +807,7 @@ async function loadingFadeOut() {
mainScreen.style.transform = "";
mainScreen.style.opacity = "";
}
+
async function loadingFadeIn() {
loadingScreen.style.transform = "scale(0.85)";
@@ -831,33 +829,32 @@ var blah;
async function initBlahs() {
lang = lang.toLowerCase();
let res;
-
+
let path = window.location.pathname.replace("/login/index.html", "/");
-
+
try { //try user lang first
res = await fetchAsync(`${path}blah/${lang}.json`);
} catch (e) { //fallback to en-us
res = await fetchAsync(`${path}blah/en-us.json`);
}
blah = JSON.parse(res);
-
+
let blahTags = document.getElementsByTagName("blah");
for (let i = 0; i < blahTags.length; i++) {
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
}
blahTags = document.getElementsByClassName("blah");
- for (let i = 0; i < blahTags.length; i++) {
+ for (let i = 0; i < blahTags.length; i++) {
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
}
-
+
let placeholders = document.querySelectorAll("[placeholder]");
for (let i = 0; i < placeholders.length; i++) {
- if (placeholders[i].placeholder.startsWith("{blah("))
- {
+ if (placeholders[i].placeholder.startsWith("{blah(")) {
let value = placeholders[i].placeholder.split("{blah(")[1].split(")}")[0];
placeholders[i].placeholder = processBlah(value);
}
-
+
}
}
@@ -881,7 +878,7 @@ function processBlah(blahmessage) {
blahmessage = `:${blahmessage}`;
prepended = true;
}
-
+
let split = splitLimit(blahmessage, ":", 3);
let values = [];
@@ -891,14 +888,14 @@ function processBlah(blahmessage) {
}
} catch (e) {
}
-
+
let message = blah[split[1]];
if (message === undefined) throw new Error();
-
+
let valueslist = "";
for (let i = 0; i < values.length; i++) {
let value = processBlah(values[i]);
- valueslist+=`${value}, `;
+ valueslist += `${value}, `;
message = message.replaceAll(`{${i}}`, value);
}
if (values.length > 0) {
@@ -908,9 +905,7 @@ function processBlah(blahmessage) {
message = message.replaceAll('{all}', valueslist);
}
return message;
- }
- catch (e)
- {
+ } catch (e) {
if (prepended) {
return blahmessage.substring(1);
}
@@ -962,7 +957,7 @@ async function generateUserKeysBundle() {
privX25519: uint8ToBase64(privX25519),
privMlKem: uint8ToBase64(privMlKem)
};
- return { pub, priv };
+ return {pub, priv};
}
async function ensureUserKeys() {
@@ -1060,7 +1055,7 @@ async function ensureUserKeys() {
async function mainJS() {
await initBlahs();
-
+
passwordHash = await hashSHA3_512(password);
if (localStorage.getItem('lang') != null) {
lang = localStorage.getItem('lang');
@@ -1082,6 +1077,7 @@ async function mainJS() {
await start();
}
+
id = localStorage.getItem('id');
username = localStorage.getItem('username');
password = localStorage.getItem('password');
@@ -1104,8 +1100,7 @@ function showFixedContextMenu(rect, html) {
let placeholders = doc.querySelectorAll("[placeholder]");
for (let i = 0; i < placeholders.length; i++) {
- if (placeholders[i].placeholder.startsWith("{blah("))
- {
+ if (placeholders[i].placeholder.startsWith("{blah(")) {
let value = placeholders[i].placeholder.split("{blah(")[1].split(")}")[0];
placeholders[i].placeholder = processBlah(value);
}
@@ -1114,10 +1109,31 @@ function showFixedContextMenu(rect, html) {
fixedContextMenu.innerHTML = doc.body.innerHTML;
- fixedContextMenu.style.left = `${rect.right + 10}px`;
- fixedContextMenu.style.top = `${rect.top}px`;
+ fixedContextMenu.style.transition = 'none';
+ fixedContextMenu.style.opacity = '0';
+ fixedContextMenu.style.left = '0px';
+ fixedContextMenu.style.top = '0px';
fixedContextMenu.classList.add("show");
+
+ let menuRect = fixedContextMenu.getBoundingClientRect();
+
+ let newLeft = rect.right + 10;
+ let newTop = rect.top;
+
+ if (newLeft + menuRect.width > window.innerWidth) {
+ newLeft = window.innerWidth - menuRect.width - 10;
+ }
+ if (newTop + menuRect.height > window.innerHeight) {
+ newTop = window.innerHeight - menuRect.height - 10;
+ }
+
+ fixedContextMenu.style.left = `${newLeft}px`;
+ fixedContextMenu.style.top = `${newTop}px`;
+
+ fixedContextMenu.offsetHeight;
+ fixedContextMenu.style.transition = '';
+ fixedContextMenu.style.opacity = '';
}
var currentRoomsBarTitle = null;
@@ -1127,20 +1143,20 @@ async function switchRoomsBar(title, content) {
if (currentRoomsBarTitle === title && currentRoomsBarHtml === content) return;
currentRoomsBarTitle = title;
currentRoomsBarHtml = content;
-
+
let roomsTopBarTransition = roomsTopBar.children.item(0);
-
+
roomsBar.style.transform = "scale(0.85)";
roomsBar.style.opacity = "0";
roomsTopBarTransition.style.transform = "scale(0.85)";
roomsTopBarTransition.style.opacity = "0";
-
+
await delay(200);
roomsTopBarTransition.innerHTML = processBlah(title);
-
+
let parser = new DOMParser();
let doc = parser.parseFromString(content, "text/html");
@@ -1156,8 +1172,7 @@ async function switchRoomsBar(title, content) {
let placeholders = doc.querySelectorAll("[placeholder]");
for (let i = 0; i < placeholders.length; i++) {
- if (placeholders[i].placeholder.startsWith("{blah("))
- {
+ if (placeholders[i].placeholder.startsWith("{blah(")) {
let value = placeholders[i].placeholder.split("{blah(")[1].split(")}")[0];
placeholders[i].placeholder = processBlah(value);
}
@@ -1171,25 +1186,26 @@ async function switchRoomsBar(title, content) {
roomsTopBarTransition.style.transform = "";
roomsTopBarTransition.style.opacity = "";
}
+
var currentRoomContentTitle = null;
var currentRoomContentHtml = null;
-async function switchRoomContent(title, content, showRoomBar, icon = "", skipMobileSlide = false)
-{
+
+async function switchRoomContent(title, content, showRoomBar, icon = "", skipMobileSlide = false) {
if (currentRoomContentTitle === title && currentRoomContentHtml === content) {
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
if (window.innerWidth <= 52 * rem && !skipMobileSlide) {
- if (title != "title.splash") { //do not show splash on mobile
- mainScreen.classList.remove('mobile-details');
- mainScreen.classList.add('mobile-content');
- }
+ if (title != "title.splash") { //do not show splash on mobile
+ mainScreen.classList.remove('mobile-details');
+ mainScreen.classList.add('mobile-content');
}
+ }
return;
}
currentRoomContentTitle = title;
currentRoomContentHtml = content;
-
+
let roomsTopBarTransition = roomTopBar.children.item(0);
-
+
roomContentMain.style.transform = "scale(0.85)";
roomContentMain.style.opacity = "0";
@@ -1203,13 +1219,11 @@ async function switchRoomContent(title, content, showRoomBar, icon = "", skipMob
mainScreen.classList.remove('mobile-details');
mainScreen.classList.add('mobile-content');
}
- }
- else
- {
+ } else {
await delay(200);
}
-
-
+
+
let detailsBtnHtml = showRoomBar ? detailsBtn : '';
roomsTopBarTransition.innerHTML = `${backBtnHtml}${icon}