Improve context menu

This commit is contained in:
2025-08-23 20:32:22 +03:00
Unverified
parent 320c6bbe95
commit b939067114
6 changed files with 135 additions and 111 deletions
+1 -1
View File
@@ -321,7 +321,7 @@
</div>
</mdui-dialog>
<div id="message-context-menu">
<div id="message-context-menu" class="context-menu">
<div class="context-menu-item" data-action="reply">
<span class="material-symbols">reply</span>
Reply
+2 -2
View File
@@ -45,12 +45,12 @@ export function addMessage(message: Message, isAuthor: boolean): void {
profileImg.src = message.profile_picture || defaultAvatar;
profileImg.alt = message.username;
let errorLock = false
let errorLock = false;
profileImg.addEventListener("error", () => {
if (!errorLock) {
profileImg.src = defaultAvatar;
errorLock = true
errorLock = true;
}
});
-55
View File
@@ -319,61 +319,6 @@
}
}
// Message Context Menu
#message-context-menu {
position: fixed;
background-color: $color-dark-surface-container;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
padding: 0.5rem 0;
z-index: 1000;
display: none;
min-width: 150px;
max-width: 200px;
white-space: nowrap;
.context-menu-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
cursor: pointer;
color: $color-dark-on-surface;
transition: background-color 0.2s ease;
font-size: 0.9rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.material-symbols {
font-size: 1.1rem;
flex-shrink: 0;
}
}
}
// Dialog content styles
.dialog-content {
padding: 1.5rem;
h3 {
margin: 0 0 1rem 0;
color: $color-dark-on-surface;
font-size: 1.2rem;
}
.dialog-actions {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
margin-top: 1.5rem;
}
}
// Reply preview styles
.reply-preview {
background-color: $color-dark-surface;
+84
View File
@@ -28,3 +28,87 @@
button, input {
font: inherit;
}
.context-menu {
position: fixed;
background-color: $color-dark-surface-container;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
padding: 0.5rem 0;
z-index: 1000;
display: none;
min-width: 150px;
max-width: 200px;
white-space: nowrap;
user-select: none;
.context-menu-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
cursor: pointer;
color: $color-dark-on-surface;
transition: background-color 0.2s ease;
font-size: 0.9rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.material-symbols {
font-size: 1.1rem;
flex-shrink: 0;
}
}
&.pos-top-left {
transform-origin: top right;
}
&.pos-top-right {
transform-origin: top left;
}
&.pos-bottom-left {
transform-origin: bottom right;
}
&.pos-bottom-right {
transform-origin: bottom left;
}
&.open {
animation: context-menu-open 0.25s ease;
}
@keyframes context-menu-open {
0% {
opacity: 0;
transform: scale(0.5);
}
100% {
opacity: 1;
transform: scale(1);
}
}
}
// Dialog content styles
.dialog-content {
padding: 1.5rem;
h3 {
margin: 0 0 1rem 0;
color: $color-dark-on-surface;
font-size: 1.2rem;
}
.dialog-actions {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
margin-top: 1.5rem;
}
}
+34 -42
View File
@@ -9,11 +9,14 @@ import { currentUser, authToken } from "./auth";
import { websocket } from "./websocket";
import type { Message, WebSocketMessage } from "./types";
import { showSuccess, showError } from "./utils/notification";
import { delay } from "./utils/utils";
import type { Dialog } from "mdui/components/dialog";
import type { TextField } from "mdui/components/text-field";
let menu = document.getElementById("message-context-menu")!;
let editDialog = document.getElementById("edit-message-dialog");
let replyDialog = document.getElementById("reply-message-dialog");
let editDialog = document.getElementById("edit-message-dialog") as Dialog;
let replyDialog = document.getElementById("reply-message-dialog") as Dialog;
let currentMessage: Message | null = null;
function init() {
@@ -73,8 +76,6 @@ function bindEvents(): void {
* @param {number} y - Y coordinate
*/
export function show(message: Message, x: number, y: number): void {
if (!menu) return;
currentMessage = message;
// Only show edit and delete for own messages
@@ -89,25 +90,27 @@ export function show(message: Message, x: number, y: number): void {
deleteItem.style.display = 'none';
}
// Calculate menu position
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Position the menu properly
menu.style.display = 'block';
// Default menu size estimates
const menuWidth = 150;
const menuHeight = 120;
let menuWidth = menu.offsetWidth;
let menuHeight = menu.offsetHeight;
let adjustedX = x;
let adjustedY = y;
let vertical = "top";
let horizontal = "right";
// Adjust horizontal position if menu would go off-screen
if (x + menuWidth > viewportWidth) {
if (x + menuWidth > window.innerWidth) {
adjustedX = x - menuWidth;
horizontal = "left";
}
// Adjust vertical position if menu would go off-screen
if (y + menuHeight > viewportHeight) {
if (y + menuHeight > window.innerHeight) {
adjustedY = y - menuHeight;
vertical = "bottom";
}
// Ensure menu doesn't go off the left or top edges
@@ -116,16 +119,19 @@ export function show(message: Message, x: number, y: number): void {
menu.style.left = `${adjustedX}px`;
menu.style.top = `${adjustedY}px`;
menu.style.display = 'block';
menu.classList.add(`pos-${vertical}-${horizontal}`, "open");
}
/**
* Hides the context menu
*/
export function hide(): void {
if (menu) {
menu.style.display = 'none';
menu.classList.forEach((name) => {
if (name.match(/pos-\w+-\w+/)) {
menu.classList.remove(name);
}
})
currentMessage = null;
}
@@ -156,21 +162,16 @@ function handleAction(action: string, message: Message): void {
* @param {Message} message - The message to edit
* @private
*/
function showEditDialog(message: Message): void {
if (!editDialog) return;
const textField = editDialog.querySelector('#edit-message-input') as any;
if (textField) {
async function showEditDialog(message: Message): Promise<void> {
const textField = editDialog.querySelector('#edit-message-input') as TextField;
textField.value = message.content;
}
currentMessage = message;
(editDialog as any).open = true;
editDialog.open = true;
// Focus the text field
setTimeout(() => {
await delay(100);
textField?.focus();
}, 100);
}
/**
@@ -178,9 +179,7 @@ function showEditDialog(message: Message): void {
* @private
*/
function hideEditDialog(): void {
if (editDialog) {
(editDialog as any).open = false;
}
editDialog.open = false;
}
/**
@@ -188,9 +187,9 @@ function hideEditDialog(): void {
* @private
*/
function saveEdit(): void {
if (!currentMessage || !editDialog) return;
if (!currentMessage) return;
const textField = editDialog.querySelector('#edit-message-input') as any;
const textField = editDialog.querySelector('#edit-message-input') as TextField;
const newContent = textField?.value?.trim() || '';
if (!newContent) {
@@ -231,26 +230,21 @@ function saveEdit(): void {
* @param {Message} message - The message to reply to
* @private
*/
function showReplyDialog(message: Message): void {
if (!replyDialog) return;
async function showReplyDialog(message: Message): Promise<void> {
const preview = replyDialog.querySelector('#reply-preview') as HTMLElement;
if (preview) {
preview.innerHTML = `
<div class="reply-preview-content">
<strong>${message.username}</strong>: ${message.content}
</div>
`;
}
currentMessage = message;
(replyDialog as any).open = true;
replyDialog.open = true;
// Focus the text field
setTimeout(() => {
const textField = replyDialog?.querySelector('#reply-message-input') as any;
await delay(100);
const textField = replyDialog?.querySelector('#reply-message-input') as TextField;
textField?.focus();
}, 100);
}
/**
@@ -258,9 +252,7 @@ function showReplyDialog(message: Message): void {
* @private
*/
function hideReplyDialog(): void {
if (replyDialog) {
(replyDialog as any).open = false;
}
replyDialog.open = false;
}
/**
@@ -268,9 +260,9 @@ function hideReplyDialog(): void {
* @private
*/
function sendReply(): void {
if (!currentMessage || !replyDialog) return;
if (!currentMessage) return;
const textField = replyDialog.querySelector('#reply-message-input') as any;
const textField = replyDialog.querySelector('#reply-message-input') as TextField;
const content = textField?.value?.trim() || '';
if (!content) {
+3
View File
@@ -19,6 +19,9 @@ import 'mdui/components/text-field';
import 'mdui/components/button-icon';
import 'mdui/components/top-app-bar';
import 'mdui/components/top-app-bar-title';
import 'mdui/components/dropdown.js';
import 'mdui/components/menu.js';
import 'mdui/components/menu-item.js';
import { setColorScheme } from 'mdui/functions/setColorScheme.js';