diff --git a/android/app/src/main/assets/public/blah/en-cat.json b/android/app/src/main/assets/public/blah/en-cat.json index 16927f31..e2d7812c 100644 --- a/android/app/src/main/assets/public/blah/en-cat.json +++ b/android/app/src/main/assets/public/blah/en-cat.json @@ -194,5 +194,12 @@ "placeholder.type.text.emoji": "Type purr / meow...", "action.cancel": "Cancel", "title.crop.image": "Crop Image", - "title.zoom": "Zoom" + "title.zoom": "Zoom", + "title.pin.message": "Pin", + "title.unpin.message": "Unpin", + "message.pinned.by": "{0} pinned a {1}.", + "message.unpinned.by": "{0} unpinned a {1}.", + "title.pinned.messages": "Pinned", + "error.pinned.messages.empty": "No pinned meows", + "title.message.lowercase": "meowsage" } \ No newline at end of file diff --git a/android/app/src/main/assets/public/blah/en-us.json b/android/app/src/main/assets/public/blah/en-us.json index dc3c41f2..21b5fcf2 100644 --- a/android/app/src/main/assets/public/blah/en-us.json +++ b/android/app/src/main/assets/public/blah/en-us.json @@ -194,5 +194,12 @@ "placeholder.type.text.emoji": "Type text / emoji...", "action.cancel": "Cancel", "title.crop.image": "Crop Image", - "title.zoom": "Zoom" + "title.zoom": "Zoom", + "title.pin.message": "Pin", + "title.unpin.message": "Unpin", + "message.pinned.by": "{0} pinned a {1}.", + "message.unpinned.by": "{0} unpinned a {1}.", + "title.pinned.messages": "Pinned", + "error.pinned.messages.empty": "No pinned messages", + "title.message.lowercase": "message" } \ No newline at end of file diff --git a/android/app/src/main/assets/public/blah/pl-pl.json b/android/app/src/main/assets/public/blah/pl-pl.json index d59ae8b3..7ab7e4bf 100644 --- a/android/app/src/main/assets/public/blah/pl-pl.json +++ b/android/app/src/main/assets/public/blah/pl-pl.json @@ -194,5 +194,12 @@ "placeholder.type.text.emoji": "Wpisz tekst / emoji...", "action.cancel": "Anuluj", "title.crop.image": "Przytnij obraz", - "title.zoom": "Przybliżenie" + "title.zoom": "Przybliżenie", + "title.pin.message": "Przypnij", + "title.unpin.message": "Odepnij", + "message.pinned.by": "{0} przypiął {1}.", + "message.unpinned.by": "{0} odpiął {1}.", + "title.pinned.messages": "Przypięte", + "error.pinned.messages.empty": "Brak przypiętych wiadomości", + "title.message.lowercase": "wiadomość" } \ No newline at end of file diff --git a/android/app/src/main/assets/public/icons.js b/android/app/src/main/assets/public/icons.js index 9d419cd5..06596908 100644 --- a/android/app/src/main/assets/public/icons.js +++ b/android/app/src/main/assets/public/icons.js @@ -16,7 +16,6 @@ const ICONS = { _feather: function(content, props) { return this._create(content, { viewBox: "0 0 24 24", width: "1.4rem", height: "1.4rem", fill: "none", stroke: "var(--text-color)", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, props); }, - addChat: (props) => ICONS._material(``, props), createGroup: (props) => ICONS._material(``, props), createSpace: (props) => ICONS._material(``, props), @@ -28,8 +27,8 @@ const ICONS = { delete: (props) => ICONS._material(``, props), menu: (props) => ICONS._material(``, props), arrowBack: (props) => ICONS._material(``, props), - pin: (props) => ICONS._material(``, props), - call: (props) => ICONS._material(``, props), + pin: (props) => ICONS._material(``, props), + call: (props) => ICONS._material(``, props), account: (props) => ICONS._material(``, props), settings: (props) => ICONS._material(``, props), profile: (props) => ICONS._material(``, props), diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js index 21860dfc..5334dbdd 100644 --- a/android/app/src/main/assets/public/main.js +++ b/android/app/src/main/assets/public/main.js @@ -1350,7 +1350,14 @@ async function switchRoomContent(title, content, showRoomBar, icon = "", skipMob var currentDetailsContentTitle = null; var currentDetailsContentHtml = null; -async function switchDetailsContent(title, content) { +var savedDetailsHtml = null; +var savedDetailsTitle = null; + +async function switchDetailsContent(title, content, showBack = false, backAction = "", saveCurrent = false) { + if (saveCurrent && currentDetailsContentTitle !== title) { + savedDetailsHtml = currentDetailsContentHtml; + savedDetailsTitle = currentDetailsContentTitle; + } if (currentDetailsContentTitle === title && currentDetailsContentHtml === content) return; currentDetailsContentTitle = title; currentDetailsContentHtml = content; @@ -1364,13 +1371,14 @@ async function switchDetailsContent(title, content) { roomsTopBarTransition.style.opacity = "0"; const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); - if (window.innerWidth > 55 * rem) { + if (window.innerWidth > 55 * rem || mainScreen.classList.contains('mobile-details')) { await delay(200); } - let pinCallBtns = `${pinBtnHtml}${callBtnHtml}`; - roomsTopBarTransition.innerHTML = `${processBlah(title)}
${pinCallBtns}`; + let pinCallBtns = showBack ? `${callBtnHtml}` : `${pinBtnHtml}${callBtnHtml}`; + let backBtn = showBack ? `` : ""; + roomsTopBarTransition.innerHTML = `${backBtn}${processBlah(title)}
${pinCallBtns}`; let parser = new DOMParser(); let doc = parser.parseFromString(content, "text/html"); @@ -1495,7 +1503,7 @@ document.addEventListener("click", (e) => { if (mainScreen && mainScreen.classList.contains('mobile-details')) { const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); if (window.innerWidth <= 55 * rem) { - if (!roomDetailsBar.contains(e.target) && !e.target.closest('#room-topbar-clickable') && !e.target.closest('.profile-popup-container')) { + if (!roomDetailsBar.contains(e.target) && !e.target.closest('#room-topbar-clickable') && !e.target.closest('.profile-popup-container') && !e.target.closest('.room-action-pin')) { mobileNavBack(); } } @@ -1513,6 +1521,21 @@ if (App) { } function popstate() { + if (fixedContextMenu && fixedContextMenu.classList.contains("show")) { + fixedContextMenu.classList.remove("show"); + if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); + if (typeof setActiveSidebarIndicator === "function" && typeof currentMainIndicator !== "undefined") { + setActiveSidebarIndicator(currentMainIndicator); + } + return; + } + + let cropperPopup = document.getElementById("cropper-popup-container"); + if (cropperPopup && cropperPopup.classList.contains("show")) { + cropperPopup.classList.remove("show"); + return; + } + if (isProfileOpen) { closeProfile(); return; @@ -1582,7 +1605,17 @@ async function openProfile(accountId) { let bannerUrl = `${url}/user/storage/public/getentry?id=${accountId}&e=larp.profile.banner`; if (accountId === window.id && window.profileUpdateTimestamp) bannerUrl += `&t=${window.profileUpdateTimestamp}`; - let bannerHtml = ``; + let bannerHtml = ``; + setTimeout(() => { + let bannerImg = document.querySelector("#profile-popup-inner .profile-banner"); + if (bannerImg) { + bannerImg.addEventListener("error", function() { + let div = document.createElement("div"); + div.className = "profile-banner"; + this.parentNode.replaceChild(div, this); + }); + } + }, 50); let bio = await fetchAsync(`${url}/user/storage/public/getentry?id=${accountId}&e=larp.profile.bio`); if (bio.startsWith("error:")) bio = ""; @@ -1624,7 +1657,8 @@ async function openProfile(accountId) { } if (hasDm) { - actionBtnHtml = ``; + let safeName = encodeURIComponent(nameWithHost).replace(/'/g, "%27"); + actionBtnHtml = ``; } else { let sentInvite = false; let receivedInvite = false; @@ -2141,8 +2175,10 @@ async function renderDms(res) { let isRead = dmData.string3 === "true"; let nameStyle = isRead ? "opacity: 0.8;" : "opacity: 1;"; + let safeTargetUsername = encodeURIComponent(targetUsername).replace(/'/g, "%27"); + html += ` - `; + } + } +} + + +async function togglePinMessage(msgId) { + if (fixedContextMenu) fixedContextMenu.classList.remove("show"); + if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); + + try { + let payload = JSON.stringify({ + string1: currentDmId, + string2: msgId + }); + await fetchEncrypted("dm/message/pin/toggle", payload); + } catch (e) { + console.error(e); + showBlahNotification("error.network.error"); + } +} + +async function restoreDmDetails() { + if (savedDetailsHtml && savedDetailsTitle) { + await switchDetailsContent(savedDetailsTitle, savedDetailsHtml); + savedDetailsHtml = null; + savedDetailsTitle = null; } } @@ -3245,9 +3354,12 @@ async function loadEmotes() { reactionPickerMenuHtml = `
- +
- ${loadedEmotes.map(emoji => ``).join('')} + ${loadedEmotes.map(emoji => { + let safeChar = encodeURIComponent(emoji.char).replace(/'/g, "%27"); + return ``; + }).join('')}
- - + @@ -236,9 +236,9 @@ var attachmentMenu = ` ${ICONS.poll()} action.attachment.poll - - - + + + `; var addSpaceMenu = ` @@ -255,7 +255,7 @@ var messageContextMenu = ` ${ICONS.replyAction()} title.reply.message - @@ -263,11 +263,12 @@ var messageContextMenu = ` ${ICONS.delete({ fill: "var(--big-red)" })} title.delete.message +
`; //elements var backBtnHtml = ``; -var pinBtnHtml = ``; +var pinBtnHtml = ``; var callBtnHtml = ``; var settingsBar = ` diff --git a/webroot/blah/en-cat.json b/webroot/blah/en-cat.json index 16927f31..e2d7812c 100644 --- a/webroot/blah/en-cat.json +++ b/webroot/blah/en-cat.json @@ -194,5 +194,12 @@ "placeholder.type.text.emoji": "Type purr / meow...", "action.cancel": "Cancel", "title.crop.image": "Crop Image", - "title.zoom": "Zoom" + "title.zoom": "Zoom", + "title.pin.message": "Pin", + "title.unpin.message": "Unpin", + "message.pinned.by": "{0} pinned a {1}.", + "message.unpinned.by": "{0} unpinned a {1}.", + "title.pinned.messages": "Pinned", + "error.pinned.messages.empty": "No pinned meows", + "title.message.lowercase": "meowsage" } \ No newline at end of file diff --git a/webroot/blah/en-us.json b/webroot/blah/en-us.json index dc3c41f2..21b5fcf2 100644 --- a/webroot/blah/en-us.json +++ b/webroot/blah/en-us.json @@ -194,5 +194,12 @@ "placeholder.type.text.emoji": "Type text / emoji...", "action.cancel": "Cancel", "title.crop.image": "Crop Image", - "title.zoom": "Zoom" + "title.zoom": "Zoom", + "title.pin.message": "Pin", + "title.unpin.message": "Unpin", + "message.pinned.by": "{0} pinned a {1}.", + "message.unpinned.by": "{0} unpinned a {1}.", + "title.pinned.messages": "Pinned", + "error.pinned.messages.empty": "No pinned messages", + "title.message.lowercase": "message" } \ No newline at end of file diff --git a/webroot/blah/pl-pl.json b/webroot/blah/pl-pl.json index d59ae8b3..7ab7e4bf 100644 --- a/webroot/blah/pl-pl.json +++ b/webroot/blah/pl-pl.json @@ -194,5 +194,12 @@ "placeholder.type.text.emoji": "Wpisz tekst / emoji...", "action.cancel": "Anuluj", "title.crop.image": "Przytnij obraz", - "title.zoom": "Przybliżenie" + "title.zoom": "Przybliżenie", + "title.pin.message": "Przypnij", + "title.unpin.message": "Odepnij", + "message.pinned.by": "{0} przypiął {1}.", + "message.unpinned.by": "{0} odpiął {1}.", + "title.pinned.messages": "Przypięte", + "error.pinned.messages.empty": "Brak przypiętych wiadomości", + "title.message.lowercase": "wiadomość" } \ No newline at end of file diff --git a/webroot/icons.js b/webroot/icons.js index 9d419cd5..06596908 100644 --- a/webroot/icons.js +++ b/webroot/icons.js @@ -16,7 +16,6 @@ const ICONS = { _feather: function(content, props) { return this._create(content, { viewBox: "0 0 24 24", width: "1.4rem", height: "1.4rem", fill: "none", stroke: "var(--text-color)", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, props); }, - addChat: (props) => ICONS._material(``, props), createGroup: (props) => ICONS._material(``, props), createSpace: (props) => ICONS._material(``, props), @@ -28,8 +27,8 @@ const ICONS = { delete: (props) => ICONS._material(``, props), menu: (props) => ICONS._material(``, props), arrowBack: (props) => ICONS._material(``, props), - pin: (props) => ICONS._material(``, props), - call: (props) => ICONS._material(``, props), + pin: (props) => ICONS._material(``, props), + call: (props) => ICONS._material(``, props), account: (props) => ICONS._material(``, props), settings: (props) => ICONS._material(``, props), profile: (props) => ICONS._material(``, props), diff --git a/webroot/main.js b/webroot/main.js index 21860dfc..5334dbdd 100644 --- a/webroot/main.js +++ b/webroot/main.js @@ -1350,7 +1350,14 @@ async function switchRoomContent(title, content, showRoomBar, icon = "", skipMob var currentDetailsContentTitle = null; var currentDetailsContentHtml = null; -async function switchDetailsContent(title, content) { +var savedDetailsHtml = null; +var savedDetailsTitle = null; + +async function switchDetailsContent(title, content, showBack = false, backAction = "", saveCurrent = false) { + if (saveCurrent && currentDetailsContentTitle !== title) { + savedDetailsHtml = currentDetailsContentHtml; + savedDetailsTitle = currentDetailsContentTitle; + } if (currentDetailsContentTitle === title && currentDetailsContentHtml === content) return; currentDetailsContentTitle = title; currentDetailsContentHtml = content; @@ -1364,13 +1371,14 @@ async function switchDetailsContent(title, content) { roomsTopBarTransition.style.opacity = "0"; const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); - if (window.innerWidth > 55 * rem) { + if (window.innerWidth > 55 * rem || mainScreen.classList.contains('mobile-details')) { await delay(200); } - let pinCallBtns = `${pinBtnHtml}${callBtnHtml}`; - roomsTopBarTransition.innerHTML = `${processBlah(title)}
${pinCallBtns}`; + let pinCallBtns = showBack ? `${callBtnHtml}` : `${pinBtnHtml}${callBtnHtml}`; + let backBtn = showBack ? `` : ""; + roomsTopBarTransition.innerHTML = `${backBtn}${processBlah(title)}
${pinCallBtns}`; let parser = new DOMParser(); let doc = parser.parseFromString(content, "text/html"); @@ -1495,7 +1503,7 @@ document.addEventListener("click", (e) => { if (mainScreen && mainScreen.classList.contains('mobile-details')) { const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); if (window.innerWidth <= 55 * rem) { - if (!roomDetailsBar.contains(e.target) && !e.target.closest('#room-topbar-clickable') && !e.target.closest('.profile-popup-container')) { + if (!roomDetailsBar.contains(e.target) && !e.target.closest('#room-topbar-clickable') && !e.target.closest('.profile-popup-container') && !e.target.closest('.room-action-pin')) { mobileNavBack(); } } @@ -1513,6 +1521,21 @@ if (App) { } function popstate() { + if (fixedContextMenu && fixedContextMenu.classList.contains("show")) { + fixedContextMenu.classList.remove("show"); + if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); + if (typeof setActiveSidebarIndicator === "function" && typeof currentMainIndicator !== "undefined") { + setActiveSidebarIndicator(currentMainIndicator); + } + return; + } + + let cropperPopup = document.getElementById("cropper-popup-container"); + if (cropperPopup && cropperPopup.classList.contains("show")) { + cropperPopup.classList.remove("show"); + return; + } + if (isProfileOpen) { closeProfile(); return; @@ -1582,7 +1605,17 @@ async function openProfile(accountId) { let bannerUrl = `${url}/user/storage/public/getentry?id=${accountId}&e=larp.profile.banner`; if (accountId === window.id && window.profileUpdateTimestamp) bannerUrl += `&t=${window.profileUpdateTimestamp}`; - let bannerHtml = ``; + let bannerHtml = ``; + setTimeout(() => { + let bannerImg = document.querySelector("#profile-popup-inner .profile-banner"); + if (bannerImg) { + bannerImg.addEventListener("error", function() { + let div = document.createElement("div"); + div.className = "profile-banner"; + this.parentNode.replaceChild(div, this); + }); + } + }, 50); let bio = await fetchAsync(`${url}/user/storage/public/getentry?id=${accountId}&e=larp.profile.bio`); if (bio.startsWith("error:")) bio = ""; @@ -1624,7 +1657,8 @@ async function openProfile(accountId) { } if (hasDm) { - actionBtnHtml = ``; + let safeName = encodeURIComponent(nameWithHost).replace(/'/g, "%27"); + actionBtnHtml = ``; } else { let sentInvite = false; let receivedInvite = false; @@ -2141,8 +2175,10 @@ async function renderDms(res) { let isRead = dmData.string3 === "true"; let nameStyle = isRead ? "opacity: 0.8;" : "opacity: 1;"; + let safeTargetUsername = encodeURIComponent(targetUsername).replace(/'/g, "%27"); + html += ` - `; + } + } +} + + +async function togglePinMessage(msgId) { + if (fixedContextMenu) fixedContextMenu.classList.remove("show"); + if (typeof clearContextMenuStyles === "function") clearContextMenuStyles(); + + try { + let payload = JSON.stringify({ + string1: currentDmId, + string2: msgId + }); + await fetchEncrypted("dm/message/pin/toggle", payload); + } catch (e) { + console.error(e); + showBlahNotification("error.network.error"); + } +} + +async function restoreDmDetails() { + if (savedDetailsHtml && savedDetailsTitle) { + await switchDetailsContent(savedDetailsTitle, savedDetailsHtml); + savedDetailsHtml = null; + savedDetailsTitle = null; } } @@ -3245,9 +3354,12 @@ async function loadEmotes() { reactionPickerMenuHtml = `
- +
- ${loadedEmotes.map(emoji => ``).join('')} + ${loadedEmotes.map(emoji => { + let safeChar = encodeURIComponent(emoji.char).replace(/'/g, "%27"); + return ``; + }).join('')}
- - + @@ -236,9 +236,9 @@ var attachmentMenu = ` ${ICONS.poll()} action.attachment.poll - - - + + + `; var addSpaceMenu = ` @@ -255,7 +255,7 @@ var messageContextMenu = ` ${ICONS.replyAction()} title.reply.message - @@ -263,11 +263,12 @@ var messageContextMenu = ` ${ICONS.delete({ fill: "var(--big-red)" })} title.delete.message +
`; //elements var backBtnHtml = ``; -var pinBtnHtml = ``; +var pinBtnHtml = ``; var callBtnHtml = ``; var settingsBar = `