fix czat in mobile
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.94"
|
rust-version = "1.94"
|
||||||
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
||||||
|
|||||||
+23
-4
@@ -4537,7 +4537,8 @@ dialog::backdrop {
|
|||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-editor-bubble>button {
|
.mobile-editor-bubble>button,
|
||||||
|
.mobile-editor-bubble>.mobile-color-button {
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
@@ -4548,6 +4549,25 @@ dialog::backdrop {
|
|||||||
background: rgba(255, 255, 255, .07);
|
background: rgba(255, 255, 255, .07);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-color-button {
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-color-button input[type="color"] {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-color-dot {
|
.mobile-color-dot {
|
||||||
@@ -4583,7 +4603,6 @@ dialog::backdrop {
|
|||||||
/* Movable quick actions for compact editor screens. */
|
/* Movable quick actions for compact editor screens. */
|
||||||
@media (max-width: 1499px) {
|
@media (max-width: 1499px) {
|
||||||
.pad-page .mobile-editor-bubble {
|
.pad-page .mobile-editor-bubble {
|
||||||
touch-action: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4591,10 +4610,10 @@ dialog::backdrop {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pad-page .room-details.is-mobile-open .room-popover {
|
.pad-page .room-popover.is-mobile-open {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
bottom: 72px;
|
bottom: calc(72px + env(safe-area-inset-bottom, 0px));
|
||||||
left: auto;
|
left: auto;
|
||||||
z-index: 60;
|
z-index: 60;
|
||||||
width: min(560px, calc(100vw - 24px));
|
width: min(560px, calc(100vw - 24px));
|
||||||
|
|||||||
+33
-14
@@ -27,7 +27,7 @@ export function startNoteEditor(adapter) {
|
|||||||
const modeToggle = document.querySelector("#mode-toggle"), passwordDialog = document.querySelector("#password-dialog"), identityDialog = document.querySelector("#identity-dialog");
|
const modeToggle = document.querySelector("#mode-toggle"), passwordDialog = document.querySelector("#password-dialog"), identityDialog = document.querySelector("#identity-dialog");
|
||||||
const accessLevel = document.querySelector("#access-level"), roomDetails = document.querySelector("#room-details"), roomUsers = document.querySelector("#room-users"), roomCount = document.querySelector("#room-count"), socketLatency = document.querySelector("#socket-latency"), chatMessages = document.querySelector("#chat-messages"), chatForm = document.querySelector("#chat-form"), chatInput = document.querySelector("#chat-input"), chatUnread = document.querySelector("#chat-unread"), mobileChatUnread = document.querySelector("#mobile-chat-unread");
|
const accessLevel = document.querySelector("#access-level"), roomDetails = document.querySelector("#room-details"), roomUsers = document.querySelector("#room-users"), roomCount = document.querySelector("#room-count"), socketLatency = document.querySelector("#socket-latency"), chatMessages = document.querySelector("#chat-messages"), chatForm = document.querySelector("#chat-form"), chatInput = document.querySelector("#chat-input"), chatUnread = document.querySelector("#chat-unread"), mobileChatUnread = document.querySelector("#mobile-chat-unread");
|
||||||
let unreadChat = 0;
|
let unreadChat = 0;
|
||||||
const compactToggle = document.querySelector("#compact-toggle"), lineLinksToggle = document.querySelector("#line-links-toggle"), authorshipColorsToggle = document.querySelector("#authorship-colors-toggle"), authorshipColorsLabel = document.querySelector("#authorship-colors-label"), saveEditorSettingsButton = document.querySelector("#save-editor-settings"), publicPageEnabled = document.querySelector("#public-page-enabled"), publicTaskUpdates = document.querySelector("#public-task-updates"), unprotectPublicPage = document.querySelector("#unprotect-public-page"), participantBadges = document.querySelector("#participant-badges"), fontFamily = document.querySelector("#font-family"), fontSize = document.querySelector("#font-size"), currentUser = document.querySelector("#current-user"), userColorPicker = document.querySelector("#user-color-picker"), useGlobalColorButton = document.querySelector("#use-global-color");
|
const compactToggle = document.querySelector("#compact-toggle"), lineLinksToggle = document.querySelector("#line-links-toggle"), authorshipColorsToggle = document.querySelector("#authorship-colors-toggle"), authorshipColorsLabel = document.querySelector("#authorship-colors-label"), saveEditorSettingsButton = document.querySelector("#save-editor-settings"), publicPageEnabled = document.querySelector("#public-page-enabled"), publicTaskUpdates = document.querySelector("#public-task-updates"), unprotectPublicPage = document.querySelector("#unprotect-public-page"), participantBadges = document.querySelector("#participant-badges"), fontFamily = document.querySelector("#font-family"), fontSize = document.querySelector("#font-size"), currentUser = document.querySelector("#current-user"), userColorPicker = document.querySelector("#user-color-picker"), mobileColorPicker = document.querySelector("#mobile-color-picker"), useGlobalColorButton = document.querySelector("#use-global-color");
|
||||||
const shareToken = new URLSearchParams(location.search).get("share"); if (shareToken) setAccessToken(adapter.access.kind, adapter.access.key, shareToken);
|
const shareToken = new URLSearchParams(location.search).get("share"); if (shareToken) setAccessToken(adapter.access.kind, adapter.access.key, shareToken);
|
||||||
const notePreferenceKey = name => `rustpad:${name}:${adapter.access.kind}:${adapter.access.key}`;
|
const notePreferenceKey = name => `rustpad:${name}:${adapter.access.kind}:${adapter.access.key}`;
|
||||||
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, resourceUnlocked = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "", globalColor = "", noteColor = "", presenceUsers = [], authorshipMode = "simple", authorshipColorsEnabled = true, lastRevealedLineHash = "";
|
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, resourceUnlocked = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "", globalColor = "", noteColor = "", presenceUsers = [], authorshipMode = "simple", authorshipColorsEnabled = true, lastRevealedLineHash = "";
|
||||||
@@ -66,7 +66,7 @@ export function startNoteEditor(adapter) {
|
|||||||
return /^#[0-9a-f]{6}$/i.test(parts.color) && parts.color.toLowerCase() === color.toLowerCase();
|
return /^#[0-9a-f]{6}$/i.test(parts.color) && parts.color.toLowerCase() === color.toLowerCase();
|
||||||
}, replacement, contentLength);
|
}, replacement, contentLength);
|
||||||
}
|
}
|
||||||
function updateCurrentUser() { const color = currentUserColor() || defaultColorFor(nickname); const overridden = Boolean(noteUserColor()); currentUser.querySelector(".user-chip__name").textContent = nickname; currentUser.style.setProperty("--owner", color); currentUser.title = overridden ? "Note color override" : "Global profile color"; userColorPicker.value = /^#[0-9a-f]{6}$/i.test(color) ? color : "#7c6cff"; useGlobalColorButton.hidden = !overridden; document.querySelector(".mobile-editor-bubble")?.style.setProperty("--owner", color); }
|
function updateCurrentUser() { const color = currentUserColor() || defaultColorFor(nickname); const pickerColor = /^#[0-9a-f]{6}$/i.test(color) ? color : "#7c6cff"; const overridden = Boolean(noteUserColor()); currentUser.querySelector(".user-chip__name").textContent = nickname; currentUser.style.setProperty("--owner", color); currentUser.title = overridden ? "Note color override" : "Global profile color"; userColorPicker.value = pickerColor; if (mobileColorPicker) mobileColorPicker.value = pickerColor; useGlobalColorButton.hidden = !overridden; document.querySelector(".mobile-editor-bubble")?.style.setProperty("--owner", color); }
|
||||||
function sessionHeaders() { const token = accessToken || getAuthToken(); return token ? { Authorization: `Bearer ${token}` } : {}; }
|
function sessionHeaders() { const token = accessToken || getAuthToken(); return token ? { Authorization: `Bearer ${token}` } : {}; }
|
||||||
function accountHeaders() { const token = getAuthToken(); return token ? { Authorization: `Bearer ${token}` } : {}; }
|
function accountHeaders() { const token = getAuthToken(); return token ? { Authorization: `Bearer ${token}` } : {}; }
|
||||||
async function loadNoteInfo() {
|
async function loadNoteInfo() {
|
||||||
@@ -632,18 +632,32 @@ export function startNoteEditor(adapter) {
|
|||||||
});
|
});
|
||||||
modeToggle.addEventListener("click", () => { uiState = { ...uiState, mode: uiState.mode === "markdown" ? "text" : "markdown" }; applyUi({ write: true }); }); lineToggle.addEventListener("change", () => { localStorage.setItem("rustpad:line-numbers", lineToggle.checked ? "on" : "off"); renderGutter(); }); previewLineToggle.addEventListener("change", () => { localStorage.setItem("rustpad:preview-line-numbers", previewLineToggle.checked ? "on" : "off"); renderGutter(); alignPreviewLineNumbers(preview); }); compactToggle.addEventListener("change", () => { localStorage.setItem("rustpad:compact", compactToggle.checked ? "on" : "off"); applyUi(); }); lineLinksToggle.addEventListener("change", () => { localStorage.setItem("rustpad:line-links", lineLinksToggle.checked ? "on" : "off"); renderGutter(); }); fontFamily.addEventListener("change", () => { localStorage.setItem("rustpad:font-family", fontFamily.value); applyUi(); }); fontSize.addEventListener("change", () => { localStorage.setItem("rustpad:font-size", fontSize.value); applyUi(); });
|
modeToggle.addEventListener("click", () => { uiState = { ...uiState, mode: uiState.mode === "markdown" ? "text" : "markdown" }; applyUi({ write: true }); }); lineToggle.addEventListener("change", () => { localStorage.setItem("rustpad:line-numbers", lineToggle.checked ? "on" : "off"); renderGutter(); }); previewLineToggle.addEventListener("change", () => { localStorage.setItem("rustpad:preview-line-numbers", previewLineToggle.checked ? "on" : "off"); renderGutter(); alignPreviewLineNumbers(preview); }); compactToggle.addEventListener("change", () => { localStorage.setItem("rustpad:compact", compactToggle.checked ? "on" : "off"); applyUi(); }); lineLinksToggle.addEventListener("change", () => { localStorage.setItem("rustpad:line-links", lineLinksToggle.checked ? "on" : "off"); renderGutter(); }); fontFamily.addEventListener("change", () => { localStorage.setItem("rustpad:font-family", fontFamily.value); applyUi(); }); fontSize.addEventListener("change", () => { localStorage.setItem("rustpad:font-size", fontSize.value); applyUi(); });
|
||||||
document.querySelector("#mobile-files-button")?.addEventListener("click", () => document.querySelector("#files-button")?.click());
|
document.querySelector("#mobile-files-button")?.addEventListener("click", () => document.querySelector("#files-button")?.click());
|
||||||
document.querySelector("#mobile-color-button")?.addEventListener("click", () => userColorPicker.click());
|
|
||||||
const compactBubbleQuery = matchMedia("(max-width: 1499px)");
|
const compactBubbleQuery = matchMedia("(max-width: 1499px)");
|
||||||
|
const roomPopover = roomDetails.querySelector(".room-popover");
|
||||||
|
function setMobileChatOpen(open) {
|
||||||
|
if (!roomPopover) return;
|
||||||
|
if (open && compactBubbleQuery.matches) {
|
||||||
|
roomDetails.classList.add("is-mobile-open");
|
||||||
|
roomPopover.classList.add("is-mobile-open");
|
||||||
|
document.body.append(roomPopover);
|
||||||
|
roomDetails.open = true;
|
||||||
|
clearUnread();
|
||||||
|
requestAnimationFrame(() => chatInput.focus());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
roomDetails.open = false;
|
||||||
|
roomDetails.classList.remove("is-mobile-open");
|
||||||
|
roomPopover.classList.remove("is-mobile-open");
|
||||||
|
if (roomPopover.parentElement !== roomDetails) roomDetails.append(roomPopover);
|
||||||
|
}
|
||||||
document.querySelector("#mobile-chat-button")?.addEventListener("click", event => {
|
document.querySelector("#mobile-chat-button")?.addEventListener("click", event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
roomDetails.classList.add("is-mobile-open");
|
setMobileChatOpen(true);
|
||||||
roomDetails.open = true;
|
|
||||||
clearUnread();
|
|
||||||
requestAnimationFrame(() => chatInput.focus());
|
|
||||||
});
|
});
|
||||||
document.addEventListener("click", event => {
|
document.addEventListener("click", event => {
|
||||||
if (roomDetails.classList.contains("is-mobile-open") && !event.target.closest("#mobile-chat-button, #room-details")) { roomDetails.open = false; roomDetails.classList.remove("is-mobile-open"); }
|
if (roomDetails.classList.contains("is-mobile-open") && !event.target.closest("#mobile-chat-button, #room-details, .room-popover.is-mobile-open")) setMobileChatOpen(false);
|
||||||
});
|
});
|
||||||
|
compactBubbleQuery.addEventListener("change", event => { if (!event.matches && roomDetails.classList.contains("is-mobile-open")) setMobileChatOpen(false); });
|
||||||
const mobileBubble = document.querySelector("#mobile-editor-bubble");
|
const mobileBubble = document.querySelector("#mobile-editor-bubble");
|
||||||
const mobileBubbleDrag = document.querySelector("#mobile-bubble-drag");
|
const mobileBubbleDrag = document.querySelector("#mobile-bubble-drag");
|
||||||
const bubblePositionKey = `rustpad:mobile-bubble:${adapter.access.kind}`;
|
const bubblePositionKey = `rustpad:mobile-bubble:${adapter.access.kind}`;
|
||||||
@@ -937,13 +951,16 @@ export function startNoteEditor(adapter) {
|
|||||||
publicTaskUpdates.addEventListener("change", async () => { publicTaskUpdates.disabled = true; try { await savePublicOptions(); toast(publicTaskUpdates.checked ? "Public task updates enabled" : "Public task updates disabled"); } catch (error) { publicTaskUpdates.checked = !publicTaskUpdates.checked; toast(error.message); } finally { updatePageControls(); } });
|
publicTaskUpdates.addEventListener("change", async () => { publicTaskUpdates.disabled = true; try { await savePublicOptions(); toast(publicTaskUpdates.checked ? "Public task updates enabled" : "Public task updates disabled"); } catch (error) { publicTaskUpdates.checked = !publicTaskUpdates.checked; toast(error.message); } finally { updatePageControls(); } });
|
||||||
unprotectPublicPage.addEventListener("change", async () => { unprotectPublicPage.disabled = true; try { await savePublicOptions(); toast(unprotectPublicPage.checked ? "Published page is now unprotected" : "Published page protection enabled"); } catch (error) { unprotectPublicPage.checked = !unprotectPublicPage.checked; toast(error.message); } finally { updatePageControls(); } });
|
unprotectPublicPage.addEventListener("change", async () => { unprotectPublicPage.disabled = true; try { await savePublicOptions(); toast(unprotectPublicPage.checked ? "Published page is now unprotected" : "Published page protection enabled"); } catch (error) { unprotectPublicPage.checked = !unprotectPublicPage.checked; toast(error.message); } finally { updatePageControls(); } });
|
||||||
publishPageButton.addEventListener("click", async () => { if (!publicPageEnabled.checked) return; try { const result = await savePublicOptions(); if (!result.url) throw new Error("Page is disabled"); const url = new URL(result.url, location.origin).href; await copyText(url); toast("Page link copied"); window.open(url, "_blank", "noopener"); } catch (error) { toast(error.message); } });
|
publishPageButton.addEventListener("click", async () => { if (!publicPageEnabled.checked) return; try { const result = await savePublicOptions(); if (!result.url) throw new Error("Page is disabled"); const url = new URL(result.url, location.origin).href; await copyText(url); toast("Page link copied"); window.open(url, "_blank", "noopener"); } catch (error) { toast(error.message); } });
|
||||||
roomDetails.addEventListener("toggle", () => { if (roomDetails.open) { clearUnread(); chatInput.focus(); if ("Notification" in window && Notification.permission === "default") Notification.requestPermission().catch(() => { }); } else { roomDetails.classList.remove("is-mobile-open"); } });
|
roomDetails.addEventListener("toggle", () => { if (roomDetails.open) { clearUnread(); chatInput.focus(); if ("Notification" in window && Notification.permission === "default") Notification.requestPermission().catch(() => { }); } else if (roomDetails.classList.contains("is-mobile-open")) { setMobileChatOpen(false); } });
|
||||||
document.addEventListener("visibilitychange", () => { if (!document.hidden && roomDetails.open) clearUnread(); });
|
document.addEventListener("visibilitychange", () => { if (!document.hidden && roomDetails.open) clearUnread(); });
|
||||||
chatForm.addEventListener("submit", event => { event.preventDefault(); const text = chatInput.value.trim(); if (!text || !socket) return; socket.chat(text); chatInput.value = ""; chatInput.focus(); });
|
chatForm.addEventListener("submit", event => { event.preventDefault(); const text = chatInput.value.trim(); if (!text || !socket) return; socket.chat(text); chatInput.value = ""; chatInput.focus(); });
|
||||||
if (!chatMessages.children.length) { const empty = document.createElement("p"); empty.className = "chat-empty"; empty.textContent = "No messages yet"; chatMessages.append(empty); }
|
if (!chatMessages.children.length) { const empty = document.createElement("p"); empty.className = "chat-empty"; empty.textContent = "No messages yet"; chatMessages.append(empty); }
|
||||||
currentUser.addEventListener("click", () => userColorPicker.click());
|
currentUser.addEventListener("click", () => {
|
||||||
userColorPicker.addEventListener("change", async () => {
|
if (typeof userColorPicker.showPicker === "function") userColorPicker.showPicker();
|
||||||
noteColor = userColorPicker.value;
|
else userColorPicker.click();
|
||||||
|
});
|
||||||
|
async function saveUserColor(color) {
|
||||||
|
noteColor = color;
|
||||||
if (getAuthToken()) {
|
if (getAuthToken()) {
|
||||||
try { await adapter.saveColor(accountHeaders(), noteColor); } catch (error) { toast(error.message); await loadNoteInfo(); return; }
|
try { await adapter.saveColor(accountHeaders(), noteColor); } catch (error) { toast(error.message); await loadNoteInfo(); return; }
|
||||||
} else {
|
} else {
|
||||||
@@ -953,9 +970,11 @@ export function startNoteEditor(adapter) {
|
|||||||
const replacement = currentOwner();
|
const replacement = currentOwner();
|
||||||
authorship = replaceAuthorshipOwner(authorship, owner => ownerName(owner) === nickname, replacement, editor.value.length);
|
authorship = replaceAuthorshipOwner(authorship, owner => ownerName(owner) === nickname, replacement, editor.value.length);
|
||||||
updateCurrentUser(); render();
|
updateCurrentUser(); render();
|
||||||
socket?.setColor(userColorPicker.value);
|
socket?.setColor(noteColor);
|
||||||
if (socket) socket.update(editor.value, serializeAuthorship(authorship, editor.value.length));
|
if (socket) socket.update(editor.value, serializeAuthorship(authorship, editor.value.length));
|
||||||
});
|
}
|
||||||
|
userColorPicker.addEventListener("change", () => saveUserColor(userColorPicker.value));
|
||||||
|
mobileColorPicker?.addEventListener("change", () => saveUserColor(mobileColorPicker.value));
|
||||||
useGlobalColorButton.addEventListener("click", async () => {
|
useGlobalColorButton.addEventListener("click", async () => {
|
||||||
if (getAuthToken()) {
|
if (getAuthToken()) {
|
||||||
try { await adapter.saveColor(accountHeaders(), null); } catch (error) { toast(error.message); return; }
|
try { await adapter.saveColor(accountHeaders(), null); } catch (error) { toast(error.message); return; }
|
||||||
|
|||||||
+3
-2
@@ -244,8 +244,9 @@
|
|||||||
<button id="mobile-bubble-drag" class="mobile-bubble-drag" type="button" title="Move quick actions"
|
<button id="mobile-bubble-drag" class="mobile-bubble-drag" type="button" title="Move quick actions"
|
||||||
aria-label="Move quick actions">⋮⋮</button>
|
aria-label="Move quick actions">⋮⋮</button>
|
||||||
<button id="mobile-files-button" type="button" title="Files" aria-label="Open files">📎</button>
|
<button id="mobile-files-button" type="button" title="Files" aria-label="Open files">📎</button>
|
||||||
<button id="mobile-color-button" type="button" title="Editor color" aria-label="Change editor color"><span
|
<label id="mobile-color-button" class="mobile-color-button" title="Editor color"
|
||||||
class="mobile-color-dot" aria-hidden="true"></span></button>
|
aria-label="Change editor color"><span class="mobile-color-dot" aria-hidden="true"></span><input
|
||||||
|
id="mobile-color-picker" type="color" aria-label="Change editor color"></label>
|
||||||
<button id="mobile-chat-button" class="mobile-chat-button" type="button" title="Chat"
|
<button id="mobile-chat-button" class="mobile-chat-button" type="button" title="Chat"
|
||||||
aria-label="Open chat">💬<span id="mobile-chat-unread" class="mobile-chat-unread" hidden></span></button>
|
aria-label="Open chat">💬<span id="mobile-chat-unread" class="mobile-chat-unread" hidden></span></button>
|
||||||
<span id="mobile-connection-status" class="mobile-connection-status" title="WebSocket status"><span
|
<span id="mobile-connection-status" class="mobile-connection-status" title="WebSocket status"><span
|
||||||
|
|||||||
+3
-2
@@ -245,8 +245,9 @@
|
|||||||
<button id="mobile-bubble-drag" class="mobile-bubble-drag" type="button" title="Move quick actions"
|
<button id="mobile-bubble-drag" class="mobile-bubble-drag" type="button" title="Move quick actions"
|
||||||
aria-label="Move quick actions">⋮⋮</button>
|
aria-label="Move quick actions">⋮⋮</button>
|
||||||
<button id="mobile-files-button" type="button" title="Files" aria-label="Open files">📎</button>
|
<button id="mobile-files-button" type="button" title="Files" aria-label="Open files">📎</button>
|
||||||
<button id="mobile-color-button" type="button" title="Editor color" aria-label="Change editor color"><span
|
<label id="mobile-color-button" class="mobile-color-button" title="Editor color"
|
||||||
class="mobile-color-dot" aria-hidden="true"></span></button>
|
aria-label="Change editor color"><span class="mobile-color-dot" aria-hidden="true"></span><input
|
||||||
|
id="mobile-color-picker" type="color" aria-label="Change editor color"></label>
|
||||||
<button id="mobile-chat-button" class="mobile-chat-button" type="button" title="Chat"
|
<button id="mobile-chat-button" class="mobile-chat-button" type="button" title="Chat"
|
||||||
aria-label="Open chat">💬<span id="mobile-chat-unread" class="mobile-chat-unread" hidden></span></button>
|
aria-label="Open chat">💬<span id="mobile-chat-unread" class="mobile-chat-unread" hidden></span></button>
|
||||||
<span id="mobile-connection-status" class="mobile-connection-status" title="WebSocket status"><span
|
<span id="mobile-connection-status" class="mobile-connection-status" title="WebSocket status"><span
|
||||||
|
|||||||
Reference in New Issue
Block a user