1156 lines
No EOL
34 KiB
JavaScript
1156 lines
No EOL
34 KiB
JavaScript
var prot = window.location.protocol;
|
|
|
|
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) {
|
|
try {
|
|
JSON.parse(await fetchAsync(`https://${host}/_larpix/serverinfo`));
|
|
prot = "https:";
|
|
}
|
|
catch (error) {}
|
|
}
|
|
}
|
|
url = `${prot}//${host}/_larpix`;
|
|
}
|
|
|
|
console.log(prot);
|
|
|
|
var url = `${prot}//${window.location.hostname}/_larpix`;
|
|
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");
|
|
var addGroupBtn = document.getElementById("add-group-btn");
|
|
|
|
var sidebarHome = document.getElementById("sidebar-home");
|
|
var sidebarHomeButton = sidebarHome.children.item(1);
|
|
var sidebarHomeIndicator = sidebarHome.children.item(0);
|
|
|
|
var sidebarAdd = document.getElementById("sidebar-add");
|
|
var sidebarAddButton = sidebarAdd.children.item(1);
|
|
var sidebarAddIndicator = sidebarAdd.children.item(0);
|
|
var roomsBarContainer = document.getElementById("roomsbar");
|
|
|
|
var roomDetailsBar = document.getElementById("roomdetailsbar");
|
|
var roomContent = document.getElementsByTagName("roomcontent")[0];
|
|
var roomsBar = document.getElementsByTagName("roomcontent2")[0];
|
|
var sideBar = document.getElementsByTagName("sidebar")[0];
|
|
|
|
var roomContentMain = document.getElementsByTagName("roomcontent2")[1];
|
|
|
|
var roomDetailsMain = document.getElementsByTagName("roomcontent2")[2];
|
|
|
|
var roomContentBar = roomContent.children[0];
|
|
|
|
var roomsTopBar = document.getElementsByTagName("roomtopbar")[0];
|
|
var roomTopBar = document.getElementsByTagName("roomtopbar")[1];
|
|
var detailsTopBar = document.getElementsByTagName("roomtopbar")[2];
|
|
|
|
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) {
|
|
}
|
|
|
|
|
|
function delay(time) {
|
|
return new Promise(resolve => setTimeout(resolve, time));
|
|
}
|
|
|
|
async function packetEncPass(pass, key, username) {
|
|
return await encryptWithNonce(pass, key, getNonce(username, key));
|
|
}
|
|
|
|
async function getNonce(username, key) {
|
|
|
|
let nonce;
|
|
let fetchRes = await (await fetch(`${url}/nextnonce?u=${username}`)).text();
|
|
|
|
try {
|
|
nonce = await decryptString(fetchRes, key);
|
|
} catch (err) {
|
|
nonce = await decryptString(fetchRes, "");
|
|
}
|
|
return nonce;
|
|
}
|
|
|
|
async function encryptWithNonce(value, key, nonce) {
|
|
return await encryptString(value, nonce + key);
|
|
}
|
|
|
|
async function calcCommunicationKeyClient(p, g, pubServer) {
|
|
const secretClientBytes = window.crypto.getRandomValues(new Uint8Array(512));
|
|
const secretClient = BigInt('0x' + Array.from(secretClientBytes).map(b => b.toString(16).padStart(2, '0')).join(''));
|
|
const pubClient = power(BigInt(g), secretClient, BigInt(p));
|
|
const sharedSecret = power(BigInt(pubServer), secretClient, BigInt(p));
|
|
|
|
let sharedSecretStr = sharedSecret.toString(16);
|
|
|
|
if (sharedSecretStr.length % 2 !== 0) {
|
|
sharedSecretStr = '0' + sharedSecretStr;
|
|
}
|
|
|
|
const sharedSecretBytes = new Uint8Array(sharedSecretStr.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
|
|
|
const hashBuffer = await window.crypto.subtle.digest('SHA-256', sharedSecretBytes);
|
|
const aesKey = new Uint8Array(hashBuffer);
|
|
|
|
return [pubClient.toString(), aesKey];
|
|
}
|
|
|
|
function keyDataFromServerJson(jsonFromServer) {
|
|
const data = JSON.parse(jsonFromServer);
|
|
|
|
const p = BigInt(data.p);
|
|
const g = BigInt(data.g);
|
|
const pubServer = BigInt(data.pubServer);
|
|
|
|
return [p, g, pubServer, data.idKey]
|
|
}
|
|
|
|
function base64ToUint8(base64) {
|
|
return Uint8Array.from(atob(base64), c => c.charCodeAt(0));
|
|
}
|
|
|
|
function uint8ToBase64(uint8) {
|
|
return fixBase64Padding(btoa(String.fromCharCode(...uint8)));
|
|
}
|
|
|
|
async function encrypt(plainText, keyBytes) {
|
|
const iv = window.crypto.getRandomValues(new Uint8Array(16));
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(plainText);
|
|
|
|
const cryptoKey = await window.crypto.subtle.importKey(
|
|
"raw", keyBytes, "AES-CBC", false, ["encrypt"]
|
|
);
|
|
|
|
const encryptedContent = await window.crypto.subtle.encrypt(
|
|
{name: "AES-CBC", iv: iv},
|
|
cryptoKey,
|
|
data
|
|
);
|
|
const result = new Uint8Array(iv.length + encryptedContent.byteLength);
|
|
result.set(iv);
|
|
result.set(new Uint8Array(encryptedContent), iv.length);
|
|
|
|
return uint8ToBase64(result);
|
|
}
|
|
|
|
async function decrypt(cipherTextBase64, keyBytes) {
|
|
const fullData = base64ToUint8(cipherTextBase64);
|
|
const iv = fullData.slice(0, 16);
|
|
const cipherData = fullData.slice(16);
|
|
|
|
const cryptoKey = await window.crypto.subtle.importKey(
|
|
"raw", keyBytes, "AES-CBC", false, ["decrypt"]
|
|
);
|
|
|
|
const decrypted = await window.crypto.subtle.decrypt(
|
|
{name: "AES-CBC", iv: iv},
|
|
cryptoKey,
|
|
cipherData
|
|
);
|
|
|
|
return new TextDecoder().decode(decrypted);
|
|
}
|
|
|
|
|
|
async function getCryptoKey(passphrase) {
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(passphrase);
|
|
const hash = await crypto.subtle.digest('SHA-256', data);
|
|
return await crypto.subtle.importKey('raw', hash, {name: 'AES-CBC'}, false, ['encrypt', 'decrypt']);
|
|
}
|
|
|
|
async function encryptBytes(plainBytes, passphrase) {
|
|
const key = await getCryptoKey(passphrase);
|
|
const iv = crypto.getRandomValues(new Uint8Array(16));
|
|
|
|
const encryptedContent = await crypto.subtle.encrypt(
|
|
{name: 'AES-CBC', iv: iv},
|
|
key,
|
|
plainBytes
|
|
);
|
|
|
|
const result = new Uint8Array(iv.length + encryptedContent.byteLength);
|
|
result.set(iv);
|
|
result.set(new Uint8Array(encryptedContent), iv.length);
|
|
return result;
|
|
}
|
|
|
|
async function decryptBytes(combinedBytes, passphrase) {
|
|
const key = await getCryptoKey(passphrase);
|
|
const iv = combinedBytes.slice(0, 16);
|
|
const data = combinedBytes.slice(16);
|
|
|
|
const decryptedContent = await crypto.subtle.decrypt(
|
|
{name: 'AES-CBC', iv: iv},
|
|
key,
|
|
data
|
|
);
|
|
|
|
return new Uint8Array(decryptedContent);
|
|
}
|
|
|
|
async function encryptString(plainText, passphrase) {
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(plainText);
|
|
const pwHash = await crypto.subtle.digest('SHA-256', encoder.encode(passphrase));
|
|
|
|
const key = await crypto.subtle.importKey(
|
|
'raw', pwHash, {name: 'AES-CBC'}, false, ['encrypt']
|
|
);
|
|
|
|
const iv = crypto.getRandomValues(new Uint8Array(16));
|
|
const encrypted = await crypto.subtle.encrypt(
|
|
{name: 'AES-CBC', iv: iv},
|
|
key,
|
|
data
|
|
);
|
|
|
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
combined.set(iv);
|
|
combined.set(new Uint8Array(encrypted), iv.length);
|
|
|
|
return fixBase64Padding(btoa(String.fromCharCode(...combined)));
|
|
}
|
|
|
|
async function decryptString(base64Text, passphrase) {
|
|
const encoder = new TextEncoder();
|
|
const combined = new Uint8Array(atob(base64Text).split("").map(c => c.charCodeAt(0)));
|
|
|
|
const pwHash = await crypto.subtle.digest('SHA-256', encoder.encode(passphrase));
|
|
const key = await crypto.subtle.importKey(
|
|
'raw', pwHash, {name: 'AES-CBC'}, false, ['decrypt']
|
|
);
|
|
|
|
const iv = combined.slice(0, 16);
|
|
const data = combined.slice(16);
|
|
|
|
const decrypted = await crypto.subtle.decrypt(
|
|
{name: 'AES-CBC', iv: iv},
|
|
key,
|
|
data
|
|
);
|
|
|
|
return new TextDecoder().decode(decrypted);
|
|
}
|
|
|
|
|
|
async function fetchPost(url, value) {
|
|
let response = await fetch(url, {
|
|
method: "POST",
|
|
body: value,
|
|
headers: {
|
|
"secret": await encryptWithNonce(passwordHash, passwordHash, await getNonce(username, passwordHash))
|
|
}
|
|
});
|
|
let data = await response.text();
|
|
return data;
|
|
}
|
|
|
|
async function fetchPostEnc(url, value) {
|
|
let nonce = await getNonce(username, passwordHash);
|
|
let response = await fetch(url, {
|
|
method: "POST",
|
|
body: await encryptWithNonce(value, passwordHash, nonce),
|
|
headers: {
|
|
"secret": await encryptWithNonce(passwordHash, passwordHash, nonce)
|
|
}
|
|
});
|
|
let data = await response.text();
|
|
return data;
|
|
}
|
|
|
|
async function fetchAsync(url) {
|
|
let response = await fetch(url, {
|
|
method: "GET",
|
|
});
|
|
|
|
let data = await response.text();
|
|
return data;
|
|
}
|
|
|
|
async function fetchAsyncWAuth(url) {
|
|
let response = await fetch(url, {
|
|
method: "GET",
|
|
headers: {
|
|
"secret": await encryptWithNonce(passwordHash, passwordHash, await getNonce(username, passwordHash))
|
|
}
|
|
});
|
|
|
|
let data = await response.text();
|
|
return data;
|
|
}
|
|
|
|
async function getServerInfo(host) {
|
|
console.log(`${prot}//${host}/_larpix/serverinfo`)
|
|
return JSON.parse(await fetchAsync(`${prot}//${host}/_larpix/serverinfo`));
|
|
}
|
|
|
|
async function Auth(username, password) {
|
|
let passwordHash = await hashSHA3_512(password);
|
|
let response = await fetch(`${url}/auth?u=${username}`, {
|
|
method: "GET",
|
|
headers: {
|
|
"secret": await encryptWithNonce(passwordHash, passwordHash, await getNonce(username, passwordHash))
|
|
}
|
|
});
|
|
|
|
let data = await response.text();
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
async function fetchEncrypted(request, body) {
|
|
|
|
let nonce = await getNonce(username, passwordHash);
|
|
|
|
let response = await fetch(`${url}/encryptedrequest?u=${username}`, {
|
|
method: "POST",
|
|
body: await encryptWithNonce(
|
|
JSON.stringify({
|
|
string1: request,
|
|
string2: body
|
|
})
|
|
|
|
, passwordHash, nonce),
|
|
headers: {
|
|
"secret": await encryptWithNonce(passwordHash, passwordHash, nonce)
|
|
}
|
|
});
|
|
let data = await response.text();
|
|
return decryptString(data, passwordHash);
|
|
}
|
|
|
|
function power(base, exponent, mod) {
|
|
let res = 1n;
|
|
base = base % mod;
|
|
while (exponent > 0n) {
|
|
if (exponent % 2n === 1n) res = (res * base) % mod;
|
|
base = (base * base) % mod;
|
|
exponent = exponent / 2n;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function fixBase64Padding(base64String) {
|
|
let str = base64String.replace(/-/g, '+').replace(/_/g, '/');
|
|
const pad = str.length % 4;
|
|
|
|
if (pad) {
|
|
if (pad === 1) {
|
|
throw new Error("");
|
|
}
|
|
str += '='.repeat(4 - pad);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
function showBlahNotification(blahmessage, duration = 3500) {
|
|
try {
|
|
let split = blahmessage.split(":");
|
|
showNotification(processBlah(blahmessage), split[0], duration);
|
|
} catch (e) {
|
|
showNotification(blahmessage, "info", duration);
|
|
}
|
|
}
|
|
|
|
function showNotification(message, type = 'info', duration = 3500) {
|
|
let container = document.getElementById('notification-container');
|
|
if (!container) {
|
|
container = document.createElement('div');
|
|
container.id = 'notification-container';
|
|
document.body.appendChild(container);
|
|
}
|
|
|
|
const notif = document.createElement('div');
|
|
notif.className = `notification ${type}`;
|
|
notif.textContent = message;
|
|
|
|
container.appendChild(notif);
|
|
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
notif.classList.add('show');
|
|
});
|
|
});
|
|
|
|
setTimeout(() => {
|
|
notif.classList.remove('show');
|
|
|
|
notif.addEventListener('transitionend', () => {
|
|
notif.remove();
|
|
});
|
|
}, duration);
|
|
}
|
|
|
|
|
|
function showAction(message, actionid) {
|
|
let container = document.getElementById('notification-container');
|
|
if (!container) {
|
|
container = document.createElement('div');
|
|
container.id = 'notification-container';
|
|
document.body.appendChild(container);
|
|
}
|
|
|
|
const notif = document.createElement('div');
|
|
notif.className = `notification action`;
|
|
notif.textContent = message;
|
|
notif.id = `notification-${actionid}`;
|
|
|
|
container.appendChild(notif);
|
|
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
notif.classList.add('show');
|
|
});
|
|
});
|
|
}
|
|
|
|
function clearAction(actionid) {
|
|
const notifications = document.querySelectorAll(`[id="notification-${actionid}"]`);
|
|
|
|
notifications.forEach(notif => {
|
|
notif.classList.remove('show');
|
|
|
|
notif.addEventListener('transitionend', () => {
|
|
notif.remove();
|
|
}, { once: true });
|
|
});
|
|
}
|
|
|
|
|
|
async function hashSHA3_512(input) {
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(input);
|
|
const hashBuffer = await crypto.subtle.digest('SHA-512', data); //-3 kiedys xddddddddddddddddd
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
return hashHex;
|
|
}
|
|
|
|
//placeholder pfp
|
|
function getInitials(name) {
|
|
const cleanName = (name || "").trim();
|
|
|
|
//name empty
|
|
if (!cleanName) return "";
|
|
|
|
const parts = cleanName.split(/\s+/);
|
|
|
|
//at least 2 words
|
|
if (parts.length >= 2) {
|
|
const firstInitial = parts[0][0];
|
|
const secondInitial = parts[parts.length - 1][0]; //from last word
|
|
return (firstInitial + secondInitial).toUpperCase();
|
|
}
|
|
|
|
//only 1 word with 1 letter
|
|
const word = parts[0];
|
|
if (word.length === 1) {
|
|
return word.toUpperCase();
|
|
}
|
|
|
|
//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++) {
|
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
}
|
|
const h = Math.abs(hash) % 360;
|
|
const s = 50;
|
|
const l = 65;
|
|
|
|
return `hsl(${h}, ${s}%, ${l}%)`;
|
|
}
|
|
function createAvatarSvg(name, size = 512) {
|
|
const initials = getInitials(name);
|
|
const color = stringToColor(name);
|
|
|
|
const svg = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
|
|
<rect width="100%" height="100%" fill="${color}" />
|
|
<text
|
|
x="50%"
|
|
y="50%"
|
|
fill="white"
|
|
font-family="Nunito"
|
|
font-size="${size * 0.45}"
|
|
text-anchor="middle"
|
|
dy=".35em">
|
|
${initials}
|
|
</text>
|
|
</svg>
|
|
`.trim();
|
|
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
|
|
}
|
|
|
|
async function getAvatarUrl(username)
|
|
{
|
|
try {
|
|
let pfpUrl = `${url}/user/storage/public/getentry?u=${username}&e=larp.profile.pfp`;
|
|
if ((await fetchAsync(pfpUrl)) == "")
|
|
{
|
|
throw Error();
|
|
}
|
|
return pfpUrl;
|
|
}
|
|
catch (e) {
|
|
return createAvatarSvg(username);
|
|
}
|
|
}
|
|
|
|
function updateLoadingStatus(message) {
|
|
loadingStatus.innerHTML = processBlah(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);
|
|
loadingScreen.style.display = "none";
|
|
mainScreen.style.display = "";
|
|
await delay(200);
|
|
mainScreen.style.transform = "";
|
|
mainScreen.style.opacity = "";
|
|
}
|
|
async function loadingFadeIn() {
|
|
|
|
loadingScreen.style.transform = "scale(0.85)";
|
|
loadingScreen.style.opacity = "0";
|
|
|
|
mainScreen.style.transform = "scale(0.85)";
|
|
mainScreen.style.opacity = "0";
|
|
await delay(200);
|
|
mainScreen.style.display = "none";
|
|
loadingScreen.style.display = "";
|
|
await delay(200);
|
|
loadingScreen.style.transform = "";
|
|
loadingScreen.style.opacity = "";
|
|
}
|
|
|
|
|
|
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++) {
|
|
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("))
|
|
{
|
|
let value = placeholders[i].placeholder.split("{blah(")[1].split(")}")[0];
|
|
placeholders[i].placeholder = processBlah(value);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
function processBlah(blahmessage) {
|
|
try {
|
|
if (!blahmessage.includes(":")) {
|
|
blahmessage = `:${blahmessage}`;
|
|
}
|
|
|
|
let split = blahmessage.split(":");
|
|
let values = [];
|
|
try {
|
|
values = split[2].split(";");
|
|
} catch (e) {
|
|
}
|
|
|
|
let message = blah[split[1]];
|
|
|
|
let valueslist = "";
|
|
for (let i = 0; i < values.length; i++) {
|
|
let value = processBlah(values[i]);
|
|
valueslist+=`${value}, `;
|
|
|
|
message = message.replaceAll(`{${i}}`, value);
|
|
}
|
|
valueslist = valueslist.slice(0, -2);
|
|
if (message.includes('{all}')) {
|
|
|
|
message = message.replaceAll('{all}', valueslist);
|
|
}
|
|
return message;
|
|
}
|
|
catch (e)
|
|
{
|
|
return blahmessage;
|
|
}
|
|
}
|
|
|
|
function getLang() {
|
|
return (navigator.language || navigator.languages[0]);
|
|
}
|
|
|
|
var password = "";
|
|
var username = "";
|
|
var passwordHash = "";
|
|
var host = "";
|
|
var lang = getLang();
|
|
|
|
async function mainJS() {
|
|
await initBlahs();
|
|
|
|
passwordHash = await hashSHA3_512(password);
|
|
if (localStorage.getItem('lang') != null) {
|
|
lang = localStorage.getItem('lang');
|
|
}
|
|
|
|
await start();
|
|
}
|
|
username = localStorage.getItem('username');
|
|
password = localStorage.getItem('password');
|
|
host = localStorage.getItem('host');
|
|
mainJS();
|
|
|
|
|
|
function showFixedContextMenu(rect, html) {
|
|
let parser = new DOMParser();
|
|
let doc = parser.parseFromString(html, "text/html");
|
|
|
|
let blahTags = doc.getElementsByTagName("blah");
|
|
for (let i = 0; i < blahTags.length; i++) {
|
|
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
|
}
|
|
blahTags = doc.getElementsByClassName("blah");
|
|
for (let i = 0; i < blahTags.length; i++) {
|
|
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
|
}
|
|
|
|
let placeholders = doc.querySelectorAll("[placeholder]");
|
|
for (let i = 0; i < placeholders.length; i++) {
|
|
if (placeholders[i].placeholder.startsWith("{blah("))
|
|
{
|
|
let value = placeholders[i].placeholder.split("{blah(")[1].split(")}")[0];
|
|
placeholders[i].placeholder = processBlah(value);
|
|
}
|
|
|
|
}
|
|
|
|
fixedContextMenu.innerHTML = doc.body.innerHTML;
|
|
|
|
fixedContextMenu.style.left = `${rect.right + 10}px`;
|
|
fixedContextMenu.style.top = `${rect.top}px`;
|
|
|
|
fixedContextMenu.classList.add("show");
|
|
}
|
|
|
|
var currentRoomsBarTitle = null;
|
|
var currentRoomsBarHtml = null;
|
|
|
|
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");
|
|
|
|
let blahTags = doc.getElementsByTagName("blah");
|
|
for (let i = 0; i < blahTags.length; i++) {
|
|
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
|
}
|
|
blahTags = doc.getElementsByClassName("blah");
|
|
for (let i = 0; i < blahTags.length; i++) {
|
|
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
|
}
|
|
|
|
let placeholders = doc.querySelectorAll("[placeholder]");
|
|
for (let i = 0; i < placeholders.length; i++) {
|
|
if (placeholders[i].placeholder.startsWith("{blah("))
|
|
{
|
|
let value = placeholders[i].placeholder.split("{blah(")[1].split(")}")[0];
|
|
placeholders[i].placeholder = processBlah(value);
|
|
}
|
|
}
|
|
|
|
roomsBar.innerHTML = doc.body.innerHTML;
|
|
|
|
roomsBar.style.transform = "";
|
|
roomsBar.style.opacity = "";
|
|
|
|
roomsTopBarTransition.style.transform = "";
|
|
roomsTopBarTransition.style.opacity = "";
|
|
}
|
|
var currentRoomContentTitle = null;
|
|
var currentRoomContentHtml = null;
|
|
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');
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
currentRoomContentTitle = title;
|
|
currentRoomContentHtml = content;
|
|
|
|
let roomsTopBarTransition = roomTopBar.children.item(0);
|
|
|
|
roomContentMain.style.transform = "scale(0.85)";
|
|
roomContentMain.style.opacity = "0";
|
|
|
|
roomsTopBarTransition.style.transform = "scale(0.85)";
|
|
roomsTopBarTransition.style.opacity = "0";
|
|
|
|
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');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await delay(200);
|
|
}
|
|
|
|
|
|
let detailsBtnHtml = showRoomBar ? detailsBtn : '';
|
|
|
|
roomsTopBarTransition.innerHTML = `${backBtnHtml}${icon}<space></space><inherit>${processBlah(title)}</inherit><div class="flex-spacer"></div>${detailsBtnHtml}`;
|
|
roomDetailsBar.style.display = showRoomBar ? "flex" : "none";
|
|
|
|
let parser = new DOMParser();
|
|
let doc = parser.parseFromString(content, "text/html");
|
|
|
|
let blahTags = doc.getElementsByTagName("blah");
|
|
for (let i = 0; i < blahTags.length; i++) {
|
|
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
|
}
|
|
blahTags = doc.getElementsByClassName("blah");
|
|
for (let i = 0; i < blahTags.length; i++) {
|
|
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
|
}
|
|
|
|
let placeholders = doc.querySelectorAll("[placeholder]");
|
|
for (let i = 0; i < placeholders.length; i++) {
|
|
if (placeholders[i].placeholder.startsWith("{blah("))
|
|
{
|
|
let value = placeholders[i].placeholder.split("{blah(")[1].split(")}")[0];
|
|
placeholders[i].placeholder = processBlah(value);
|
|
}
|
|
}
|
|
|
|
roomContentMain.innerHTML = doc.body.innerHTML;
|
|
|
|
roomContentMain.style.transform = "";
|
|
roomContentMain.style.opacity = "";
|
|
|
|
roomsTopBarTransition.style.transform = "";
|
|
roomsTopBarTransition.style.opacity = "";
|
|
|
|
|
|
}
|
|
var currentDetailsContentTitle = null;
|
|
var currentDetailsContentHtml = null;
|
|
async function switchDetailsContent(title, content)
|
|
{
|
|
if (currentDetailsContentTitle === title && currentDetailsContentHtml === content) return;
|
|
currentDetailsContentTitle = title;
|
|
currentDetailsContentHtml = content;
|
|
|
|
let roomsTopBarTransition = detailsTopBar.children.item(0);
|
|
|
|
roomDetailsMain.style.transform = "scale(0.85)";
|
|
roomDetailsMain.style.opacity = "0";
|
|
|
|
roomsTopBarTransition.style.transform = "scale(0.85)";
|
|
roomsTopBarTransition.style.opacity = "0";
|
|
|
|
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
if (window.innerWidth > 52 * rem) {
|
|
await delay(200);
|
|
}
|
|
|
|
|
|
roomsTopBarTransition.innerHTML = processBlah(title);
|
|
|
|
let parser = new DOMParser();
|
|
let doc = parser.parseFromString(content, "text/html");
|
|
|
|
let blahTags = doc.getElementsByTagName("blah");
|
|
for (let i = 0; i < blahTags.length; i++) {
|
|
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
|
}
|
|
blahTags = doc.getElementsByClassName("blah");
|
|
for (let i = 0; i < blahTags.length; i++) {
|
|
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
|
|
}
|
|
|
|
let placeholders = doc.querySelectorAll("[placeholder]");
|
|
for (let i = 0; i < placeholders.length; i++) {
|
|
if (placeholders[i].placeholder.startsWith("{blah("))
|
|
{
|
|
let value = placeholders[i].placeholder.split("{blah(")[1].split(")}")[0];
|
|
placeholders[i].placeholder = processBlah(value);
|
|
}
|
|
}
|
|
|
|
roomDetailsMain.innerHTML = doc.body.innerHTML;
|
|
|
|
roomDetailsMain.style.transform = "";
|
|
roomDetailsMain.style.opacity = "";
|
|
|
|
roomsTopBarTransition.style.transform = "";
|
|
roomsTopBarTransition.style.opacity = "";
|
|
|
|
|
|
}
|
|
function clickCollapseDms()
|
|
{
|
|
var collapseDmsBtn = document.getElementById("collapse-dms");
|
|
|
|
collapseDmsBtn.classList.toggle("collapsed");
|
|
}
|
|
function clickCollapseGroups()
|
|
{
|
|
var collapseGroupsBtn = document.getElementById("collapse-groups");
|
|
|
|
collapseGroupsBtn.classList.toggle("collapsed");
|
|
}
|
|
function clickAddGroup()
|
|
{
|
|
switchRoomContent("title.create.group", createGroupScreen, false);
|
|
}
|
|
function clickAddDm()
|
|
{
|
|
switchRoomContent("title.create.dm", addDmScreen, false);
|
|
}
|
|
|
|
|
|
var currentMainIndicator = null;
|
|
sidebarAddButton.addEventListener("click", (e) => {
|
|
e.stopPropagation();
|
|
history.pushState({trap: true}, "", location.href);
|
|
|
|
if (fixedContextMenu.classList.contains("show")) {
|
|
fixedContextMenu.classList.remove("show");
|
|
|
|
setActiveSidebarIndicator(currentMainIndicator);
|
|
} else {
|
|
setActiveSidebarIndicator(sidebarAddIndicator, true);
|
|
const rect = sidebarAddButton.getBoundingClientRect();
|
|
showFixedContextMenu(rect, addSpaceMenu);
|
|
}
|
|
});
|
|
|
|
document.addEventListener("click", (e) => {
|
|
history.pushState({trap: true}, "", location.href);
|
|
|
|
if (fixedContextMenu.classList.contains("show")) {
|
|
if (!fixedContextMenu.contains(e.target)) {
|
|
fixedContextMenu.classList.remove("show");
|
|
if (!e.target.closest('sidebarelement')) {
|
|
setActiveSidebarIndicator(currentMainIndicator);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mainScreen && mainScreen.classList.contains('mobile-details')) {
|
|
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
if (window.innerWidth <= 52 * rem) {
|
|
if (!roomDetailsBar.contains(e.target) && !e.target.closest('.mobile-nav-btn.right')) {
|
|
mobileNavBack();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
window.addEventListener("popstate", (e) => {
|
|
popstate();
|
|
});
|
|
|
|
const { App } = window.Capacitor?.Plugins || {};
|
|
if (App) {
|
|
App.addListener('backButton', () => {
|
|
popstate();
|
|
});
|
|
}
|
|
function popstate()
|
|
{
|
|
if (!mainScreen.classList.contains('mobile-content') && !mainScreen.classList.contains('mobile-details'))
|
|
{
|
|
history.back();
|
|
if (App) {
|
|
App.minimizeApp();
|
|
}
|
|
}
|
|
mobileNavBack();
|
|
}
|
|
|
|
|
|
function gotoSideProfilePopup() {
|
|
|
|
}
|
|
function setActiveRoombarItem(itemId) {
|
|
let items = document.querySelectorAll('.collapse-text-button');
|
|
items.forEach(item => item.classList.remove('selected'));
|
|
|
|
if (itemId) {
|
|
let activeItem = document.getElementById(itemId);
|
|
if (activeItem) activeItem.classList.add('selected');
|
|
}
|
|
}
|
|
|
|
function switchInvitesTab(tab) {
|
|
if (!document.getElementById(`tab-invites-${tab}`).classList.contains('tab-inactive')) return;
|
|
document.getElementById('tab-invites-received').classList.add('tab-inactive');
|
|
document.getElementById('tab-invites-sent').classList.add('tab-inactive');
|
|
document.getElementById(`tab-invites-${tab}`).classList.remove('tab-inactive');
|
|
|
|
|
|
if (tab == "received")
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
function switchNotifisTab(tab) {
|
|
if (!document.getElementById(`tab-notifis-${tab}`).classList.contains('tab-inactive')) return;
|
|
document.getElementById('tab-notifis-all').classList.add('tab-inactive');
|
|
document.getElementById('tab-notifis-unread').classList.add('tab-inactive');
|
|
document.getElementById(`tab-notifis-${tab}`).classList.remove('tab-inactive');
|
|
}
|
|
|
|
|
|
async function gotoInbox() {
|
|
if (roomsBarContainer) roomsBarContainer.style.display = ""; //show roombar
|
|
setActiveSidebarIndicator(sidebarInboxIndicator);
|
|
|
|
await switchRoomsBar("title.inbox", inboxRoomBar);
|
|
|
|
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
if (window.innerWidth > 52 * rem) {
|
|
gotoInvites();
|
|
}
|
|
}
|
|
|
|
async function gotoInvites() {
|
|
setActiveRoombarItem('collapse-invites');
|
|
await switchRoomContent("title.invites", invitesScreen, false);
|
|
switchInvitesTab('received');
|
|
}
|
|
|
|
async function gotoNotifis() {
|
|
setActiveRoombarItem('collapse-notifis');
|
|
await switchRoomContent("title.notifications", notifisScreen, false);
|
|
switchNotifisTab('all');
|
|
}
|
|
|
|
function gotoCreateSpace() {
|
|
fixedContextMenu.classList.remove("show");
|
|
if (roomsBarContainer) roomsBarContainer.style.display = "none"; //hide roombar
|
|
switchRoomContent("title.create.space", createSpaceScreen, false);
|
|
}
|
|
|
|
function gotoJoinSpace() {
|
|
fixedContextMenu.classList.remove("show");
|
|
if (roomsBarContainer) roomsBarContainer.style.display = "none"; //hide roombar
|
|
switchRoomContent("title.join.space", joinSpaceScreen, false);
|
|
}
|
|
|
|
function gotoHome() {
|
|
if (roomsBarContainer) roomsBarContainer.style.display = ""; //show roombar
|
|
setActiveSidebarIndicator(sidebarHomeIndicator);
|
|
switchRoomContent("title.splash", splashScreen, false);
|
|
switchRoomsBar("title.home", homeRoomBar);
|
|
setActiveRoombarItem(null);
|
|
}
|
|
|
|
function setActiveSidebarIndicator(element, isTemporary = false) {
|
|
let indicators = document.getElementsByTagName("indicator");
|
|
for (let i = 0; i < indicators.length; i++) {
|
|
indicators[i].classList.remove("active");
|
|
}
|
|
|
|
if (element) {
|
|
element.classList.add("active");
|
|
}
|
|
|
|
if (!isTemporary && element) {
|
|
currentMainIndicator = element;
|
|
}
|
|
}
|
|
function mobileNavBack() {
|
|
if(mainScreen.classList.contains('mobile-details')) {
|
|
mainScreen.classList.remove('mobile-details');
|
|
mainScreen.classList.add('mobile-content');
|
|
|
|
setActiveSidebarIndicator(currentMainIndicator);
|
|
} else {
|
|
setActiveRoombarItem(null);
|
|
if (roomsBarContainer) {
|
|
roomsBarContainer.style.display = ""; //restore roombar on mobile
|
|
void roomsBarContainer.offsetWidth;
|
|
}
|
|
|
|
mainScreen.classList.remove('mobile-content');
|
|
|
|
setActiveSidebarIndicator(currentMainIndicator);
|
|
}
|
|
}
|
|
function mobileNavDetails() {
|
|
mainScreen.classList.add('mobile-details');
|
|
}
|
|
|
|
|
|
let touchStartX = 0;
|
|
let touchEndX = 0;
|
|
let touchStartY = 0;
|
|
let touchEndY = 0;
|
|
let touchMoved = false;
|
|
|
|
document.addEventListener('touchstart', e => {
|
|
touchStartX = e.changedTouches[0].screenX;
|
|
touchStartY = e.changedTouches[0].screenY;
|
|
touchMoved = false;
|
|
|
|
activeTouchButton = e.target.closest('button, input');
|
|
if (activeTouchButton) {
|
|
activeTouchButton.classList.add('is-pressed');
|
|
}
|
|
}, { passive: true });
|
|
|
|
document.addEventListener('touchmove', e => {
|
|
if (!touchMoved) {
|
|
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
let diffX = Math.abs(e.changedTouches[0].screenX - touchStartX);
|
|
let diffY = Math.abs(e.changedTouches[0].screenY - touchStartY);
|
|
if (diffX > rem || diffY > rem) {
|
|
touchMoved = true;
|
|
|
|
if (activeTouchButton) {
|
|
activeTouchButton.classList.remove('is-pressed');
|
|
activeTouchButton = null;
|
|
}
|
|
}
|
|
}
|
|
}, { passive: true });
|
|
|
|
document.addEventListener('touchend', e => {
|
|
touchEndX = e.changedTouches[0].screenX;
|
|
touchEndY = e.changedTouches[0].screenY;
|
|
handleMobileSwipe();
|
|
|
|
if (activeTouchButton) {
|
|
activeTouchButton.classList.remove('is-pressed');
|
|
|
|
if (!touchMoved) {
|
|
if (activeTouchButton.tagName !== 'INPUT') {
|
|
if (document.activeElement && document.activeElement.tagName === 'INPUT') {
|
|
document.activeElement.blur();
|
|
}
|
|
|
|
if (e.cancelable) {
|
|
e.preventDefault();
|
|
}
|
|
activeTouchButton.click();
|
|
} else {
|
|
activeTouchButton.focus();
|
|
}
|
|
}
|
|
activeTouchButton = null;
|
|
}
|
|
});
|
|
|
|
document.addEventListener('touchcancel', () => {
|
|
if (activeTouchButton) {
|
|
activeTouchButton.classList.remove('is-pressed');
|
|
activeTouchButton = null;
|
|
}
|
|
});
|
|
|
|
document.addEventListener('contextmenu', e => {
|
|
if (e.target.closest('button')) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
function handleMobileSwipe() {
|
|
const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
if (window.innerWidth > 52 * rem) return;
|
|
|
|
let diffX = touchEndX - touchStartX;
|
|
let diffY = touchEndY - touchStartY;
|
|
|
|
if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 4 * rem) {
|
|
if (diffX > 0) {
|
|
mobileNavBack();
|
|
} else {
|
|
if (mainScreen.classList.contains('mobile-content') && roomDetailsBar.style.display !== 'none') {
|
|
mobileNavDetails();
|
|
}
|
|
}
|
|
}
|
|
} |