update UI/UX

This commit is contained in:
2025-11-30 20:44:15 -06:00
parent c054d16f08
commit 6dffe70e9b
3 changed files with 132 additions and 4 deletions

View File

@@ -35,6 +35,20 @@
<span>Set Custom Display Name</span> <span>Set Custom Display Name</span>
</DropDownMenuItem> </DropDownMenuItem>
<!-- block/unblock button -->
<div class="border-t">
<DropDownMenuItem v-if="!isBlocked" @click="onBlockDestination">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-5 text-red-500">
<path fill-rule="evenodd" d="M12 1.5a5.25 5.25 0 0 0-5.25 5.25v3a3 3 0 0 0-3 3v6.75a3 3 0 0 0 3 3h10.5a3 3 0 0 0 3-3V13.5a3 3 0 0 0-3-3v-3c0-2.9-2.35-5.25-5.25-5.25Zm-1.5 8.25v3a1.5 1.5 0 0 0 3 0v-3a1.5 1.5 0 0 0-3 0Z" clip-rule="evenodd" />
</svg>
<span class="text-red-500">Block User</span>
</DropDownMenuItem>
<DropDownMenuItem v-else @click="onUnblockDestination">
<MaterialDesignIcon icon-name="check-circle" class="size-5 text-green-500"/>
<span class="text-green-500">Unblock User</span>
</DropDownMenuItem>
</div>
<!-- delete message history button --> <!-- delete message history button -->
<div class="border-t"> <div class="border-t">
<DropDownMenuItem @click="onDeleteMessageHistory"> <DropDownMenuItem @click="onDeleteMessageHistory">
@@ -53,6 +67,7 @@
import DropDownMenu from "../DropDownMenu.vue"; import DropDownMenu from "../DropDownMenu.vue";
import DropDownMenuItem from "../DropDownMenuItem.vue"; import DropDownMenuItem from "../DropDownMenuItem.vue";
import IconButton from "../IconButton.vue"; import IconButton from "../IconButton.vue";
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import DialogUtils from "../../js/DialogUtils"; import DialogUtils from "../../js/DialogUtils";
export default { export default {
@@ -61,6 +76,7 @@ export default {
IconButton, IconButton,
DropDownMenuItem, DropDownMenuItem,
DropDownMenu, DropDownMenu,
MaterialDesignIcon,
}, },
props: { props: {
peer: Object, peer: Object,
@@ -68,8 +84,72 @@ export default {
emits: [ emits: [
"conversation-deleted", "conversation-deleted",
"set-custom-display-name", "set-custom-display-name",
"block-status-changed",
], ],
data() {
return {
isBlocked: false,
blockedDestinations: [],
};
},
async mounted() {
await this.loadBlockedDestinations();
},
watch: {
peer: {
handler() {
this.checkIfBlocked();
},
immediate: true,
},
},
methods: { methods: {
async loadBlockedDestinations() {
try {
const response = await window.axios.get("/api/v1/blocked-destinations");
this.blockedDestinations = response.data.blocked_destinations || [];
this.checkIfBlocked();
} catch(e) {
console.log(e);
}
},
checkIfBlocked() {
if (!this.peer) {
this.isBlocked = false;
return;
}
this.isBlocked = this.blockedDestinations.some(
b => b.destination_hash === this.peer.destination_hash
);
},
async onBlockDestination() {
if (!await DialogUtils.confirm("Are you sure you want to block this user? They will not be able to send you messages or establish links.")) {
return;
}
try {
await window.axios.post("/api/v1/blocked-destinations", {
destination_hash: this.peer.destination_hash,
});
await this.loadBlockedDestinations();
DialogUtils.alert("User blocked successfully");
this.$emit("block-status-changed");
} catch(e) {
DialogUtils.alert("Failed to block user");
console.log(e);
}
},
async onUnblockDestination() {
try {
await window.axios.delete(`/api/v1/blocked-destinations/${this.peer.destination_hash}`);
await this.loadBlockedDestinations();
DialogUtils.alert("User unblocked successfully");
this.$emit("block-status-changed");
} catch(e) {
DialogUtils.alert("Failed to unblock user");
console.log(e);
}
},
async onDeleteMessageHistory() { async onDeleteMessageHistory() {
// ask user to confirm deleting conversation history // ask user to confirm deleting conversation history

View File

@@ -69,7 +69,8 @@
v-if="selectedPeer" v-if="selectedPeer"
:peer="selectedPeer" :peer="selectedPeer"
@conversation-deleted="onConversationDeleted" @conversation-deleted="onConversationDeleted"
@set-custom-display-name="updateCustomDisplayName"/> @set-custom-display-name="updateCustomDisplayName"
@block-status-changed="loadBlockedDestinations"/>
<!-- popout button --> <!-- popout button -->
<IconButton @click="openConversationPopout" title="Pop out chat" class="text-gray-500 dark:text-zinc-400 hover:text-gray-700 dark:hover:text-zinc-200"> <IconButton @click="openConversationPopout" title="Pop out chat" class="text-gray-500 dark:text-zinc-400 hover:text-gray-700 dark:hover:text-zinc-200">
@@ -97,6 +98,8 @@
<div @click="onChatItemClick(chatItem)" class="relative rounded-2xl overflow-hidden transition-all duration-200 hover:shadow-md" :class="[ <div @click="onChatItemClick(chatItem)" class="relative rounded-2xl overflow-hidden transition-all duration-200 hover:shadow-md" :class="[
['cancelled', 'failed'].includes(chatItem.lxmf_message.state) ['cancelled', 'failed'].includes(chatItem.lxmf_message.state)
? 'bg-red-500 text-white shadow-sm' ? 'bg-red-500 text-white shadow-sm'
: chatItem.lxmf_message.is_spam
? 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-900 dark:text-yellow-100 border border-yellow-300 dark:border-yellow-700 shadow-sm'
: chatItem.is_outbound : chatItem.is_outbound
? 'bg-blue-600 text-white shadow-sm' ? 'bg-blue-600 text-white shadow-sm'
: 'bg-white dark:bg-zinc-900 text-gray-900 dark:text-zinc-100 border border-gray-200/60 dark:border-zinc-800/60 shadow-sm' : 'bg-white dark:bg-zinc-900 text-gray-900 dark:text-zinc-100 border border-gray-200/60 dark:border-zinc-800/60 shadow-sm'
@@ -104,6 +107,14 @@
<div class="w-full space-y-1 px-4 py-2.5"> <div class="w-full space-y-1 px-4 py-2.5">
<!-- spam badge -->
<div v-if="chatItem.lxmf_message.is_spam" class="flex items-center gap-1.5 text-xs font-medium mb-1" :class="chatItem.is_outbound ? 'text-yellow-200' : 'text-yellow-700 dark:text-yellow-300'">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
<span>Marked as Spam</span>
</div>
<!-- content --> <!-- content -->
<div v-if="chatItem.lxmf_message.content" class="text-sm leading-relaxed whitespace-pre-wrap break-words" style="font-family:inherit;">{{ chatItem.lxmf_message.content }}</div> <div v-if="chatItem.lxmf_message.content" class="text-sm leading-relaxed whitespace-pre-wrap break-words" style="font-family:inherit;">{{ chatItem.lxmf_message.content }}</div>
@@ -271,6 +282,14 @@
<div class="w-full border-t border-gray-200/60 dark:border-zinc-800/60 bg-white/80 dark:bg-zinc-900/50 backdrop-blur-sm px-3 sm:px-4 py-2.5"> <div class="w-full border-t border-gray-200/60 dark:border-zinc-800/60 bg-white/80 dark:bg-zinc-900/50 backdrop-blur-sm px-3 sm:px-4 py-2.5">
<div class="w-full"> <div class="w-full">
<!-- blocked user notification -->
<div v-if="isSelectedPeerBlocked" class="mb-3 p-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
<span class="text-sm text-yellow-800 dark:text-yellow-200">You have blocked this user. They cannot send you messages or establish links.</span>
</div>
<!-- message composer --> <!-- message composer -->
<div> <div>
@@ -475,6 +494,8 @@ export default {
lxmfMessageAudioAttachmentCache: {}, lxmfMessageAudioAttachmentCache: {},
expandedMessageInfo: null, expandedMessageInfo: null,
imageModalUrl: null, imageModalUrl: null,
isSelectedPeerBlocked: false,
blockedDestinations: [],
lxmfAudioModeToCodec2ModeMap: { lxmfAudioModeToCodec2ModeMap: {
// https://github.com/markqvist/LXMF/blob/master/LXMF/LXMF.py#L21 // https://github.com/markqvist/LXMF/blob/master/LXMF/LXMF.py#L21
0x01: "450PWB", // AM_CODEC2_450PWB 0x01: "450PWB", // AM_CODEC2_450PWB
@@ -495,6 +516,14 @@ export default {
WebSocketConnection.off("message", this.onWebsocketMessage); WebSocketConnection.off("message", this.onWebsocketMessage);
GlobalEmitter.off("compose-new-message", this.onComposeNewMessageEvent); GlobalEmitter.off("compose-new-message", this.onComposeNewMessageEvent);
}, },
watch: {
selectedPeer: {
handler() {
this.checkIfSelectedPeerBlocked();
},
immediate: true,
},
},
mounted() { mounted() {
// listen for websocket messages // listen for websocket messages
@@ -503,8 +532,29 @@ export default {
// listen for compose new message event // listen for compose new message event
GlobalEmitter.on("compose-new-message", this.onComposeNewMessageEvent); GlobalEmitter.on("compose-new-message", this.onComposeNewMessageEvent);
// load blocked destinations
this.loadBlockedDestinations();
}, },
methods: { methods: {
async loadBlockedDestinations() {
try {
const response = await window.axios.get("/api/v1/blocked-destinations");
this.blockedDestinations = response.data.blocked_destinations || [];
this.checkIfSelectedPeerBlocked();
} catch(e) {
console.log(e);
}
},
checkIfSelectedPeerBlocked() {
if (!this.selectedPeer) {
this.isSelectedPeerBlocked = false;
return;
}
this.isSelectedPeerBlocked = this.blockedDestinations.some(
b => b.destination_hash === this.selectedPeer.destination_hash
);
},
close() { close() {
this.$emit("close"); this.$emit("close");
}, },

View File

@@ -59,9 +59,7 @@
</div> </div>
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<div v-if="conversation.has_attachments" class="text-gray-500 dark:text-gray-300"> <div v-if="conversation.has_attachments" class="text-gray-500 dark:text-gray-300">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4"> <MaterialDesignIcon icon-name="paperclip" class="w-4 h-4"/>
<path d="M15.26 5.01a2.25 2.25 0 0 1 3.182 3.182L11.44 15.195a3 3 0 0 1-4.243-4.243l6.364-6.364a.75.75 0 0 1 1.06 1.06l-6.364 6.364a1.5 1.5 0 1 0 2.121 2.121l6.999-6.998a.75.75 0 0 0-1.06-1.06l-6.364 6.363a.75.75 0 1 1-1.06-1.06Z"/>
</svg>
</div> </div>
<div v-if="conversation.is_unread" class="my-auto ml-1"> <div v-if="conversation.is_unread" class="my-auto ml-1">
<div class="bg-blue-500 dark:bg-blue-400 rounded-full p-1"></div> <div class="bg-blue-500 dark:bg-blue-400 rounded-full p-1"></div>