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/main.js b/android/app/src/main/assets/public/main.js
index 6217d060..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();
}
}
@@ -2370,12 +2378,7 @@ function truncateFilename(name, maxLength = 20) {
return baseName.substring(0, half) + "..." + baseName.substring(baseName.length - (allowedBaseLength - half)) + ext;
}
-async function renderMessages(messages, isPrepend = false) {
- let container = document.getElementById("chat-messages");
- if (!container) return;
-
- let entries = Object.entries(messages).sort((a, b) => parseInt(a[0]) - parseInt(b[0]));
-
+async function generateMessagesHtml(entries, isPinnedContext = false) {
let html = "";
let lastAuthor = null;
@@ -2419,7 +2422,7 @@ async function renderMessages(messages, isPrepend = false) {
let safeType = encodeURIComponent(att.type).replace(/'/g, "%27");
let safeName = encodeURIComponent(att.name).replace(/'/g, "%27");
- attachmentsHtml += `
+ attachmentsHtml += `
${iconSvg}
${escapeHtml(displayName)}
@@ -2449,7 +2452,33 @@ async function renderMessages(messages, isPrepend = false) {
// Also still process the whole thing in case it's just "dm.begin.notice" without {blah()}
content = processBlah(content);
- if (msg.type === "larp.info") {
+ if (msg.type && msg.type.startsWith("larp.info")) {
+ if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin") {
+ let authorName = "Unknown";
+ if (msg.author && msg.author !== "0") {
+ if (!dmUsernameCache[msg.author]) {
+ let fullUsername = await fetchAsync(`${url}/idtoname?id=${msg.author}`);
+ if (fullUsername && !fullUsername.startsWith("error:")) {
+ dmUsernameCache[msg.author] = fullUsername;
+ dmPfpCache[msg.author] = await getAvatarUrl(msg.author, fullUsername);
+ }
+ }
+ if (dmUsernameCache[msg.author]) {
+ authorName = escapeHtml(dmUsernameCache[msg.author].split(':')[0]);
+ } else {
+ authorName = escapeHtml(msg.author.split(';')[0]);
+ }
+ }
+
+ let isPinned = msg.type === "larp.info.pin";
+ let actionStr = isPinned ? "message.pinned.by" : "message.unpinned.by";
+
+ let clickableMsg = `
${processBlah('title.message.lowercase')}`;
+ let clickableAuthor = `
${authorName}`;
+
+ let textStr = processBlah(actionStr);
+ content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableMsg);
+ }
html += `
${content}
`;
lastAuthor = null;
} else {
@@ -2560,7 +2589,7 @@ async function renderMessages(messages, isPrepend = false) {
}
let divIdStr = randId !== "" ? `id="${randId}" ` : "";
- repliedHtml = `
${ICONS.cornerUpLeft({ width: "0.8rem", height: "0.8rem", stroke: "currentColor", style: "vertical-align: middle;" })} ${processBlah('title.replied.to')} ${replyAuthor}
`;
+ repliedHtml = `
${ICONS.cornerUpLeft({ width: "0.8rem", height: "0.8rem", stroke: "currentColor", style: "vertical-align: middle;" })} ${processBlah('title.replied.to')} ${replyAuthor}
`;
}
let reactionsHtml = "";
@@ -2593,7 +2622,7 @@ async function renderMessages(messages, isPrepend = false) {
let safeRx = encodeURIComponent(rxVal).replace(/'/g, "%27");
let escapedRxVal = escapeHtml(rxVal);
let pillStyle = `display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.1rem 0.45rem; border-radius: 1rem; font-size: 0.85rem; border: var(--border-width) solid var(--light-border-color); cursor: pointer; ${style}`;
- reactionsHtml += `
${processBlah(escapedRxVal)}${rxCounts[rxVal].count > 1 ? `${rxCounts[rxVal].count}` : ""}`;
+ reactionsHtml += `
${processBlah(escapedRxVal)}${rxCounts[rxVal].count > 1 ? `${rxCounts[rxVal].count}` : ""}`;
}
reactionsHtml += `
`;
}
@@ -2603,11 +2632,11 @@ async function renderMessages(messages, isPrepend = false) {
}
html += `
-