@@ -1833,6 +1859,7 @@ export default {
x: 0,
y: 0,
chatItem: null,
+ justOpened: false,
},
now: Date.now(),
updateTimer: null,
@@ -2872,12 +2899,30 @@ export default {
}
},
async onConversationDeleted() {
- // reload conversation
await this.initialLoad();
-
- // reload conversations
this.$emit("reload-conversations");
},
+ async onBanishHeaderClick() {
+ if (!this.selectedPeer) return;
+ if (
+ !(await DialogUtils.confirm(
+ this.$t("messages.banish_confirm") ||
+ "Are you sure you want to banish 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.selectedPeer.destination_hash,
+ });
+ GlobalEmitter.emit("block-status-changed");
+ DialogUtils.alert(this.$t("messages.user_banished"));
+ } catch (e) {
+ DialogUtils.alert(this.$t("messages.failed_banish_user"));
+ console.error(e);
+ }
+ },
onChatItemClick: function (chatItem) {
if (!chatItem.is_actions_expanded) {
chatItem.is_actions_expanded = true;
@@ -2922,28 +2967,28 @@ export default {
},
onMessageContextMenu(event, chatItem) {
this.messageContextMenu.chatItem = chatItem;
+ this.messageContextMenu.justOpened = true;
this.messageContextMenu.show = true;
- // wait for context menu to be rendered to calculate its width/height
this.$nextTick(() => {
- const menuWidth = 180; // approximate minimum width
- const menuHeight = 150; // approximate height
+ const menuWidth = 180;
+ const menuHeight = 150;
let x = event.clientX;
let y = event.clientY;
- // Adjust X if it would go off-screen on the right
if (x + menuWidth > window.innerWidth) {
x = window.innerWidth - menuWidth - 10;
}
-
- // Adjust Y if it would go off-screen at the bottom
if (y + menuHeight > window.innerHeight) {
y = window.innerHeight - menuHeight - 10;
}
this.messageContextMenu.x = x;
this.messageContextMenu.y = y;
+ setTimeout(() => {
+ this.messageContextMenu.justOpened = false;
+ }, 50);
});
},
async showRawMessage(chatItem) {
diff --git a/meshchatx/src/frontend/components/network-visualiser/NetworkVisualiser.vue b/meshchatx/src/frontend/components/network-visualiser/NetworkVisualiser.vue
index 8518c16..b978831 100644
--- a/meshchatx/src/frontend/components/network-visualiser/NetworkVisualiser.vue
+++ b/meshchatx/src/frontend/components/network-visualiser/NetworkVisualiser.vue
@@ -425,39 +425,42 @@ export default {
if (window.axios.isCancel(e)) return;
}
},
- async getPathTableBatch() {
+ async getPathTableBatch(destinationHashes = null) {
this.pathTable = [];
try {
- this.loadingStatus = "Loading Paths...";
- const firstResp = await window.axios.get(`/api/v1/path-table`, {
- params: { limit: this.pageSize, offset: 0 },
- signal: this.abortController.signal,
- });
- this.pathTable.push(...firstResp.data.path_table);
- const totalCount = firstResp.data.total_count;
-
- if (totalCount > this.pageSize) {
- const remainingOffsets = [];
- for (let offset = this.pageSize; offset < totalCount; offset += this.pageSize) {
- remainingOffsets.push(offset);
- }
-
- // Fetch remaining batches in parallel with limited concurrency to not overwhelm backend
- const concurrency = 3;
- for (let i = 0; i < remainingOffsets.length; i += concurrency) {
- if (this.abortController.signal.aborted) return;
- const chunk = remainingOffsets.slice(i, i + concurrency);
- const promises = chunk.map((offset) =>
- window.axios.get(`/api/v1/path-table`, {
- params: { limit: this.pageSize, offset: offset },
- signal: this.abortController.signal,
- })
- );
- const responses = await Promise.all(promises);
- for (const r of responses) {
- this.pathTable.push(...r.data.path_table);
+ this.loadingStatus = "Loading paths...";
+ if (destinationHashes && destinationHashes.length > 0) {
+ const resp = await window.axios.post(`/api/v1/path-table`, { destination_hashes: destinationHashes }, {
+ signal: this.abortController.signal,
+ });
+ this.pathTable.push(...resp.data.path_table);
+ } else {
+ const firstResp = await window.axios.get(`/api/v1/path-table`, {
+ params: { limit: this.pageSize, offset: 0 },
+ signal: this.abortController.signal,
+ });
+ this.pathTable.push(...firstResp.data.path_table);
+ const totalCount = firstResp.data.total_count;
+ if (totalCount > this.pageSize) {
+ const concurrency = 3;
+ for (let offset = this.pageSize; offset < totalCount; offset += this.pageSize * concurrency) {
+ if (this.abortController.signal.aborted) return;
+ const chunk = [];
+ for (let i = 0; i < concurrency && offset + i * this.pageSize < totalCount; i++) {
+ chunk.push(offset + i * this.pageSize);
+ }
+ const promises = chunk.map((o) =>
+ window.axios.get(`/api/v1/path-table`, {
+ params: { limit: this.pageSize, offset: o },
+ signal: this.abortController.signal,
+ })
+ );
+ const responses = await Promise.all(promises);
+ for (const r of responses) {
+ this.pathTable.push(...r.data.path_table);
+ }
+ this.loadingStatus = `Loading paths (${this.pathTable.length} / ${totalCount})`;
}
- this.loadingStatus = `Loading Paths (${this.pathTable.length} / ${totalCount})`;
}
}
} catch (e) {
@@ -467,41 +470,26 @@ export default {
},
async getAnnouncesBatch() {
this.announces = {};
+ const aspectsToFetch = ["lxmf.delivery", "nomadnetwork.node"];
try {
- this.loadingStatus = "Loading Announces...";
- const firstResp = await window.axios.get(`/api/v1/announces`, {
- params: { limit: this.pageSize, offset: 0 },
- signal: this.abortController.signal,
- });
-
- for (const announce of firstResp.data.announces) {
- this.announces[announce.destination_hash] = announce;
- }
- const totalCount = firstResp.data.total_count;
-
- if (totalCount > this.pageSize) {
- const remainingOffsets = [];
- for (let offset = this.pageSize; offset < totalCount; offset += this.pageSize) {
- remainingOffsets.push(offset);
- }
-
- const concurrency = 3;
- for (let i = 0; i < remainingOffsets.length; i += concurrency) {
- if (this.abortController.signal.aborted) return;
- const chunk = remainingOffsets.slice(i, i + concurrency);
- const promises = chunk.map((offset) =>
- window.axios.get(`/api/v1/announces`, {
- params: { limit: this.pageSize, offset: offset },
- signal: this.abortController.signal,
- })
- );
- const responses = await Promise.all(promises);
- for (const r of responses) {
- for (const announce of r.data.announces) {
- this.announces[announce.destination_hash] = announce;
- }
+ for (const aspect of aspectsToFetch) {
+ if (this.abortController.signal.aborted) return;
+ this.loadingStatus = `Loading ${aspect}...`;
+ let offset = 0;
+ let hasMore = true;
+ while (hasMore) {
+ const resp = await window.axios.get(`/api/v1/announces`, {
+ params: { aspect, limit: this.pageSize, offset },
+ signal: this.abortController.signal,
+ });
+ for (const announce of resp.data.announces) {
+ this.announces[announce.destination_hash] = announce;
}
- this.loadingStatus = `Loading Announces (${Object.keys(this.announces).length} / ${totalCount})`;
+ const loaded = Object.keys(this.announces).length;
+ const total = resp.data.total_count;
+ this.loadingStatus = `Loading announces (${loaded})`;
+ offset += resp.data.announces.length;
+ hasMore = resp.data.announces.length === this.pageSize && offset < total;
}
}
} catch (e) {
@@ -1100,7 +1088,9 @@ export default {
if (this.abortController.signal.aborted) return;
this.loadingStatus = "Fetching network data...";
- await Promise.all([this.getPathTableBatch(), this.getAnnouncesBatch()]);
+ await this.getAnnouncesBatch();
+ if (this.abortController.signal.aborted) return;
+ await this.getPathTableBatch(Object.keys(this.announces));
if (this.abortController.signal.aborted) return;
await this.processVisualization();
diff --git a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
index 8c754c7..e3f3c59 100644
--- a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
+++ b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkPage.vue
@@ -14,6 +14,7 @@
@node-click="onNodeClick"
@rename-favourite="onRenameFavourite"
@remove-favourite="onRemoveFavourite"
+ @add-favourite="addFavourite"
@nodes-search-changed="onNodesSearchChanged"
@load-more-nodes="loadMoreNodes"
/>
diff --git a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkSidebar.vue b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkSidebar.vue
index ebe20e6..b1d7970 100644
--- a/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkSidebar.vue
+++ b/meshchatx/src/frontend/components/nomadnetwork/NomadNetworkSidebar.vue
@@ -161,7 +161,7 @@