fix icons size and message update formatting & add missing blahs

This commit is contained in:
olcxja 2026-07-17 11:40:04 +02:00
commit 50ebf57b4c
16 changed files with 132 additions and 104 deletions

View file

@ -14,7 +14,7 @@
"dm.key.too.large": "meowchat key too large :o", "dm.key.too.large": "meowchat key too large :o",
"dm.key.not.found": "meowchat key not found :c", "dm.key.not.found": "meowchat key not found :c",
"dm.key.updated": "meowchat key updated successfully :3", "dm.key.updated": "meowchat key updated successfully :3",
"body.too.large": "request body too large ><", "body.too.large": "request too large ><",
"invite.revoked": "invite revoked successfully", "invite.revoked": "invite revoked successfully",
"invite.declined": "invite declined successfully", "invite.declined": "invite declined successfully",
"account.creation.request.expired": "cat creation request expired. try again", "account.creation.request.expired": "cat creation request expired. try again",
@ -33,12 +33,8 @@
"username.conditions.allowed": "cat name can only include {all}", "username.conditions.allowed": "cat name can only include {all}",
"password.not.hashed.properly": "meow word is not hashed properly", "password.not.hashed.properly": "meow word is not hashed properly",
"invalid.username.or.password": "invalid cat name or meow word", "invalid.username.or.password": "invalid cat name or meow word",
"error.account.not.exist": "Meowccount Does Not Exist",
"error.storage.limit.exceeded": "Storage Limit Exceeded, purr", "error.storage.limit.exceeded": "Storage Limit Exceeded, purr",
"error.username.taken": "Meowname is Taken",
"error.network.error": "Network error, check connection or file size, meow.", "error.network.error": "Network error, check connection or file size, meow.",
"error.body.too.large": "File is too large, meow! (max 10MB)",
"error.unknown.error": "Unknown error, purr",
"account.not.exist": "cat with this name doesn't exist", "account.not.exist": "cat with this name doesn't exist",
"invalid.nonce": "invalid nonce. try again", "invalid.nonce": "invalid nonce. try again",
"username.changed": "cat name changed successfully", "username.changed": "cat name changed successfully",
@ -122,7 +118,6 @@
"title.back.to.register": "back to registration", "title.back.to.register": "back to registration",
"title.details": "details", "title.details": "details",
"title.members": "cats", "title.members": "cats",
"placeholder.username": "cat name", "placeholder.username": "cat name",
"placeholder.captcha.code": "captcha code", "placeholder.captcha.code": "captcha code",
"desc.messages.loading": "loading meows...", "desc.messages.loading": "loading meows...",
@ -191,5 +186,11 @@
"action.attachment.poll": "Meowll", "action.attachment.poll": "Meowll",
"action.attachment.uploading": "Uploading meowttachments...", "action.attachment.uploading": "Uploading meowttachments...",
"attachment.legacy": "Legacy meowttachment", "attachment.legacy": "Legacy meowttachment",
"warning.unencrypted": "This meowsage is not encrypted" "warning.unencrypted": "This meowsage is not encrypted",
"action.change.avatar": "Change Cat Picture",
"action.change.banner": "Change Cat Banner",
"placeholder.type.text.emoji": "Type purr / meow...",
"action.cancel": "Cancel",
"title.crop.image": "Crop Image",
"title.zoom": "Zoom"
} }

View file

@ -14,7 +14,7 @@
"dm.key.too.large": "DM key too large", "dm.key.too.large": "DM key too large",
"dm.key.not.found": "DM key not found", "dm.key.not.found": "DM key not found",
"dm.key.updated": "DM key updated successfully", "dm.key.updated": "DM key updated successfully",
"body.too.large": "Request body too large", "body.too.large": "Request too large",
"invite.revoked": "Invite revoked successfully", "invite.revoked": "Invite revoked successfully",
"invite.declined": "Invite declined successfully", "invite.declined": "Invite declined successfully",
"account.creation.request.expired": "Account creation request expired. Try again", "account.creation.request.expired": "Account creation request expired. Try again",
@ -33,12 +33,8 @@
"username.conditions.allowed": "Username can only include {all}", "username.conditions.allowed": "Username can only include {all}",
"password.not.hashed.properly": "Password is not hashed properly", "password.not.hashed.properly": "Password is not hashed properly",
"invalid.username.or.password": "Invalid username or password", "invalid.username.or.password": "Invalid username or password",
"error.account.not.exist": "Account Does Not Exist",
"error.storage.limit.exceeded": "Storage Limit Exceeded", "error.storage.limit.exceeded": "Storage Limit Exceeded",
"error.username.taken": "Username is Taken",
"error.network.error": "Network error, check connection or file size.", "error.network.error": "Network error, check connection or file size.",
"error.body.too.large": "File is too large (max 10MB)",
"error.unknown.error": "Unknown error",
"account.not.exist": "Account with this name doesn't exist", "account.not.exist": "Account with this name doesn't exist",
"invalid.nonce": "Invalid nonce. Try again", "invalid.nonce": "Invalid nonce. Try again",
"username.changed": "Username changed successfully", "username.changed": "Username changed successfully",
@ -156,7 +152,6 @@
"title.loading": "Loading...", "title.loading": "Loading...",
"title.details": "Details", "title.details": "Details",
"title.members": "Members", "title.members": "Members",
"action.edit.profile": "Edit profile", "action.edit.profile": "Edit profile",
"action.open.dm": "Open DM", "action.open.dm": "Open DM",
"action.invite.accept": "Accept invite", "action.invite.accept": "Accept invite",
@ -191,5 +186,11 @@
"action.attachment.poll": "Poll", "action.attachment.poll": "Poll",
"action.attachment.uploading": "Uploading attachments...", "action.attachment.uploading": "Uploading attachments...",
"attachment.legacy": "Legacy attachment", "attachment.legacy": "Legacy attachment",
"warning.unencrypted": "This message is not encrypted" "warning.unencrypted": "This message is not encrypted",
"action.change.avatar": "Change Avatar",
"action.change.banner": "Change Banner",
"placeholder.type.text.emoji": "Type text / emoji...",
"action.cancel": "Cancel",
"title.crop.image": "Crop Image",
"title.zoom": "Zoom"
} }

View file

@ -1,4 +1,5 @@
{ {
"en-us": { "english": "English (US)", "native": "English (US)" }, "en-us": { "english": "English (US)", "native": "English (US)" },
"en-cat": { "english": "English (Cat)", "native": "Meowlish" } "en-cat": { "english": "English (Cat)", "native": "Meowlish" },
"pl-pl": { "english": "Polish", "native": "Polski" }
} }

View file

@ -3,6 +3,7 @@
const ICONS = { const ICONS = {
_create: function(content, defaultProps, customProps = {}) { _create: function(content, defaultProps, customProps = {}) {
let p = { ...defaultProps, ...customProps }; let p = { ...defaultProps, ...customProps };
if (customProps.width && !customProps.height) p.height = customProps.width;
let strokeAttrs = p.strokeWidth ? ` stroke-width="${p.strokeWidth}" stroke-linecap="${p.strokeLinecap}" stroke-linejoin="${p.strokeLinejoin}"` : ""; let strokeAttrs = p.strokeWidth ? ` stroke-width="${p.strokeWidth}" stroke-linecap="${p.strokeLinecap}" stroke-linejoin="${p.strokeLinejoin}"` : "";
let classAttr = p.className ? ` class="${p.className}"` : ""; let classAttr = p.className ? ` class="${p.className}"` : "";
let styleAttr = p.style ? ` style="${p.style}"` : ""; let styleAttr = p.style ? ` style="${p.style}"` : "";

View file

@ -93,17 +93,17 @@
<div id="cropper-popup-container" class="profile-popup-container" style="z-index: 9999;"> <div id="cropper-popup-container" class="profile-popup-container" style="z-index: 9999;">
<div class="profile-popup-bg" onclick="closeCropper()"></div> <div class="profile-popup-bg" onclick="closeCropper()"></div>
<div class="profile-popup-content" style="max-width: 40rem; padding: 2rem; display: flex; flex-direction: column; align-items: center; gap: 1rem; height: auto;"> <div class="profile-popup-content" style="max-width: 40rem; padding: 2rem; display: flex; flex-direction: column; align-items: center; gap: 1rem; height: auto;">
<h2>Crop Image</h2> <h2><blah>title.crop.image</blah></h2>
<div id="cropper-viewport-container" style="position: relative; overflow: hidden; background: #000; width: 100%; max-width: 30rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255,255,255,0.2);"> <div id="cropper-viewport-container" style="position: relative; overflow: hidden; background: #000; width: 100%; max-width: 30rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255,255,255,0.2);">
<img id="cropper-image" src="" style="position: absolute; transform-origin: 0 0; cursor: move;"> <img id="cropper-image" src="" style="position: absolute; transform-origin: 0 0; cursor: move;">
</div> </div>
<div style="display: flex; align-items: center; gap: 1rem; width: 100%; max-width: 30rem;"> <div style="display: flex; align-items: center; gap: 1rem; width: 100%; max-width: 30rem;">
<span>Zoom</span> <span><blah>title.zoom</blah></span>
<input type="range" id="cropper-zoom" min="0.1" max="5" step="0.01" value="1" style="flex: 1;" oninput="updateCropperZoom()"> <input type="range" id="cropper-zoom" min="0.1" max="5" step="0.01" value="1" style="flex: 1;" oninput="updateCropperZoom()">
</div> </div>
<div style="display: flex; gap: 1rem; margin-top: 1rem; width: 100%; max-width: 30rem;"> <div style="display: flex; gap: 1rem; margin-top: 1rem; width: 100%; max-width: 30rem;">
<button class="submit-button" style="background: rgba(255,255,255,0.1); color: var(--text-color); flex: 1;" onclick="closeCropper()">Cancel</button> <button class="submit-button blah" style="background: rgba(255,255,255,0.1); color: var(--text-color); flex: 1;" onclick="closeCropper()">action.cancel</button>
<button class="submit-button" style="flex: 1;" onclick="confirmCropper()">Save</button> <button class="submit-button blah" style="flex: 1;" onclick="confirmCropper()">action.save</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -163,79 +163,79 @@
<main style="display: none;"> <main style="display: none;">
<div class="auth-card"> <div class="auth-card">
<div class="auth-container" id="auth-container"> <div class="auth-container" id="auth-container">
<div class="auth-box" id="login-box"> <div class="auth-box" id="login-box">
<div class="auth-header"> <div class="auth-header">
<h1>Larpix</h1> <h1>Larpix</h1>
<p><blah>title.sign.in.to</blah></p> <p><blah>title.sign.in.to</blah></p>
</div> </div>
<form id="form-login"> <form id="form-login">
<div class="input-group"> <div class="input-group">
<label for="login-host"><blah>title.server.host</blah></label> <label for="login-host"><blah>title.server.host</blah></label>
<input type="text" id="login-host" placeholder="example.com" autocomplete="url" onchange="loginHostChanged()"> <input type="text" id="login-host" placeholder="example.com" autocomplete="url" onchange="loginHostChanged()">
<p class="mininote serverstatus"><blah>loading.connecting</blah></p> <p class="mininote serverstatus"><blah>loading.connecting</blah></p>
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="login-username"><blah>title.username</blah></label> <label for="login-username"><blah>title.username</blah></label>
<input type="text" id="login-username" placeholder="{blah(placeholder.username)}" autocomplete="username"> <input type="text" id="login-username" placeholder="{blah(placeholder.username)}" autocomplete="username">
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="login-password"><blah>title.password</blah></label> <label for="login-password"><blah>title.password</blah></label>
<input type="password" id="login-password" placeholder="••••••••" autocomplete="current-password"> <input type="password" id="login-password" placeholder="••••••••" autocomplete="current-password">
</div> </div>
<button type="submit" class="submit-button blah">title.sign.in</button> <button type="submit" class="submit-button blah">title.sign.in</button>
</form> </form>
<div class="footer-links"> <div class="footer-links">
<span class="footer-link-toggle" id="to-register"> <span class="footer-link-toggle" id="to-register">
<blah>title.new.here.?</blah> <strong class="blah">title.create.an.account</strong> <blah>title.new.here.?</blah> <strong class="blah">title.create.an.account</strong>
</span> </span>
</div> </div>
</div> </div>
<div class="auth-box" id="register-box"> <div class="auth-box" id="register-box">
<div class="auth-header"> <div class="auth-header">
<h1><blah>join.larpix</blah></h1> <h1><blah>join.larpix</blah></h1>
<p><blah>title.create.new.account</blah></p> <p><blah>title.create.new.account</blah></p>
</div> </div>
<form id="form-register"> <form id="form-register">
<div class="input-group"> <div class="input-group">
<label for="reg-host"><blah>title.server.host</blah></label> <label for="reg-host"><blah>title.server.host</blah></label>
<input type="text" id="reg-host" placeholder="example.com" autocomplete="url" onchange="regHostChanged()"> <input type="text" id="reg-host" placeholder="example.com" autocomplete="url" onchange="regHostChanged()">
<p class="mininote serverstatus"><blah>loading.connecting</blah></p> <p class="mininote serverstatus"><blah>loading.connecting</blah></p>
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="reg-username"><blah>title.username</blah></label> <label for="reg-username"><blah>title.username</blah></label>
<input type="text" id="reg-username" placeholder="{blah(placeholder.username)}" autocomplete="username"> <input type="text" id="reg-username" placeholder="{blah(placeholder.username)}" autocomplete="username">
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="reg-password"><blah>title.password</blah></label> <label for="reg-password"><blah>title.password</blah></label>
<input type="password" id="reg-password" placeholder="••••••••" autocomplete="new-password"> <input type="password" id="reg-password" placeholder="••••••••" autocomplete="new-password">
<p class="red mininote"><blah>notice.use.strong.pass</blah></p> <p class="red mininote"><blah>notice.use.strong.pass</blah></p>
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="reg-confirm"><blah>title.confirm.password</blah></label> <label for="reg-confirm"><blah>title.confirm.password</blah></label>
<input type="password" id="reg-confirm" placeholder="••••••••" autocomplete="new-password"> <input type="password" id="reg-confirm" placeholder="••••••••" autocomplete="new-password">
</div> </div>
<button type="submit" class="submit-button blah" id="register-button">title.sign.up</button> <button type="submit" class="submit-button blah" id="register-button">title.sign.up</button>
</form> </form>
<div class="footer-links"> <div class="footer-links">
<span class="footer-link-toggle" id="to-login"> <span class="footer-link-toggle" id="to-login">
<blah>title.already.registered.?</blah> <strong class="blah">title.back.to.login</strong> <blah>title.already.registered.?</blah> <strong class="blah">title.back.to.login</strong>
</span> </span>
</div> </div>
</div> </div>
<div class="auth-box" id="captcha-box"> <div class="auth-box" id="captcha-box">
<div class="auth-header"> <div class="auth-header">
<h1><blah>title.verify</blah></h1> <h1><blah>title.verify</blah></h1>
@ -291,19 +291,19 @@
const loginPassword = document.getElementById("login-password"); const loginPassword = document.getElementById("login-password");
const captchaCode = document.getElementById("captcha-input"); const captchaCode = document.getElementById("captcha-input");
const regKeyInput = document.getElementById("regkey-input"); const regKeyInput = document.getElementById("regkey-input");
const registerButton = document.getElementById("register-button"); const registerButton = document.getElementById("register-button");
const regKeyGroup = document.getElementById("regkey-group"); const regKeyGroup = document.getElementById("regkey-group");
async function start() { async function start() {
updateLoadingStatus("loading.loading"); updateLoadingStatus("loading.loading");
// //
updateLoadingStatus("loading.done"); updateLoadingStatus("loading.done");
await loadingFadeOut(); await loadingFadeOut();
} }
toRegister.addEventListener('click', () => { toRegister.addEventListener('click', () => {
authContainer.className = 'auth-container show-register'; authContainer.className = 'auth-container show-register';
@ -323,7 +323,7 @@
try { try {
await updateProtocolAndUrl(loginHost.value); await updateProtocolAndUrl(loginHost.value);
let res = await Auth(loginUsername.value, loginPassword.value); let res = await Auth(loginUsername.value, loginPassword.value);
if (res.startsWith("success:")) if (res.startsWith("success:"))
{ {
let parts = res.split("|"); let parts = res.split("|");
@ -337,14 +337,14 @@
} }
await loadingFadeIn(); await loadingFadeIn();
window.location.href = "../"; window.location.href = "../";
} }
else else
{ {
showBlahNotification(res); showBlahNotification(res);
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
showBlahNotification("error:something.wrong.mayb.pass"); showBlahNotification("error:something.wrong.mayb.pass");
@ -436,7 +436,7 @@
if (res.startsWith("success")) { if (res.startsWith("success")) {
showBlahNotification(res); showBlahNotification(res);
await delay(1000); await delay(1000);
let resolvedId = await fetchAsync(`${url}/nametoid?u=${registerUsername.value}`); let resolvedId = await fetchAsync(`${url}/nametoid?u=${registerUsername.value}`);
if (resolvedId && !resolvedId.startsWith("error:")) { if (resolvedId && !resolvedId.startsWith("error:")) {
localStorage.setItem("id", resolvedId.split(":")[0]); localStorage.setItem("id", resolvedId.split(":")[0]);
@ -458,7 +458,7 @@
authContainer.className = 'auth-container show-register'; authContainer.className = 'auth-container show-register';
} }
}); });
async function regHostChanged() async function regHostChanged()
{ {
loginHost.value = registerHost.value; loginHost.value = registerHost.value;
@ -495,7 +495,7 @@
setServerStatus("Can't ping this server", "red"); setServerStatus("Can't ping this server", "red");
} }
} }
function setServerStatus(status, color) function setServerStatus(status, color)
{ {
let elements = document.getElementsByClassName("serverstatus"); let elements = document.getElementsByClassName("serverstatus");

View file

@ -2598,8 +2598,18 @@ async function renderMessages(messages, isPrepend = false) {
let oldText = existingChild.querySelector('.chat-message-text'); let oldText = existingChild.querySelector('.chat-message-text');
let newText = newChild.querySelector('.chat-message-text'); let newText = newChild.querySelector('.chat-message-text');
if (oldText && newText && oldText.innerHTML !== newText.innerHTML) { if (oldText && newText) {
oldText.innerHTML = newText.innerHTML; if (oldText.innerHTML !== newText.innerHTML) {
oldText.innerHTML = newText.innerHTML;
}
if (oldText.getAttribute('style') !== newText.getAttribute('style')) {
let newStyle = newText.getAttribute('style');
if (newStyle) {
oldText.setAttribute('style', newStyle);
} else {
oldText.removeAttribute('style');
}
}
} }
let oldReactions = existingChild.querySelector('.chat-message-reactions'); let oldReactions = existingChild.querySelector('.chat-message-reactions');
@ -3218,7 +3228,7 @@ async function loadEmotes() {
reactionPickerMenuHtml = ` reactionPickerMenuHtml = `
<div style="display: flex; flex-direction: column; gap: 0.4rem; padding: 0.2rem; min-width: 14rem;"> <div style="display: flex; flex-direction: column; gap: 0.4rem; padding: 0.2rem; min-width: 14rem;">
<input type="text" id="reaction-picker-search" placeholder="Type text / emoji..." oninput="filterReactions(this.value)" onkeydown="if(event.key === 'Enter') { if(this.value.trim() !== '') reactMessagePrompt(currentContextMenuMsgId, encodeURIComponent(this.value)); }" style="width: auto; align-self: stretch; margin: 0; box-sizing: border-box; padding: 0.5rem 0.6rem; border-radius: 0.65rem; border: var(--border-width) solid var(--light-border-color); background: transparent; color: var(--text-color); font-family: inherit;"> <input type="text" id="reaction-picker-search" placeholder="{blah(placeholder.type.text.emoji)}" oninput="filterReactions(this.value)" onkeydown="if(event.key === 'Enter') { if(this.value.trim() !== '') reactMessagePrompt(currentContextMenuMsgId, encodeURIComponent(this.value)); }" style="width: auto; align-self: stretch; margin: 0; box-sizing: border-box; padding: 0.5rem 0.6rem; border-radius: 0.65rem; border: var(--border-width) solid var(--light-border-color); background: transparent; color: var(--text-color); font-family: inherit;">
<div id="reaction-picker-grid" style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.2rem; max-height: 12rem; overflow-y: auto; overflow-x: hidden;"> <div id="reaction-picker-grid" style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.2rem; max-height: 12rem; overflow-y: auto; overflow-x: hidden;">
${loadedEmotes.map(emoji => `<button onclick="reactMessagePrompt(currentContextMenuMsgId, encodeURIComponent('${emoji.char}'))" data-name="${emoji.name}" style="min-width: unset; width: 100%; aspect-ratio: 1; display: flex; justify-content: center; align-items: center; padding: 0; font-size: 1.2rem; border-radius: 0.65rem;" class="reaction-emoji-btn">${emoji.char}</button>`).join('')} ${loadedEmotes.map(emoji => `<button onclick="reactMessagePrompt(currentContextMenuMsgId, encodeURIComponent('${emoji.char}'))" data-name="${emoji.name}" style="min-width: unset; width: 100%; aspect-ratio: 1; display: flex; justify-content: center; align-items: center; padding: 0; font-size: 1.2rem; border-radius: 0.65rem;" class="reaction-emoji-btn">${emoji.char}</button>`).join('')}
</div> </div>

View file

@ -331,7 +331,7 @@ var profileSettings = `
<img id="settings-avatar-preview" src="" style="width: 4.5rem; height: 4.5rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255, 255, 255, 0.2); object-fit: cover; background: var(--main-bg-color);"> <img id="settings-avatar-preview" src="" style="width: 4.5rem; height: 4.5rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255, 255, 255, 0.2); object-fit: cover; background: var(--main-bg-color);">
<div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;"> <div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;">
<div style="font-weight: bold;"><blah>title.avatar</blah></div> <div style="font-weight: bold;"><blah>title.avatar</blah></div>
<button class="submit-button" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('settings-avatar-input').click()">Change Avatar</button> <button class="submit-button blah" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('settings-avatar-input').click()">action.change.avatar</button>
<input type="file" id="settings-avatar-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'avatar')"> <input type="file" id="settings-avatar-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'avatar')">
</div> </div>
</div> </div>
@ -342,7 +342,7 @@ var profileSettings = `
</div> </div>
<div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;"> <div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;">
<div style="font-weight: bold;"><blah>title.banner</blah></div> <div style="font-weight: bold;"><blah>title.banner</blah></div>
<button class="submit-button" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('settings-banner-input').click()">Change Banner</button> <button class="submit-button blah" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('settings-banner-input').click()">action.change.banner</button>
<input type="file" id="settings-banner-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'banner')"> <input type="file" id="settings-banner-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'banner')">
</div> </div>
</div> </div>

View file

@ -14,7 +14,7 @@
"dm.key.too.large": "meowchat key too large :o", "dm.key.too.large": "meowchat key too large :o",
"dm.key.not.found": "meowchat key not found :c", "dm.key.not.found": "meowchat key not found :c",
"dm.key.updated": "meowchat key updated successfully :3", "dm.key.updated": "meowchat key updated successfully :3",
"body.too.large": "request body too large ><", "body.too.large": "request too large ><",
"invite.revoked": "invite revoked successfully", "invite.revoked": "invite revoked successfully",
"invite.declined": "invite declined successfully", "invite.declined": "invite declined successfully",
"account.creation.request.expired": "cat creation request expired. try again", "account.creation.request.expired": "cat creation request expired. try again",
@ -33,12 +33,8 @@
"username.conditions.allowed": "cat name can only include {all}", "username.conditions.allowed": "cat name can only include {all}",
"password.not.hashed.properly": "meow word is not hashed properly", "password.not.hashed.properly": "meow word is not hashed properly",
"invalid.username.or.password": "invalid cat name or meow word", "invalid.username.or.password": "invalid cat name or meow word",
"error.account.not.exist": "Meowccount Does Not Exist",
"error.storage.limit.exceeded": "Storage Limit Exceeded, purr", "error.storage.limit.exceeded": "Storage Limit Exceeded, purr",
"error.username.taken": "Meowname is Taken",
"error.network.error": "Network error, check connection or file size, meow.", "error.network.error": "Network error, check connection or file size, meow.",
"error.body.too.large": "File is too large, meow! (max 10MB)",
"error.unknown.error": "Unknown error, purr",
"account.not.exist": "cat with this name doesn't exist", "account.not.exist": "cat with this name doesn't exist",
"invalid.nonce": "invalid nonce. try again", "invalid.nonce": "invalid nonce. try again",
"username.changed": "cat name changed successfully", "username.changed": "cat name changed successfully",
@ -122,7 +118,6 @@
"title.back.to.register": "back to registration", "title.back.to.register": "back to registration",
"title.details": "details", "title.details": "details",
"title.members": "cats", "title.members": "cats",
"placeholder.username": "cat name", "placeholder.username": "cat name",
"placeholder.captcha.code": "captcha code", "placeholder.captcha.code": "captcha code",
"desc.messages.loading": "loading meows...", "desc.messages.loading": "loading meows...",
@ -191,5 +186,11 @@
"action.attachment.poll": "Meowll", "action.attachment.poll": "Meowll",
"action.attachment.uploading": "Uploading meowttachments...", "action.attachment.uploading": "Uploading meowttachments...",
"attachment.legacy": "Legacy meowttachment", "attachment.legacy": "Legacy meowttachment",
"warning.unencrypted": "This meowsage is not encrypted" "warning.unencrypted": "This meowsage is not encrypted",
"action.change.avatar": "Change Cat Picture",
"action.change.banner": "Change Cat Banner",
"placeholder.type.text.emoji": "Type purr / meow...",
"action.cancel": "Cancel",
"title.crop.image": "Crop Image",
"title.zoom": "Zoom"
} }

View file

@ -14,7 +14,7 @@
"dm.key.too.large": "DM key too large", "dm.key.too.large": "DM key too large",
"dm.key.not.found": "DM key not found", "dm.key.not.found": "DM key not found",
"dm.key.updated": "DM key updated successfully", "dm.key.updated": "DM key updated successfully",
"body.too.large": "Request body too large", "body.too.large": "Request too large",
"invite.revoked": "Invite revoked successfully", "invite.revoked": "Invite revoked successfully",
"invite.declined": "Invite declined successfully", "invite.declined": "Invite declined successfully",
"account.creation.request.expired": "Account creation request expired. Try again", "account.creation.request.expired": "Account creation request expired. Try again",
@ -33,12 +33,8 @@
"username.conditions.allowed": "Username can only include {all}", "username.conditions.allowed": "Username can only include {all}",
"password.not.hashed.properly": "Password is not hashed properly", "password.not.hashed.properly": "Password is not hashed properly",
"invalid.username.or.password": "Invalid username or password", "invalid.username.or.password": "Invalid username or password",
"error.account.not.exist": "Account Does Not Exist",
"error.storage.limit.exceeded": "Storage Limit Exceeded", "error.storage.limit.exceeded": "Storage Limit Exceeded",
"error.username.taken": "Username is Taken",
"error.network.error": "Network error, check connection or file size.", "error.network.error": "Network error, check connection or file size.",
"error.body.too.large": "File is too large (max 10MB)",
"error.unknown.error": "Unknown error",
"account.not.exist": "Account with this name doesn't exist", "account.not.exist": "Account with this name doesn't exist",
"invalid.nonce": "Invalid nonce. Try again", "invalid.nonce": "Invalid nonce. Try again",
"username.changed": "Username changed successfully", "username.changed": "Username changed successfully",
@ -156,7 +152,6 @@
"title.loading": "Loading...", "title.loading": "Loading...",
"title.details": "Details", "title.details": "Details",
"title.members": "Members", "title.members": "Members",
"action.edit.profile": "Edit profile", "action.edit.profile": "Edit profile",
"action.open.dm": "Open DM", "action.open.dm": "Open DM",
"action.invite.accept": "Accept invite", "action.invite.accept": "Accept invite",
@ -191,5 +186,11 @@
"action.attachment.poll": "Poll", "action.attachment.poll": "Poll",
"action.attachment.uploading": "Uploading attachments...", "action.attachment.uploading": "Uploading attachments...",
"attachment.legacy": "Legacy attachment", "attachment.legacy": "Legacy attachment",
"warning.unencrypted": "This message is not encrypted" "warning.unencrypted": "This message is not encrypted",
"action.change.avatar": "Change Avatar",
"action.change.banner": "Change Banner",
"placeholder.type.text.emoji": "Type text / emoji...",
"action.cancel": "Cancel",
"title.crop.image": "Crop Image",
"title.zoom": "Zoom"
} }

View file

@ -33,12 +33,8 @@
"username.conditions.allowed": "Nazwa użytkownika może zawierać tylko: {all}", "username.conditions.allowed": "Nazwa użytkownika może zawierać tylko: {all}",
"password.not.hashed.properly": "Hasło nie zostało poprawnie zahaszowane", "password.not.hashed.properly": "Hasło nie zostało poprawnie zahaszowane",
"invalid.username.or.password": "Nieprawidłowa nazwa użytkownika lub hasło", "invalid.username.or.password": "Nieprawidłowa nazwa użytkownika lub hasło",
"error.account.not.exist": "Konto nie istnieje",
"error.storage.limit.exceeded": "Przekroczono limit pamięci", "error.storage.limit.exceeded": "Przekroczono limit pamięci",
"error.username.taken": "Nazwa użytkownika jest już zajęta",
"error.network.error": "Błąd sieci. Sprawdź połączenie lub rozmiar pliku.", "error.network.error": "Błąd sieci. Sprawdź połączenie lub rozmiar pliku.",
"error.body.too.large": "Plik jest zbyt duży (maks. 10 MB)",
"error.unknown.error": "Nieznany błąd",
"account.not.exist": "Konto o tej nazwie nie istnieje", "account.not.exist": "Konto o tej nazwie nie istnieje",
"invalid.nonce": "Nieprawidłowy nonce. Spróbuj ponownie", "invalid.nonce": "Nieprawidłowy nonce. Spróbuj ponownie",
"username.changed": "Nazwa użytkownika została pomyślnie zmieniona", "username.changed": "Nazwa użytkownika została pomyślnie zmieniona",
@ -156,7 +152,6 @@
"title.loading": "Ładowanie...", "title.loading": "Ładowanie...",
"title.details": "Szczegóły", "title.details": "Szczegóły",
"title.members": "Członkowie", "title.members": "Członkowie",
"action.edit.profile": "Edytuj profil", "action.edit.profile": "Edytuj profil",
"action.open.dm": "Otwórz rozmowę", "action.open.dm": "Otwórz rozmowę",
"action.invite.accept": "Akceptuj zaproszenie", "action.invite.accept": "Akceptuj zaproszenie",
@ -191,5 +186,11 @@
"action.attachment.poll": "Ankieta", "action.attachment.poll": "Ankieta",
"action.attachment.uploading": "Przesyłanie załączników...", "action.attachment.uploading": "Przesyłanie załączników...",
"attachment.legacy": "Starszy załącznik", "attachment.legacy": "Starszy załącznik",
"warning.unencrypted": "Ta wiadomość nie jest szyfrowana" "warning.unencrypted": "Ta wiadomość nie jest szyfrowana",
} "action.change.avatar": "Zmień awatar",
"action.change.banner": "Zmień baner",
"placeholder.type.text.emoji": "Wpisz tekst / emoji...",
"action.cancel": "Anuluj",
"title.crop.image": "Przytnij obraz",
"title.zoom": "Przybliżenie"
}

View file

@ -3,6 +3,7 @@
const ICONS = { const ICONS = {
_create: function(content, defaultProps, customProps = {}) { _create: function(content, defaultProps, customProps = {}) {
let p = { ...defaultProps, ...customProps }; let p = { ...defaultProps, ...customProps };
if (customProps.width && !customProps.height) p.height = customProps.width;
let strokeAttrs = p.strokeWidth ? ` stroke-width="${p.strokeWidth}" stroke-linecap="${p.strokeLinecap}" stroke-linejoin="${p.strokeLinejoin}"` : ""; let strokeAttrs = p.strokeWidth ? ` stroke-width="${p.strokeWidth}" stroke-linecap="${p.strokeLinecap}" stroke-linejoin="${p.strokeLinejoin}"` : "";
let classAttr = p.className ? ` class="${p.className}"` : ""; let classAttr = p.className ? ` class="${p.className}"` : "";
let styleAttr = p.style ? ` style="${p.style}"` : ""; let styleAttr = p.style ? ` style="${p.style}"` : "";

View file

@ -93,17 +93,17 @@
<div id="cropper-popup-container" class="profile-popup-container" style="z-index: 9999;"> <div id="cropper-popup-container" class="profile-popup-container" style="z-index: 9999;">
<div class="profile-popup-bg" onclick="closeCropper()"></div> <div class="profile-popup-bg" onclick="closeCropper()"></div>
<div class="profile-popup-content" style="max-width: 40rem; padding: 2rem; display: flex; flex-direction: column; align-items: center; gap: 1rem; height: auto;"> <div class="profile-popup-content" style="max-width: 40rem; padding: 2rem; display: flex; flex-direction: column; align-items: center; gap: 1rem; height: auto;">
<h2>Crop Image</h2> <h2><blah>title.crop.image</blah></h2>
<div id="cropper-viewport-container" style="position: relative; overflow: hidden; background: #000; width: 100%; max-width: 30rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255,255,255,0.2);"> <div id="cropper-viewport-container" style="position: relative; overflow: hidden; background: #000; width: 100%; max-width: 30rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255,255,255,0.2);">
<img id="cropper-image" src="" style="position: absolute; transform-origin: 0 0; cursor: move;"> <img id="cropper-image" src="" style="position: absolute; transform-origin: 0 0; cursor: move;">
</div> </div>
<div style="display: flex; align-items: center; gap: 1rem; width: 100%; max-width: 30rem;"> <div style="display: flex; align-items: center; gap: 1rem; width: 100%; max-width: 30rem;">
<span>Zoom</span> <span><blah>title.zoom</blah></span>
<input type="range" id="cropper-zoom" min="0.1" max="5" step="0.01" value="1" style="flex: 1;" oninput="updateCropperZoom()"> <input type="range" id="cropper-zoom" min="0.1" max="5" step="0.01" value="1" style="flex: 1;" oninput="updateCropperZoom()">
</div> </div>
<div style="display: flex; gap: 1rem; margin-top: 1rem; width: 100%; max-width: 30rem;"> <div style="display: flex; gap: 1rem; margin-top: 1rem; width: 100%; max-width: 30rem;">
<button class="submit-button" style="background: rgba(255,255,255,0.1); color: var(--text-color); flex: 1;" onclick="closeCropper()">Cancel</button> <button class="submit-button blah" style="background: rgba(255,255,255,0.1); color: var(--text-color); flex: 1;" onclick="closeCropper()">action.cancel</button>
<button class="submit-button" style="flex: 1;" onclick="confirmCropper()">Save</button> <button class="submit-button blah" style="flex: 1;" onclick="confirmCropper()">action.save</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -163,79 +163,79 @@
<main style="display: none;"> <main style="display: none;">
<div class="auth-card"> <div class="auth-card">
<div class="auth-container" id="auth-container"> <div class="auth-container" id="auth-container">
<div class="auth-box" id="login-box"> <div class="auth-box" id="login-box">
<div class="auth-header"> <div class="auth-header">
<h1>Larpix</h1> <h1>Larpix</h1>
<p><blah>title.sign.in.to</blah></p> <p><blah>title.sign.in.to</blah></p>
</div> </div>
<form id="form-login"> <form id="form-login">
<div class="input-group"> <div class="input-group">
<label for="login-host"><blah>title.server.host</blah></label> <label for="login-host"><blah>title.server.host</blah></label>
<input type="text" id="login-host" placeholder="example.com" autocomplete="url" onchange="loginHostChanged()"> <input type="text" id="login-host" placeholder="example.com" autocomplete="url" onchange="loginHostChanged()">
<p class="mininote serverstatus"><blah>loading.connecting</blah></p> <p class="mininote serverstatus"><blah>loading.connecting</blah></p>
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="login-username"><blah>title.username</blah></label> <label for="login-username"><blah>title.username</blah></label>
<input type="text" id="login-username" placeholder="{blah(placeholder.username)}" autocomplete="username"> <input type="text" id="login-username" placeholder="{blah(placeholder.username)}" autocomplete="username">
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="login-password"><blah>title.password</blah></label> <label for="login-password"><blah>title.password</blah></label>
<input type="password" id="login-password" placeholder="••••••••" autocomplete="current-password"> <input type="password" id="login-password" placeholder="••••••••" autocomplete="current-password">
</div> </div>
<button type="submit" class="submit-button blah">title.sign.in</button> <button type="submit" class="submit-button blah">title.sign.in</button>
</form> </form>
<div class="footer-links"> <div class="footer-links">
<span class="footer-link-toggle" id="to-register"> <span class="footer-link-toggle" id="to-register">
<blah>title.new.here.?</blah> <strong class="blah">title.create.an.account</strong> <blah>title.new.here.?</blah> <strong class="blah">title.create.an.account</strong>
</span> </span>
</div> </div>
</div> </div>
<div class="auth-box" id="register-box"> <div class="auth-box" id="register-box">
<div class="auth-header"> <div class="auth-header">
<h1><blah>join.larpix</blah></h1> <h1><blah>join.larpix</blah></h1>
<p><blah>title.create.new.account</blah></p> <p><blah>title.create.new.account</blah></p>
</div> </div>
<form id="form-register"> <form id="form-register">
<div class="input-group"> <div class="input-group">
<label for="reg-host"><blah>title.server.host</blah></label> <label for="reg-host"><blah>title.server.host</blah></label>
<input type="text" id="reg-host" placeholder="example.com" autocomplete="url" onchange="regHostChanged()"> <input type="text" id="reg-host" placeholder="example.com" autocomplete="url" onchange="regHostChanged()">
<p class="mininote serverstatus"><blah>loading.connecting</blah></p> <p class="mininote serverstatus"><blah>loading.connecting</blah></p>
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="reg-username"><blah>title.username</blah></label> <label for="reg-username"><blah>title.username</blah></label>
<input type="text" id="reg-username" placeholder="{blah(placeholder.username)}" autocomplete="username"> <input type="text" id="reg-username" placeholder="{blah(placeholder.username)}" autocomplete="username">
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="reg-password"><blah>title.password</blah></label> <label for="reg-password"><blah>title.password</blah></label>
<input type="password" id="reg-password" placeholder="••••••••" autocomplete="new-password"> <input type="password" id="reg-password" placeholder="••••••••" autocomplete="new-password">
<p class="red mininote"><blah>notice.use.strong.pass</blah></p> <p class="red mininote"><blah>notice.use.strong.pass</blah></p>
</div> </div>
<div class="input-group"> <div class="input-group">
<label for="reg-confirm"><blah>title.confirm.password</blah></label> <label for="reg-confirm"><blah>title.confirm.password</blah></label>
<input type="password" id="reg-confirm" placeholder="••••••••" autocomplete="new-password"> <input type="password" id="reg-confirm" placeholder="••••••••" autocomplete="new-password">
</div> </div>
<button type="submit" class="submit-button blah" id="register-button">title.sign.up</button> <button type="submit" class="submit-button blah" id="register-button">title.sign.up</button>
</form> </form>
<div class="footer-links"> <div class="footer-links">
<span class="footer-link-toggle" id="to-login"> <span class="footer-link-toggle" id="to-login">
<blah>title.already.registered.?</blah> <strong class="blah">title.back.to.login</strong> <blah>title.already.registered.?</blah> <strong class="blah">title.back.to.login</strong>
</span> </span>
</div> </div>
</div> </div>
<div class="auth-box" id="captcha-box"> <div class="auth-box" id="captcha-box">
<div class="auth-header"> <div class="auth-header">
<h1><blah>title.verify</blah></h1> <h1><blah>title.verify</blah></h1>
@ -291,19 +291,19 @@
const loginPassword = document.getElementById("login-password"); const loginPassword = document.getElementById("login-password");
const captchaCode = document.getElementById("captcha-input"); const captchaCode = document.getElementById("captcha-input");
const regKeyInput = document.getElementById("regkey-input"); const regKeyInput = document.getElementById("regkey-input");
const registerButton = document.getElementById("register-button"); const registerButton = document.getElementById("register-button");
const regKeyGroup = document.getElementById("regkey-group"); const regKeyGroup = document.getElementById("regkey-group");
async function start() { async function start() {
updateLoadingStatus("loading.loading"); updateLoadingStatus("loading.loading");
// //
updateLoadingStatus("loading.done"); updateLoadingStatus("loading.done");
await loadingFadeOut(); await loadingFadeOut();
} }
toRegister.addEventListener('click', () => { toRegister.addEventListener('click', () => {
authContainer.className = 'auth-container show-register'; authContainer.className = 'auth-container show-register';
@ -323,7 +323,7 @@
try { try {
await updateProtocolAndUrl(loginHost.value); await updateProtocolAndUrl(loginHost.value);
let res = await Auth(loginUsername.value, loginPassword.value); let res = await Auth(loginUsername.value, loginPassword.value);
if (res.startsWith("success:")) if (res.startsWith("success:"))
{ {
let parts = res.split("|"); let parts = res.split("|");
@ -337,14 +337,14 @@
} }
await loadingFadeIn(); await loadingFadeIn();
window.location.href = "../"; window.location.href = "../";
} }
else else
{ {
showBlahNotification(res); showBlahNotification(res);
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
showBlahNotification("error:something.wrong.mayb.pass"); showBlahNotification("error:something.wrong.mayb.pass");
@ -436,7 +436,7 @@
if (res.startsWith("success")) { if (res.startsWith("success")) {
showBlahNotification(res); showBlahNotification(res);
await delay(1000); await delay(1000);
let resolvedId = await fetchAsync(`${url}/nametoid?u=${registerUsername.value}`); let resolvedId = await fetchAsync(`${url}/nametoid?u=${registerUsername.value}`);
if (resolvedId && !resolvedId.startsWith("error:")) { if (resolvedId && !resolvedId.startsWith("error:")) {
localStorage.setItem("id", resolvedId.split(":")[0]); localStorage.setItem("id", resolvedId.split(":")[0]);
@ -458,7 +458,7 @@
authContainer.className = 'auth-container show-register'; authContainer.className = 'auth-container show-register';
} }
}); });
async function regHostChanged() async function regHostChanged()
{ {
loginHost.value = registerHost.value; loginHost.value = registerHost.value;
@ -495,7 +495,7 @@
setServerStatus("Can't ping this server", "red"); setServerStatus("Can't ping this server", "red");
} }
} }
function setServerStatus(status, color) function setServerStatus(status, color)
{ {
let elements = document.getElementsByClassName("serverstatus"); let elements = document.getElementsByClassName("serverstatus");

View file

@ -2598,8 +2598,18 @@ async function renderMessages(messages, isPrepend = false) {
let oldText = existingChild.querySelector('.chat-message-text'); let oldText = existingChild.querySelector('.chat-message-text');
let newText = newChild.querySelector('.chat-message-text'); let newText = newChild.querySelector('.chat-message-text');
if (oldText && newText && oldText.innerHTML !== newText.innerHTML) { if (oldText && newText) {
oldText.innerHTML = newText.innerHTML; if (oldText.innerHTML !== newText.innerHTML) {
oldText.innerHTML = newText.innerHTML;
}
if (oldText.getAttribute('style') !== newText.getAttribute('style')) {
let newStyle = newText.getAttribute('style');
if (newStyle) {
oldText.setAttribute('style', newStyle);
} else {
oldText.removeAttribute('style');
}
}
} }
let oldReactions = existingChild.querySelector('.chat-message-reactions'); let oldReactions = existingChild.querySelector('.chat-message-reactions');
@ -3218,7 +3228,7 @@ async function loadEmotes() {
reactionPickerMenuHtml = ` reactionPickerMenuHtml = `
<div style="display: flex; flex-direction: column; gap: 0.4rem; padding: 0.2rem; min-width: 14rem;"> <div style="display: flex; flex-direction: column; gap: 0.4rem; padding: 0.2rem; min-width: 14rem;">
<input type="text" id="reaction-picker-search" placeholder="Type text / emoji..." oninput="filterReactions(this.value)" onkeydown="if(event.key === 'Enter') { if(this.value.trim() !== '') reactMessagePrompt(currentContextMenuMsgId, encodeURIComponent(this.value)); }" style="width: auto; align-self: stretch; margin: 0; box-sizing: border-box; padding: 0.5rem 0.6rem; border-radius: 0.65rem; border: var(--border-width) solid var(--light-border-color); background: transparent; color: var(--text-color); font-family: inherit;"> <input type="text" id="reaction-picker-search" placeholder="{blah(placeholder.type.text.emoji)}" oninput="filterReactions(this.value)" onkeydown="if(event.key === 'Enter') { if(this.value.trim() !== '') reactMessagePrompt(currentContextMenuMsgId, encodeURIComponent(this.value)); }" style="width: auto; align-self: stretch; margin: 0; box-sizing: border-box; padding: 0.5rem 0.6rem; border-radius: 0.65rem; border: var(--border-width) solid var(--light-border-color); background: transparent; color: var(--text-color); font-family: inherit;">
<div id="reaction-picker-grid" style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.2rem; max-height: 12rem; overflow-y: auto; overflow-x: hidden;"> <div id="reaction-picker-grid" style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.2rem; max-height: 12rem; overflow-y: auto; overflow-x: hidden;">
${loadedEmotes.map(emoji => `<button onclick="reactMessagePrompt(currentContextMenuMsgId, encodeURIComponent('${emoji.char}'))" data-name="${emoji.name}" style="min-width: unset; width: 100%; aspect-ratio: 1; display: flex; justify-content: center; align-items: center; padding: 0; font-size: 1.2rem; border-radius: 0.65rem;" class="reaction-emoji-btn">${emoji.char}</button>`).join('')} ${loadedEmotes.map(emoji => `<button onclick="reactMessagePrompt(currentContextMenuMsgId, encodeURIComponent('${emoji.char}'))" data-name="${emoji.name}" style="min-width: unset; width: 100%; aspect-ratio: 1; display: flex; justify-content: center; align-items: center; padding: 0; font-size: 1.2rem; border-radius: 0.65rem;" class="reaction-emoji-btn">${emoji.char}</button>`).join('')}
</div> </div>

View file

@ -331,7 +331,7 @@ var profileSettings = `
<img id="settings-avatar-preview" src="" style="width: 4.5rem; height: 4.5rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255, 255, 255, 0.2); object-fit: cover; background: var(--main-bg-color);"> <img id="settings-avatar-preview" src="" style="width: 4.5rem; height: 4.5rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255, 255, 255, 0.2); object-fit: cover; background: var(--main-bg-color);">
<div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;"> <div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;">
<div style="font-weight: bold;"><blah>title.avatar</blah></div> <div style="font-weight: bold;"><blah>title.avatar</blah></div>
<button class="submit-button" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('settings-avatar-input').click()">Change Avatar</button> <button class="submit-button blah" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('settings-avatar-input').click()">action.change.avatar</button>
<input type="file" id="settings-avatar-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'avatar')"> <input type="file" id="settings-avatar-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'avatar')">
</div> </div>
</div> </div>
@ -342,7 +342,7 @@ var profileSettings = `
</div> </div>
<div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;"> <div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;">
<div style="font-weight: bold;"><blah>title.banner</blah></div> <div style="font-weight: bold;"><blah>title.banner</blah></div>
<button class="submit-button" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('settings-banner-input').click()">Change Banner</button> <button class="submit-button blah" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('settings-banner-input').click()">action.change.banner</button>
<input type="file" id="settings-banner-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'banner')"> <input type="file" id="settings-banner-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'banner')">
</div> </div>
</div> </div>