forked from olcxjas-softworks/LarpixClient
Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 802333a7aa | |||
| 2e2c080df4 | |||
| 005d2e2820 | |||
| 92edb123f3 | |||
| b24b36adec | |||
| aa0351fb2d | |||
| bba80a19df | |||
| c5004dd52b | |||
| 5c91c341a0 | |||
| f957a081f3 | |||
| be26703908 | |||
| a666cb9915 | |||
| 8f28879f18 | |||
| a660ba32bd | |||
| 9e6d128839 | |||
| 2979881908 | |||
| 81e222250d | |||
| 81145968a1 | |||
| 5a9875ea01 |
21 changed files with 3159 additions and 419 deletions
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
run: npx cap copy electron
|
||||
|
||||
- name: Build
|
||||
run: mkdir -p build && cd build && npx electron-packager ../electron miarven --platform=linux --arch=x64 --icon=../icons/icon.png --overwrite --asar && cp ../icons/icon.png ./miarven-linux-x64/icon.png && cp ../assets/Miarven.desktop ./miarven-linux-x64/Miarven.desktop
|
||||
run: mkdir -p build && cd build && npx electron-packager ../electron miarven --platform=linux --arch=x64 --icon=../icons/icon.png --overwrite --asar && cp ../icons/icon.png ./miarven-linux-x64/icon.png && cp ../assets/olcxja.miarven.desktop ./miarven-linux-x64/olcxja.miarven.desktop && cp ../assets/install.sh ./miarven-linux-x64/install.sh && chmod +x ./miarven-linux-x64/install.sh
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
|
|
|
|||
132
android/app/src/main/assets/public/blah/en-cat.json
Normal file
132
android/app/src/main/assets/public/blah/en-cat.json
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
{
|
||||
"user.not.found": "meowser not found :c",
|
||||
"dm.already.exists": "direct meowchat already exists :3",
|
||||
"cant.invite.urself": "you can't invite yourself, silly :3",
|
||||
"user.already.invited": "you have already invited this user meow",
|
||||
"user.invited": "user invited successfully :3",
|
||||
"cant.create.dm.without.invitation": "you can't create a dm without invitation ><",
|
||||
"dm.begin.notice": "at the beginning of a meowchat, you should always verify that the person you're talking to is the intended recipient",
|
||||
"failed.accept.dm": "failed to accept direct meowchat",
|
||||
"dm.accepted": "direct meowchat accepted",
|
||||
"no.invite.found": "you can't create a dm without invitation *hiss*",
|
||||
"unknown.error": "unknown error :o",
|
||||
"forbidden": "you do not have pawmission to perform this action *hiss*",
|
||||
"dm.key.too.large": "meowchat key too large :o",
|
||||
"dm.key.not.found": "meowchat key not found :c",
|
||||
"dm.key.updated": "meowchat key updated successfully :3",
|
||||
"body.too.large": "request body too large ><",
|
||||
"invite.revoked": "invite revoked successfully",
|
||||
"invite.declined": "invite declined successfully",
|
||||
"account.creation.request.expired": "cat creation request expired. try again",
|
||||
"invalid.username": "invalid cat name: {0}",
|
||||
"invalid.password": "invalid meow word: {0}",
|
||||
"incorrect.captcha": "incorrect captcha. try meowgain",
|
||||
"username.taken": "this cat name is already taken",
|
||||
"accounts.slots.full": "you can't create a new cat because all collars are used. try again later",
|
||||
"registration.disabled": "registration disabled",
|
||||
"account.created": "cat created successfully",
|
||||
"password.changed": "meow word changed successfully",
|
||||
"keys.updated": "nametag updated successfully",
|
||||
"unknown.request": "unknown request: {0}",
|
||||
"login.successful": "pawgin successful :3",
|
||||
"username.length": "cat name must be {0} characters long",
|
||||
"username.conditions.allowed": "cat name can only include {all}",
|
||||
"password.not.hashed.properly": "meow word is not hashed properly",
|
||||
"invalid.username.or.password": "invalid cat name or meow word",
|
||||
"account.not.exist": "cat with this name doesn't exist",
|
||||
"invalid.nonce": "invalid nonce. try again",
|
||||
"username.changed": "cat name changed successfully",
|
||||
"auth.failed.redirect.to.login": "failed to find owner. redirecting to pawgin...",
|
||||
"dm.refresh.failed": "failed to refresh direct meowchats",
|
||||
"chat.add.failed": "failed to add meowchat",
|
||||
"something.wrong.mayb.pass": "something went wrong... :c (probably wrong meow word)",
|
||||
"something.wrong": "something went wrong... :c",
|
||||
"passwords.not.match": "meow word do not match",
|
||||
"password.cant.empty": "meow word cannot be empty",
|
||||
"username.cant.empty": "cat name cannot be empty",
|
||||
"bad.request": "bad request!!!",
|
||||
|
||||
"letters": "letters",
|
||||
"numbers": "numbers",
|
||||
"underscores": "underscores",
|
||||
|
||||
"loading.connecting": "connecting...",
|
||||
"loading.loading": "loading...",
|
||||
"loading.done": "ready! :3",
|
||||
|
||||
"title.home": "bed",
|
||||
"title.dms": "direct meowchats",
|
||||
"title.groups": "clowder",
|
||||
"title.welcome.splash": "welpaw to Meowven",
|
||||
"desc.welcome.splash": "first Larpix client",
|
||||
"title.splash": "splash",
|
||||
"title.create.dm": "invite to direct meowchat",
|
||||
"title.create.space": "create house",
|
||||
"title.create.group": "create clowder",
|
||||
"title.add.chat": "add meowchat",
|
||||
"desc.add.chat": "add a private, encrypted meowchat by entering a cat name",
|
||||
"desc.create.group": "create a private, encrypted clowder",
|
||||
"placeholder.create.group.input": "my clowder",
|
||||
"placeholder.create.space.input": "my house",
|
||||
"title.name": "name",
|
||||
"desc.create.space": "create a house for your community",
|
||||
"desc.join.space": "join a community that fits you",
|
||||
"title.join.space": "join house",
|
||||
"title.space.id": "house id",
|
||||
"title.inbox": "inbox",
|
||||
"title.invites": "invites",
|
||||
"title.notifications": "notifications",
|
||||
"title.received": "received",
|
||||
"title.sent": "sent",
|
||||
"title.all": "all",
|
||||
"title.unread": "unread",
|
||||
|
||||
"desc.no.invites": "no invites found :c",
|
||||
"desc.no.notifications": "no notifications found :c",
|
||||
"desc.fetching.invites": "fetching invites...",
|
||||
"desc.fetching.notifications": "fetching notifications...",
|
||||
"desc.invite.dm.received": "{0} ({1}) invited you to meowchat",
|
||||
"desc.invite.dm.sent": "you invited {0} ({1}) to meowchat",
|
||||
"desc.invite.group.received": "{0} ({1}) invited you to a clowder",
|
||||
"desc.invite.group.sent": "you invited {0} ({1}) to a clowder",
|
||||
|
||||
"action.fetching.invites.sent": "fetching sent invites...",
|
||||
"action.fetching.invites.recv": "fetching received invites...",
|
||||
"action.dm.fetch": "fetching direct meowchats...",
|
||||
"action.auth": "authenticating...",
|
||||
"action.dm.adding": "adding...",
|
||||
"action.invite.revoking": "revoking invite...",
|
||||
"action.invite.accepting": "accepting invite...",
|
||||
"action.invite.declining": "declining invite...",
|
||||
|
||||
"title.sign.up": "sign up",
|
||||
"title.sign.in": "sign in",
|
||||
"title.sign.in.to": "sign in to your larpix instance",
|
||||
"title.server.host": "server host",
|
||||
"title.username": "meow name",
|
||||
"title.password": "meow word",
|
||||
"title.new.here.?": "new here?",
|
||||
"title.create.an.account": "create a cat",
|
||||
"join.larpix": "join Larpix",
|
||||
"title.create.new.account": "create new larpix cat account",
|
||||
"notice.use.strong.pass": "remember to use a strong meow word! tt will be used to lock your collar",
|
||||
"title.confirm.password": "confirm meow word",
|
||||
"title.already.registered.?": "already a kitty?",
|
||||
"title.back.to.login": "back to pawgin",
|
||||
"title.verify": "verify",
|
||||
"title.captcha.desc": "prove you are a kitty",
|
||||
"title.captcha.code": "captcha code",
|
||||
"title.invitation.code": "invitation code",
|
||||
"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...",
|
||||
"dm.open.failed": "failed to open meowchat :c",
|
||||
"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"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
14
android/app/src/main/assets/public/crypto-pq.js
Normal file
14
android/app/src/main/assets/public/crypto-pq.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -6,6 +6,7 @@
|
|||
<title>Miarven</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||
<script src="crypto-pq.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<loading>
|
||||
|
|
@ -85,7 +86,6 @@
|
|||
async function start() {
|
||||
updateLoadingStatus("loading.loading");
|
||||
try {
|
||||
gotoHome();
|
||||
if (host != null)
|
||||
{
|
||||
await updateProtocolAndUrl(host);
|
||||
|
|
@ -94,13 +94,21 @@
|
|||
{
|
||||
await updateProtocolAndUrl(window.location.hostname);
|
||||
}
|
||||
sidebarPfp.src = await getAvatarUrl(username);
|
||||
let localIdRes = await fetchAsync(`${url}/nametoid?u=${username}`);
|
||||
let localId = localIdRes.trim();
|
||||
sidebarPfp.src = await getAvatarUrl(localId, `${username}:${host}`);
|
||||
|
||||
showAction("Authenticating...", "startauth");
|
||||
showAction("action.auth", "startauth");
|
||||
let res = await Auth(username, password);
|
||||
clearAction("startauth");
|
||||
if (res.startsWith("success:")) {
|
||||
await refreshDms();
|
||||
try {
|
||||
await ensureUserKeys();
|
||||
} catch (e) {
|
||||
//if fails continue loading encryption may be broken
|
||||
console.error(e);
|
||||
}
|
||||
gotoHome();
|
||||
} else {
|
||||
showBlahNotification("error:auth.failed.redirect.to.login");
|
||||
await delay(2000);
|
||||
|
|
@ -116,13 +124,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function refreshDms() {
|
||||
async function refreshDms(waittime = 0) {
|
||||
try {
|
||||
|
||||
|
||||
showAction("Refreshing dms...", "dmrefresh");
|
||||
let res = await fetchEncrypted("user/dm/list", "");
|
||||
console.log(res);
|
||||
showAction("action.dm.fetch", "dmrefresh");
|
||||
await delay(waittime);
|
||||
if (window.cachedDms && typeof renderDms === 'function') {
|
||||
await renderDms(window.cachedDms);
|
||||
}
|
||||
let res = await fetchEncrypted("user/dm/list");
|
||||
window.cachedDms = res;
|
||||
if (typeof renderDms === 'function') {
|
||||
await renderDms(res);
|
||||
}
|
||||
clearAction("dmrefresh");
|
||||
}
|
||||
catch (e) {
|
||||
|
|
@ -133,9 +146,17 @@
|
|||
|
||||
async function addDm() {
|
||||
try {
|
||||
showAction("Adding...", "dmadd");
|
||||
let res = await fetchEncrypted("user/dm/invite", document.getElementById("addchat-username").value);
|
||||
console.log(res);
|
||||
showAction("action.dm.adding", "dmadd");
|
||||
let username = document.getElementById("addchat-username").value;
|
||||
let idRes = await fetchAsync(`${url}/nametoid?u=${username}`);
|
||||
if (idRes.startsWith("error") || idRes.trim() === "0" || idRes.trim() === "") {
|
||||
clearAction("dmadd");
|
||||
showBlahNotification("error:user.not.found");
|
||||
return;
|
||||
}
|
||||
let targetId = idRes.trim();
|
||||
let res = await fetchEncrypted("user/dm/invite", targetId);
|
||||
|
||||
clearAction("dmadd");
|
||||
showBlahNotification(res);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<title>Miarven - Login</title>
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
<link rel="icon" type="image/svg+xml" href="../favicon.svg">
|
||||
<script src="../crypto-pq.js"></script>
|
||||
<style>
|
||||
html {
|
||||
font-size: max(17px, calc(100vw / 100));
|
||||
|
|
@ -321,16 +322,20 @@
|
|||
formLogin.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
|
||||
await updateProtocolAndUrl(loginHost.value);
|
||||
let res = await Auth(loginUsername.value, loginPassword.value);
|
||||
|
||||
if (res.startsWith("success:"))
|
||||
{
|
||||
showBlahNotification(res);
|
||||
let parts = res.split("|");
|
||||
showBlahNotification(parts[0]);
|
||||
await delay(800);
|
||||
localStorage.setItem("username", loginUsername.value);
|
||||
localStorage.setItem("password", loginPassword.value);
|
||||
localStorage.setItem("host", loginHost.value);
|
||||
if (parts.length > 1) {
|
||||
localStorage.setItem("id", parts[1]);
|
||||
}
|
||||
|
||||
await loadingFadeIn();
|
||||
|
||||
|
|
@ -371,14 +376,18 @@
|
|||
container.className = 'auth-container show-register';
|
||||
return;
|
||||
}
|
||||
|
||||
await updateProtocolAndUrl(registerHost.value);
|
||||
|
||||
let dataarray = keyDataFromServerJson(await fetchAsync(`${url}/createaccount?step=init`));
|
||||
var sharedkey = await calcCommunicationKeyClient(dataarray[0], dataarray[1], dataarray[2]);
|
||||
sharedpvkey = sharedkey[1];
|
||||
createId = dataarray[3];
|
||||
var sharedkey = await calcHybridSharedKeyClient(dataarray[0], dataarray[1]);
|
||||
sharedpvkey = sharedkey[2];
|
||||
createId = dataarray[2];
|
||||
const captchaimage = await fetch(`${url}/createaccount?step=register`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
pubClient: sharedkey[0],
|
||||
pubX25519: sharedkey[0],
|
||||
ciphertextMlKem: sharedkey[1],
|
||||
idKey: createId,
|
||||
username: await encrypt(registerUsername.value, sharedpvkey),
|
||||
password: await encrypt(await hashSHA3_512(registerPassword.value), sharedpvkey)
|
||||
|
|
@ -428,6 +437,12 @@
|
|||
if (res.startsWith("success")) {
|
||||
showBlahNotification(res);
|
||||
await delay(1000);
|
||||
|
||||
let resolvedId = await fetchAsync(`${url}/nametoid?u=${registerUsername.value}`);
|
||||
if (resolvedId && !resolvedId.startsWith("error:")) {
|
||||
localStorage.setItem("id", resolvedId.split(":")[0]);
|
||||
}
|
||||
|
||||
localStorage.setItem("username", registerUsername.value);
|
||||
localStorage.setItem("password", registerPassword.value);
|
||||
localStorage.setItem("host", registerHost.value);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -73,30 +73,73 @@ var joinSpaceScreen = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
var invitesEmptyState = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>
|
||||
<br>
|
||||
<p><blah>desc.no.invites</blah></p>
|
||||
`;
|
||||
var invitesLoadingState = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>
|
||||
<br>
|
||||
<p><blah>desc.fetching.invites</blah></p>
|
||||
`;
|
||||
|
||||
var invitesScreen = `
|
||||
<div style="display: flex; flex-direction: column; height: 100%;">
|
||||
<div style="display: flex; gap: 0.5rem; padding: 0.5rem 1rem; border-bottom: var(--border-width) solid var(--light-border-color); flex-shrink: 0;">
|
||||
<button id="tab-invites-received" class="submit-button blah tab-inactive" onclick="switchInvitesTab('received')" style="margin:0; padding: 0 1.5rem;">title.received</button>
|
||||
<button id="tab-invites-sent" class="submit-button blah tab-inactive" onclick="switchInvitesTab('sent')" style="margin:0; padding: 0 1.5rem;">title.sent</button>
|
||||
</div>
|
||||
<div id="invites-container" style="flex-grow: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; opacity: 0.5;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>
|
||||
<br>
|
||||
<p><blah>desc.no.invites</blah></p>
|
||||
<div id="invites-container" class="list-container empty">
|
||||
${invitesLoadingState}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
var notifisEmptyState = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-200v-80h80v-280q0-83 50-147.5T420-792v-28q0-25 17.5-42.5T480-880q25 0 42.5 17.5T540-820v28q80 20 130 84.5T720-560v280h80v80H160Zm320-300Zm0 420q-33 0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80ZM320-280h320v-280q0-66-47-113t-113-47q-66 0-113 47t-47 113v280Z"/></svg>
|
||||
<br>
|
||||
<p><blah>desc.no.notifications</blah></p>
|
||||
`;
|
||||
|
||||
var notifisScreen = `
|
||||
<div style="display: flex; flex-direction: column; height: 100%;">
|
||||
<div style="display: flex; gap: 0.5rem; padding: 0.5rem 1rem; border-bottom: var(--border-width) solid var(--light-border-color); flex-shrink: 0;">
|
||||
<button id="tab-notifis-all" class="submit-button blah tab-inactive" onclick="switchNotifisTab('all')" style="margin:0; padding: 0 1.5rem;">title.all</button>
|
||||
<button id="tab-notifis-unread" class="submit-button blah tab-inactive" onclick="switchNotifisTab('unread')" style="margin:0; padding: 0 1.5rem;">title.unread</button>
|
||||
</div>
|
||||
<div id="notifis-container" style="flex-grow: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; opacity: 0.5;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-200v-80h80v-280q0-83 50-147.5T420-792v-28q0-25 17.5-42.5T480-880q25 0 42.5 17.5T540-820v28q80 20 130 84.5T720-560v280h80v80H160Zm320-300Zm0 420q-33 0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80ZM320-280h320v-280q0-66-47-113t-113-47q-66 0-113 47t-47 113v280Z"/></svg>
|
||||
<br>
|
||||
<p><blah>desc.no.notifications</blah></p>
|
||||
<div id="notifis-container" class="list-container empty">
|
||||
${notifisEmptyState}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
var invitesEntry = `
|
||||
<div class="invite-entry">
|
||||
<div class="invite-info">
|
||||
<img src="{pfp}" class="invite-pfp">
|
||||
<div class="invite-details">
|
||||
<span class="invite-name">{username}</span>
|
||||
<span class="invite-desc"><blah>{desc}</blah></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invite-actions">
|
||||
{actions}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
var chatScreen = `
|
||||
<div style="display: flex; flex-direction: column; height: 100%; width: 100%;">
|
||||
<div id="chat-messages" style="flex-grow: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
</div>
|
||||
<div style="padding: 1rem; border-top: var(--border-width) solid var(--light-border-color); display: flex; gap: 0.5rem;">
|
||||
<textarea id="chat-input" class="forminput" style="flex-grow: 1; margin: 0; resize: none; min-height: 2.5rem; max-height: 8rem; padding: 0.5rem;" placeholder="{blah(placeholder.message.input)}" onkeydown="handleChatInputKey(event)" oninput="handleChatInputResize(this)"></textarea>
|
||||
<button class="submit-button" onclick="sendMessage()" style="margin: 0; padding: 0; aspect-ratio: 1;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.9 -0.5 15.6 16" fill="none" width="1.5rem">
|
||||
<path stroke="var(--main-bg-color)" stroke-linecap="round" stroke-linejoin="round" d="m3.75 7.5 -1.875 5.625 11.25 -5.625L1.875 1.875l1.875 5.625zm0 0h3.75" stroke-width="1.1"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -119,6 +162,11 @@ var homeRoomBar = `
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="dms-wrapper" class="collapsible-wrapper">
|
||||
<div class="collapsible-inner">
|
||||
<div id="dms-list" class="list-container no-flex-grow empty" style="padding: 0 0.5rem;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section-header">
|
||||
<button class="collapse-text-button" id="collapse-groups" onclick="clickCollapseGroups()">
|
||||
|
|
@ -136,6 +184,11 @@ var homeRoomBar = `
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="groups-wrapper" class="collapsible-wrapper">
|
||||
<div class="collapsible-inner">
|
||||
<div id="groups-list" class="list-container no-flex-grow empty" style="padding: 0 0.5rem;"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
var inboxRoomBar = `
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
--button-height: 2.4rem;
|
||||
--border-width: 0.09rem;
|
||||
|
||||
--big-default: rgb(207 207 207);
|
||||
--big-red: rgb(202 0 0);
|
||||
--big-green: rgb(25 189 0);
|
||||
--big-default: rgb(207, 207, 207);
|
||||
--big-red: hsl(0, 60%, 55%);
|
||||
--big-green: hsl(120, 60%, 55%);
|
||||
}
|
||||
*:focus {
|
||||
outline: none;
|
||||
|
|
@ -71,7 +71,7 @@ loading {
|
|||
user-select: none !important;
|
||||
-webkit-user-drag: none !important;
|
||||
}
|
||||
button, input {
|
||||
button, input, textarea {
|
||||
border-radius: 0.8rem;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
margin: var(--button-margin);
|
||||
|
|
@ -92,7 +92,7 @@ button, input {
|
|||
touch-action: manipulation;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
input {
|
||||
input, textarea {
|
||||
padding-left: calc(var(--button-margin) * 2);
|
||||
-webkit-touch-callout: default;
|
||||
user-select: text !important;
|
||||
|
|
@ -148,6 +148,7 @@ indicator.active {
|
|||
width: calc(var(--icon-button-height) - (var(--border-width) * 2));
|
||||
height: calc(var(--icon-button-height) - (var(--border-width) * 2));
|
||||
pointer-events: none !important;
|
||||
border-radius: 0.65rem;
|
||||
}
|
||||
|
||||
roomcontent {
|
||||
|
|
@ -252,11 +253,11 @@ hr {
|
|||
box-shadow: 0 0.5rem 2rem rgba(0, 0, 0, 0.4);
|
||||
|
||||
opacity: 0;
|
||||
transform: translateY(-1rem);
|
||||
transform: translateY(-2rem);
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
background-color 0.2s ease;
|
||||
opacity 0.2s ease,
|
||||
transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
background-color 0.15s ease;
|
||||
}
|
||||
.notification.show {
|
||||
opacity: 1;
|
||||
|
|
@ -330,52 +331,7 @@ herotitle {
|
|||
.bottom-element {
|
||||
margin-top: auto;
|
||||
}
|
||||
@media (hover: hover) {
|
||||
sidebarelement:hover indicator:not(.active) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 1.2rem;
|
||||
left: -0.08rem;
|
||||
border: 0.15rem solid var(--text-color);
|
||||
border-radius: 0.8rem;
|
||||
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
|
||||
}
|
||||
button:hover, input:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.submit-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.add-action-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
button:active, button.is-pressed, input:active, input.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transform: var(--press-scale);
|
||||
}
|
||||
button.active, input.active {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.submit-button:active, .submit-button.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.add-action-button:active, .add-action-button.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-color);
|
||||
}
|
||||
sidebarelement:has(.icon-button:active) indicator:not(.active) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 1.2rem;
|
||||
left: -0.08rem;
|
||||
border: 0.15rem solid var(--text-color);
|
||||
border-radius: 0.8rem;
|
||||
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
|
||||
}
|
||||
|
||||
blah, inherit, .inherit {
|
||||
all: inherit;
|
||||
padding: 0;
|
||||
|
|
@ -419,9 +375,7 @@ space{
|
|||
gap: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.context-menu button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.mobile-nav-btn {
|
||||
display: none;
|
||||
background: transparent;
|
||||
|
|
@ -466,7 +420,7 @@ space{
|
|||
sidebar, sidebar.second, roomcontent {
|
||||
position: absolute !important;
|
||||
height: 100%;
|
||||
transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
transition: all 0.25s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
sidebar {
|
||||
|
|
@ -537,7 +491,7 @@ space{
|
|||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
transition: all 0.25s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
main.mobile-details roomcontent::after {
|
||||
|
|
@ -554,3 +508,217 @@ space{
|
|||
background-color: transparent;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.list-container {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.list-container.empty {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.list-container.no-flex-grow {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.collapsible-wrapper {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
transition: grid-template-rows 0.25s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
.collapsible-wrapper.collapsed {
|
||||
grid-template-rows: 0fr;
|
||||
}
|
||||
|
||||
.collapsible-inner {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.invite-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.8rem 1rem;
|
||||
border: var(--border-width) solid var(--light-border-color);
|
||||
border-radius: 0.8rem;
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.invite-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
.invite-pfp {
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
border-radius: 0.8rem;
|
||||
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
|
||||
object-fit: cover;
|
||||
}
|
||||
.invite-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
}
|
||||
.invite-name {
|
||||
font-weight: 700;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.invite-desc {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.invite-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.invite-actions button {
|
||||
margin: 0;
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
width: 3.1rem;
|
||||
height: 3.1rem;
|
||||
}
|
||||
|
||||
.room-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.1rem;
|
||||
padding: 1.5rem 0.6rem;
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
.room-pfp {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 0.65rem;
|
||||
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
.room-name {
|
||||
font-weight: 600;
|
||||
font-size: 1.05rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
.chat-message.with-avatar {
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
.chat-message-pfp {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 0.65rem;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.chat-message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.chat-message-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.2rem;
|
||||
display: none; /* hidden for consecutive */
|
||||
}
|
||||
.chat-message.with-avatar .chat-message-header {
|
||||
display: flex;
|
||||
}
|
||||
.chat-message-author {
|
||||
font-weight: bold;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.chat-message-timestamp {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.chat-message-text {
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
sidebarelement:hover indicator:not(.active) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 1.2rem;
|
||||
left: -0.08rem;
|
||||
border: 0.15rem solid var(--text-color);
|
||||
border-radius: 0.8rem;
|
||||
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
|
||||
}
|
||||
button:hover, input:hover, textarea:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.submit-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.add-action-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-color);
|
||||
}
|
||||
.context-menu button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.invite-actions button:hover {
|
||||
filter: brightness(0.93);
|
||||
}
|
||||
}
|
||||
button:active, button.is-pressed, input:active, input.is-pressed, textarea:active, textarea.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transform: var(--press-scale);
|
||||
}
|
||||
.invite-actions button:active {
|
||||
filter: brightness(0.85);
|
||||
}
|
||||
button.active, input.active, textarea.active {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.submit-button:active, .submit-button.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.add-action-button:active, .add-action-button.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-color);
|
||||
}
|
||||
.context-menu button:active, .context-menu button.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
sidebarelement:has(.icon-button:active) indicator:not(.active) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 1.2rem;
|
||||
left: -0.08rem;
|
||||
border: 0.15rem solid var(--text-color);
|
||||
border-radius: 0.8rem;
|
||||
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
[Desktop Entry]
|
||||
Name=Miarven
|
||||
Exec=bash -c 'DIR="$(dirname "%k")"; mkdir -p ~/.local/share/icons; cp "$DIR/icon.png" ~/.local/share/icons/olcxjamiarven.png; cd "$DIR" && ./miarven'
|
||||
Icon=olcxjamiarven
|
||||
Type=Application
|
||||
Categories=Internet
|
||||
StartupNotify=true
|
||||
StartupWMClass=olcxja.miarven
|
||||
40
assets/install.sh
Normal file
40
assets/install.sh
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "WARNING: Make sure you don't run this script in a folder that contains anything more than Miarven"
|
||||
echo "otherwise it may result in data loss"
|
||||
|
||||
read -r -p "Press Enter to continue (or Ctrl+C, to cancel)..."
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
BIN_DIR="$HOME/.local/bin/olcxja.miarven"
|
||||
APP_DIR="$HOME/.local/share/applications"
|
||||
|
||||
echo "1. Copying files..."
|
||||
mkdir -p "$BIN_DIR"
|
||||
mkdir -p "$APP_DIR"
|
||||
|
||||
cp -r "$SCRIPT_DIR/"* "$BIN_DIR/"
|
||||
|
||||
echo "2. Creating shortcut..."
|
||||
|
||||
mv "$BIN_DIR/olcxja.miarven.desktop" "$APP_DIR/olcxja.miarven.desktop"
|
||||
|
||||
echo "3. Adding executable permissions..."
|
||||
chmod +x "$APP_DIR/olcxja.miarven.desktop"
|
||||
chmod +x "$BIN_DIR/miarven"
|
||||
|
||||
echo "4. Cleaning installation..."
|
||||
|
||||
rm -f "$BIN_DIR/olcxja.miarven.desktop"
|
||||
rm -f "$BIN_DIR/install.sh"
|
||||
|
||||
echo "5. Installation completed. Cleaning up and launching..."
|
||||
|
||||
rm -rf "$SCRIPT_DIR"/*
|
||||
|
||||
"$BIN_DIR/miarven" &
|
||||
|
||||
exit 0
|
||||
8
assets/olcxja.miarven.desktop
Normal file
8
assets/olcxja.miarven.desktop
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[Desktop Entry]
|
||||
Name=Miarven
|
||||
Exec=~/.local/bin/olcxja.miarven/miarven
|
||||
Icon=olcxja.miarven
|
||||
Type=Application
|
||||
Categories=Internet
|
||||
StartupNotify=true
|
||||
StartupWMClass=olcxja.miarven
|
||||
|
|
@ -2,4 +2,6 @@ mkdir build
|
|||
cd build
|
||||
npx electron-packager ../electron miarven --platform=linux --arch=x64 --icon=../electron/assets/icon.png --overwrite --asar
|
||||
cp ../icons/icon.png ./miarven-linux-x64/icon.png
|
||||
cp ../assets/Miarven.desktop ./miarven-linux-x64/Miarven.desktop
|
||||
cp ../assets/olcxja.miarven.desktop ./miarven-linux-x64/olcxja.miarven.desktop
|
||||
cp ../assets/install.sh ./miarven-linux-x64/install.sh
|
||||
chmod +x ./miarven-linux-x64/install.sh
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"user.not.found": "meowser not found :c",
|
||||
"dm.already.exists": "direct meowchat already exists :3",
|
||||
"cant.invite.urself": "you can't invite yourself, silly :3",
|
||||
"user.already.invited": "you have already invited this user meow",
|
||||
"user.invited": "user invited successfully :3",
|
||||
|
|
@ -9,6 +10,13 @@
|
|||
"dm.accepted": "direct meowchat accepted",
|
||||
"no.invite.found": "you can't create a dm without invitation *hiss*",
|
||||
"unknown.error": "unknown error :o",
|
||||
"forbidden": "you do not have pawmission to perform this action *hiss*",
|
||||
"dm.key.too.large": "meowchat key too large :o",
|
||||
"dm.key.not.found": "meowchat key not found :c",
|
||||
"dm.key.updated": "meowchat key updated successfully :3",
|
||||
"body.too.large": "request body too large ><",
|
||||
"invite.revoked": "invite revoked successfully",
|
||||
"invite.declined": "invite declined successfully",
|
||||
"account.creation.request.expired": "cat creation request expired. try again",
|
||||
"invalid.username": "invalid cat name: {0}",
|
||||
"invalid.password": "invalid meow word: {0}",
|
||||
|
|
@ -73,6 +81,24 @@
|
|||
"title.all": "all",
|
||||
"title.unread": "unread",
|
||||
|
||||
"desc.no.invites": "no invites found :c",
|
||||
"desc.no.notifications": "no notifications found :c",
|
||||
"desc.fetching.invites": "fetching invites...",
|
||||
"desc.fetching.notifications": "fetching notifications...",
|
||||
"desc.invite.dm.received": "{0} ({1}) invited you to meowchat",
|
||||
"desc.invite.dm.sent": "you invited {0} ({1}) to meowchat",
|
||||
"desc.invite.group.received": "{0} ({1}) invited you to a clowder",
|
||||
"desc.invite.group.sent": "you invited {0} ({1}) to a clowder",
|
||||
|
||||
"action.fetching.invites.sent": "fetching sent invites...",
|
||||
"action.fetching.invites.recv": "fetching received invites...",
|
||||
"action.dm.fetch": "fetching direct meowchats...",
|
||||
"action.auth": "authenticating...",
|
||||
"action.dm.adding": "adding...",
|
||||
"action.invite.revoking": "revoking invite...",
|
||||
"action.invite.accepting": "accepting invite...",
|
||||
"action.invite.declining": "declining invite...",
|
||||
|
||||
"title.sign.up": "sign up",
|
||||
"title.sign.in": "sign in",
|
||||
"title.sign.in.to": "sign in to your larpix instance",
|
||||
|
|
@ -93,6 +119,14 @@
|
|||
"title.invitation.code": "invitation code",
|
||||
"title.back.to.register": "back to registration",
|
||||
"placeholder.username": "cat name",
|
||||
"placeholder.invitation.code": "invitation code",
|
||||
"placeholder.captcha.code": "captcha code"
|
||||
"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...",
|
||||
"dm.open.failed": "failed to open meowchat :c",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"user.not.found": "User not found",
|
||||
"dm.already.exists": "Direct message already exists",
|
||||
"cant.invite.urself": "You can't invite yourself",
|
||||
"user.already.invited": "You have already invited this user",
|
||||
"user.invited": "User invited successfully",
|
||||
|
|
@ -9,6 +10,13 @@
|
|||
"dm.accepted": "DM accepted",
|
||||
"no.invite.found": "You can't create a dm without invitation",
|
||||
"unknown.error": "Unknown error",
|
||||
"forbidden": "You do not have permission to perform this action",
|
||||
"dm.key.too.large": "DM key too large",
|
||||
"dm.key.not.found": "DM key not found",
|
||||
"dm.key.updated": "DM key updated successfully",
|
||||
"body.too.large": "Request body too large",
|
||||
"invite.revoked": "Invite revoked successfully",
|
||||
"invite.declined": "Invite declined successfully",
|
||||
"account.creation.request.expired": "Account creation request expired. Try again",
|
||||
"invalid.username": "Invalid username: {0}",
|
||||
"invalid.password": "Invalid password: {0}",
|
||||
|
|
@ -72,6 +80,23 @@
|
|||
"title.sent": "Sent",
|
||||
"title.all": "All",
|
||||
"title.unread": "Unread",
|
||||
"desc.no.invites": "No invites found",
|
||||
"desc.no.notifications": "No notifications found",
|
||||
"desc.fetching.invites": "Fetching invites...",
|
||||
"desc.fetching.notifications": "Fetching notifications...",
|
||||
"desc.invite.dm.received": "{0} ({1}) invited you to chat",
|
||||
"desc.invite.dm.sent": "You invited {0} ({1}) to chat",
|
||||
"desc.invite.group.received": "{0} ({1}) invited you to a group",
|
||||
"desc.invite.group.sent": "You invited {0} ({1}) to a group",
|
||||
|
||||
"action.fetching.invites.sent": "Fetching sent invites...",
|
||||
"action.fetching.invites.recv": "Fetching received invites...",
|
||||
"action.dm.fetch": "Fetching dms...",
|
||||
"action.auth": "Authenticating...",
|
||||
"action.dm.adding": "Adding...",
|
||||
"action.invite.revoking": "Revoking invite...",
|
||||
"action.invite.accepting": "Accepting invite...",
|
||||
"action.invite.declining": "Declining invite...",
|
||||
|
||||
"title.sign.up": "Sign Up",
|
||||
"title.sign.in": "Sign In",
|
||||
|
|
@ -93,6 +118,14 @@
|
|||
"title.invitation.code": "Invitation code",
|
||||
"title.back.to.register": "Back to registration",
|
||||
"placeholder.username": "username",
|
||||
"placeholder.invitation.code": "invitation code",
|
||||
"placeholder.captcha.code": "captcha code"
|
||||
"placeholder.captcha.code": "captcha code",
|
||||
"placeholder.message.input": "Message...",
|
||||
"desc.messages.loading": "Fetching messages...",
|
||||
"desc.no.dms": "No direct messages",
|
||||
"action.dm.opening": "Opening DM...",
|
||||
"dm.open.failed": "Failed to open DM",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
14
webroot/crypto-pq.js
Normal file
14
webroot/crypto-pq.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -6,6 +6,7 @@
|
|||
<title>Miarven</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||
<script src="crypto-pq.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<loading>
|
||||
|
|
@ -85,7 +86,6 @@
|
|||
async function start() {
|
||||
updateLoadingStatus("loading.loading");
|
||||
try {
|
||||
gotoHome();
|
||||
if (host != null)
|
||||
{
|
||||
await updateProtocolAndUrl(host);
|
||||
|
|
@ -94,13 +94,21 @@
|
|||
{
|
||||
await updateProtocolAndUrl(window.location.hostname);
|
||||
}
|
||||
sidebarPfp.src = await getAvatarUrl(username);
|
||||
let localIdRes = await fetchAsync(`${url}/nametoid?u=${username}`);
|
||||
let localId = localIdRes.trim();
|
||||
sidebarPfp.src = await getAvatarUrl(localId, `${username}:${host}`);
|
||||
|
||||
showAction("Authenticating...", "startauth");
|
||||
showAction("action.auth", "startauth");
|
||||
let res = await Auth(username, password);
|
||||
clearAction("startauth");
|
||||
if (res.startsWith("success:")) {
|
||||
await refreshDms();
|
||||
try {
|
||||
await ensureUserKeys();
|
||||
} catch (e) {
|
||||
//if fails continue loading encryption may be broken
|
||||
console.error(e);
|
||||
}
|
||||
gotoHome();
|
||||
} else {
|
||||
showBlahNotification("error:auth.failed.redirect.to.login");
|
||||
await delay(2000);
|
||||
|
|
@ -116,13 +124,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function refreshDms() {
|
||||
async function refreshDms(waittime = 0) {
|
||||
try {
|
||||
|
||||
|
||||
showAction("Refreshing dms...", "dmrefresh");
|
||||
let res = await fetchEncrypted("user/dm/list", "");
|
||||
console.log(res);
|
||||
showAction("action.dm.fetch", "dmrefresh");
|
||||
await delay(waittime);
|
||||
if (window.cachedDms && typeof renderDms === 'function') {
|
||||
await renderDms(window.cachedDms);
|
||||
}
|
||||
let res = await fetchEncrypted("user/dm/list");
|
||||
window.cachedDms = res;
|
||||
if (typeof renderDms === 'function') {
|
||||
await renderDms(res);
|
||||
}
|
||||
clearAction("dmrefresh");
|
||||
}
|
||||
catch (e) {
|
||||
|
|
@ -133,9 +146,17 @@
|
|||
|
||||
async function addDm() {
|
||||
try {
|
||||
showAction("Adding...", "dmadd");
|
||||
let res = await fetchEncrypted("user/dm/invite", document.getElementById("addchat-username").value);
|
||||
console.log(res);
|
||||
showAction("action.dm.adding", "dmadd");
|
||||
let username = document.getElementById("addchat-username").value;
|
||||
let idRes = await fetchAsync(`${url}/nametoid?u=${username}`);
|
||||
if (idRes.startsWith("error") || idRes.trim() === "0" || idRes.trim() === "") {
|
||||
clearAction("dmadd");
|
||||
showBlahNotification("error:user.not.found");
|
||||
return;
|
||||
}
|
||||
let targetId = idRes.trim();
|
||||
let res = await fetchEncrypted("user/dm/invite", targetId);
|
||||
|
||||
clearAction("dmadd");
|
||||
showBlahNotification(res);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<title>Miarven - Login</title>
|
||||
<link rel="stylesheet" href="../style.css">
|
||||
<link rel="icon" type="image/svg+xml" href="../favicon.svg">
|
||||
<script src="../crypto-pq.js"></script>
|
||||
<style>
|
||||
html {
|
||||
font-size: max(17px, calc(100vw / 100));
|
||||
|
|
@ -321,16 +322,20 @@
|
|||
formLogin.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
|
||||
await updateProtocolAndUrl(loginHost.value);
|
||||
let res = await Auth(loginUsername.value, loginPassword.value);
|
||||
|
||||
if (res.startsWith("success:"))
|
||||
{
|
||||
showBlahNotification(res);
|
||||
let parts = res.split("|");
|
||||
showBlahNotification(parts[0]);
|
||||
await delay(800);
|
||||
localStorage.setItem("username", loginUsername.value);
|
||||
localStorage.setItem("password", loginPassword.value);
|
||||
localStorage.setItem("host", loginHost.value);
|
||||
if (parts.length > 1) {
|
||||
localStorage.setItem("id", parts[1]);
|
||||
}
|
||||
|
||||
await loadingFadeIn();
|
||||
|
||||
|
|
@ -371,14 +376,18 @@
|
|||
container.className = 'auth-container show-register';
|
||||
return;
|
||||
}
|
||||
|
||||
await updateProtocolAndUrl(registerHost.value);
|
||||
|
||||
let dataarray = keyDataFromServerJson(await fetchAsync(`${url}/createaccount?step=init`));
|
||||
var sharedkey = await calcCommunicationKeyClient(dataarray[0], dataarray[1], dataarray[2]);
|
||||
sharedpvkey = sharedkey[1];
|
||||
createId = dataarray[3];
|
||||
var sharedkey = await calcHybridSharedKeyClient(dataarray[0], dataarray[1]);
|
||||
sharedpvkey = sharedkey[2];
|
||||
createId = dataarray[2];
|
||||
const captchaimage = await fetch(`${url}/createaccount?step=register`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
pubClient: sharedkey[0],
|
||||
pubX25519: sharedkey[0],
|
||||
ciphertextMlKem: sharedkey[1],
|
||||
idKey: createId,
|
||||
username: await encrypt(registerUsername.value, sharedpvkey),
|
||||
password: await encrypt(await hashSHA3_512(registerPassword.value), sharedpvkey)
|
||||
|
|
@ -428,6 +437,12 @@
|
|||
if (res.startsWith("success")) {
|
||||
showBlahNotification(res);
|
||||
await delay(1000);
|
||||
|
||||
let resolvedId = await fetchAsync(`${url}/nametoid?u=${registerUsername.value}`);
|
||||
if (resolvedId && !resolvedId.startsWith("error:")) {
|
||||
localStorage.setItem("id", resolvedId.split(":")[0]);
|
||||
}
|
||||
|
||||
localStorage.setItem("username", registerUsername.value);
|
||||
localStorage.setItem("password", registerPassword.value);
|
||||
localStorage.setItem("host", registerHost.value);
|
||||
|
|
|
|||
1068
webroot/main.js
1068
webroot/main.js
File diff suppressed because it is too large
Load diff
|
|
@ -73,30 +73,73 @@ var joinSpaceScreen = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
var invitesEmptyState = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>
|
||||
<br>
|
||||
<p><blah>desc.no.invites</blah></p>
|
||||
`;
|
||||
var invitesLoadingState = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>
|
||||
<br>
|
||||
<p><blah>desc.fetching.invites</blah></p>
|
||||
`;
|
||||
|
||||
var invitesScreen = `
|
||||
<div style="display: flex; flex-direction: column; height: 100%;">
|
||||
<div style="display: flex; gap: 0.5rem; padding: 0.5rem 1rem; border-bottom: var(--border-width) solid var(--light-border-color); flex-shrink: 0;">
|
||||
<button id="tab-invites-received" class="submit-button blah tab-inactive" onclick="switchInvitesTab('received')" style="margin:0; padding: 0 1.5rem;">title.received</button>
|
||||
<button id="tab-invites-sent" class="submit-button blah tab-inactive" onclick="switchInvitesTab('sent')" style="margin:0; padding: 0 1.5rem;">title.sent</button>
|
||||
</div>
|
||||
<div id="invites-container" style="flex-grow: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; opacity: 0.5;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>
|
||||
<br>
|
||||
<p><blah>desc.no.invites</blah></p>
|
||||
<div id="invites-container" class="list-container empty">
|
||||
${invitesLoadingState}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
var notifisEmptyState = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-200v-80h80v-280q0-83 50-147.5T420-792v-28q0-25 17.5-42.5T480-880q25 0 42.5 17.5T540-820v28q80 20 130 84.5T720-560v280h80v80H160Zm320-300Zm0 420q-33 0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80ZM320-280h320v-280q0-66-47-113t-113-47q-66 0-113 47t-47 113v280Z"/></svg>
|
||||
<br>
|
||||
<p><blah>desc.no.notifications</blah></p>
|
||||
`;
|
||||
|
||||
var notifisScreen = `
|
||||
<div style="display: flex; flex-direction: column; height: 100%;">
|
||||
<div style="display: flex; gap: 0.5rem; padding: 0.5rem 1rem; border-bottom: var(--border-width) solid var(--light-border-color); flex-shrink: 0;">
|
||||
<button id="tab-notifis-all" class="submit-button blah tab-inactive" onclick="switchNotifisTab('all')" style="margin:0; padding: 0 1.5rem;">title.all</button>
|
||||
<button id="tab-notifis-unread" class="submit-button blah tab-inactive" onclick="switchNotifisTab('unread')" style="margin:0; padding: 0 1.5rem;">title.unread</button>
|
||||
</div>
|
||||
<div id="notifis-container" style="flex-grow: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; opacity: 0.5;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="4rem" viewBox="0 -960 960 960" fill="var(--text-color)"><path d="M160-200v-80h80v-280q0-83 50-147.5T420-792v-28q0-25 17.5-42.5T480-880q25 0 42.5 17.5T540-820v28q80 20 130 84.5T720-560v280h80v80H160Zm320-300Zm0 420q-33 0-56.5-23.5T400-160h160q0 33-23.5 56.5T480-80ZM320-280h320v-280q0-66-47-113t-113-47q-66 0-113 47t-47 113v280Z"/></svg>
|
||||
<br>
|
||||
<p><blah>desc.no.notifications</blah></p>
|
||||
<div id="notifis-container" class="list-container empty">
|
||||
${notifisEmptyState}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
var invitesEntry = `
|
||||
<div class="invite-entry">
|
||||
<div class="invite-info">
|
||||
<img src="{pfp}" class="invite-pfp">
|
||||
<div class="invite-details">
|
||||
<span class="invite-name">{username}</span>
|
||||
<span class="invite-desc"><blah>{desc}</blah></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invite-actions">
|
||||
{actions}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
var chatScreen = `
|
||||
<div style="display: flex; flex-direction: column; height: 100%; width: 100%;">
|
||||
<div id="chat-messages" style="flex-grow: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
</div>
|
||||
<div style="padding: 1rem; border-top: var(--border-width) solid var(--light-border-color); display: flex; gap: 0.5rem;">
|
||||
<textarea id="chat-input" class="forminput" style="flex-grow: 1; margin: 0; resize: none; min-height: 2.5rem; max-height: 8rem; padding: 0.5rem;" placeholder="{blah(placeholder.message.input)}" onkeydown="handleChatInputKey(event)" oninput="handleChatInputResize(this)"></textarea>
|
||||
<button class="submit-button" onclick="sendMessage()" style="margin: 0; padding: 0; aspect-ratio: 1;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.9 -0.5 15.6 16" fill="none" width="1.5rem">
|
||||
<path stroke="var(--main-bg-color)" stroke-linecap="round" stroke-linejoin="round" d="m3.75 7.5 -1.875 5.625 11.25 -5.625L1.875 1.875l1.875 5.625zm0 0h3.75" stroke-width="1.1"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -119,6 +162,11 @@ var homeRoomBar = `
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="dms-wrapper" class="collapsible-wrapper">
|
||||
<div class="collapsible-inner">
|
||||
<div id="dms-list" class="list-container no-flex-grow empty" style="padding: 0 0.5rem;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section-header">
|
||||
<button class="collapse-text-button" id="collapse-groups" onclick="clickCollapseGroups()">
|
||||
|
|
@ -136,6 +184,11 @@ var homeRoomBar = `
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="groups-wrapper" class="collapsible-wrapper">
|
||||
<div class="collapsible-inner">
|
||||
<div id="groups-list" class="list-container no-flex-grow empty" style="padding: 0 0.5rem;"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
var inboxRoomBar = `
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
--button-height: 2.4rem;
|
||||
--border-width: 0.09rem;
|
||||
|
||||
--big-default: rgb(207 207 207);
|
||||
--big-red: rgb(202 0 0);
|
||||
--big-green: rgb(25 189 0);
|
||||
--big-default: rgb(207, 207, 207);
|
||||
--big-red: hsl(0, 60%, 55%);
|
||||
--big-green: hsl(120, 60%, 55%);
|
||||
}
|
||||
*:focus {
|
||||
outline: none;
|
||||
|
|
@ -71,7 +71,7 @@ loading {
|
|||
user-select: none !important;
|
||||
-webkit-user-drag: none !important;
|
||||
}
|
||||
button, input {
|
||||
button, input, textarea {
|
||||
border-radius: 0.8rem;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
margin: var(--button-margin);
|
||||
|
|
@ -92,7 +92,7 @@ button, input {
|
|||
touch-action: manipulation;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
input {
|
||||
input, textarea {
|
||||
padding-left: calc(var(--button-margin) * 2);
|
||||
-webkit-touch-callout: default;
|
||||
user-select: text !important;
|
||||
|
|
@ -148,6 +148,7 @@ indicator.active {
|
|||
width: calc(var(--icon-button-height) - (var(--border-width) * 2));
|
||||
height: calc(var(--icon-button-height) - (var(--border-width) * 2));
|
||||
pointer-events: none !important;
|
||||
border-radius: 0.65rem;
|
||||
}
|
||||
|
||||
roomcontent {
|
||||
|
|
@ -252,11 +253,11 @@ hr {
|
|||
box-shadow: 0 0.5rem 2rem rgba(0, 0, 0, 0.4);
|
||||
|
||||
opacity: 0;
|
||||
transform: translateY(-1rem);
|
||||
transform: translateY(-2rem);
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
background-color 0.2s ease;
|
||||
opacity 0.2s ease,
|
||||
transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
background-color 0.15s ease;
|
||||
}
|
||||
.notification.show {
|
||||
opacity: 1;
|
||||
|
|
@ -330,52 +331,7 @@ herotitle {
|
|||
.bottom-element {
|
||||
margin-top: auto;
|
||||
}
|
||||
@media (hover: hover) {
|
||||
sidebarelement:hover indicator:not(.active) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 1.2rem;
|
||||
left: -0.08rem;
|
||||
border: 0.15rem solid var(--text-color);
|
||||
border-radius: 0.8rem;
|
||||
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
|
||||
}
|
||||
button:hover, input:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.submit-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.add-action-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
button:active, button.is-pressed, input:active, input.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transform: var(--press-scale);
|
||||
}
|
||||
button.active, input.active {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.submit-button:active, .submit-button.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.add-action-button:active, .add-action-button.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-color);
|
||||
}
|
||||
sidebarelement:has(.icon-button:active) indicator:not(.active) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 1.2rem;
|
||||
left: -0.08rem;
|
||||
border: 0.15rem solid var(--text-color);
|
||||
border-radius: 0.8rem;
|
||||
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
|
||||
}
|
||||
|
||||
blah, inherit, .inherit {
|
||||
all: inherit;
|
||||
padding: 0;
|
||||
|
|
@ -419,9 +375,7 @@ space{
|
|||
gap: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.context-menu button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.mobile-nav-btn {
|
||||
display: none;
|
||||
background: transparent;
|
||||
|
|
@ -466,7 +420,7 @@ space{
|
|||
sidebar, sidebar.second, roomcontent {
|
||||
position: absolute !important;
|
||||
height: 100%;
|
||||
transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
transition: all 0.25s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
sidebar {
|
||||
|
|
@ -537,7 +491,7 @@ space{
|
|||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
transition: all 0.25s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
main.mobile-details roomcontent::after {
|
||||
|
|
@ -554,3 +508,217 @@ space{
|
|||
background-color: transparent;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.list-container {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.list-container.empty {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.list-container.no-flex-grow {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.collapsible-wrapper {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
transition: grid-template-rows 0.25s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
.collapsible-wrapper.collapsed {
|
||||
grid-template-rows: 0fr;
|
||||
}
|
||||
|
||||
.collapsible-inner {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.invite-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.8rem 1rem;
|
||||
border: var(--border-width) solid var(--light-border-color);
|
||||
border-radius: 0.8rem;
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.invite-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
.invite-pfp {
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
border-radius: 0.8rem;
|
||||
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
|
||||
object-fit: cover;
|
||||
}
|
||||
.invite-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
}
|
||||
.invite-name {
|
||||
font-weight: 700;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.invite-desc {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.invite-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.invite-actions button {
|
||||
margin: 0;
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
width: 3.1rem;
|
||||
height: 3.1rem;
|
||||
}
|
||||
|
||||
.room-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.1rem;
|
||||
padding: 1.5rem 0.6rem;
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
.room-pfp {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
border-radius: 0.65rem;
|
||||
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
.room-name {
|
||||
font-weight: 600;
|
||||
font-size: 1.05rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
.chat-message.with-avatar {
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
.chat-message-pfp {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 0.65rem;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
border: var(--border-width) solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
.chat-message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.chat-message-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.2rem;
|
||||
display: none; /* hidden for consecutive */
|
||||
}
|
||||
.chat-message.with-avatar .chat-message-header {
|
||||
display: flex;
|
||||
}
|
||||
.chat-message-author {
|
||||
font-weight: bold;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.chat-message-timestamp {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.chat-message-text {
|
||||
line-height: 1.4;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
sidebarelement:hover indicator:not(.active) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 1.2rem;
|
||||
left: -0.08rem;
|
||||
border: 0.15rem solid var(--text-color);
|
||||
border-radius: 0.8rem;
|
||||
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
|
||||
}
|
||||
button:hover, input:hover, textarea:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.submit-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.add-action-button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-color);
|
||||
}
|
||||
.context-menu button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.invite-actions button:hover {
|
||||
filter: brightness(0.93);
|
||||
}
|
||||
}
|
||||
button:active, button.is-pressed, input:active, input.is-pressed, textarea:active, textarea.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transform: var(--press-scale);
|
||||
}
|
||||
.invite-actions button:active {
|
||||
filter: brightness(0.85);
|
||||
}
|
||||
button.active, input.active, textarea.active {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.submit-button:active, .submit-button.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.add-action-button:active, .add-action-button.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-color);
|
||||
}
|
||||
.context-menu button:active, .context-menu button.is-pressed {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
sidebarelement:has(.icon-button:active) indicator:not(.active) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 1.2rem;
|
||||
left: -0.08rem;
|
||||
border: 0.15rem solid var(--text-color);
|
||||
border-radius: 0.8rem;
|
||||
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue