update UI/UX
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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");
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user