@@ -1327,8 +1455,15 @@ export default {
},
searchDebounceTimeout: null,
isVoicemailSettingsExpanded: false,
+ selectedSuggestionIndex: -1,
+ isCallInputFocused: false,
};
},
+ watch: {
+ destinationHash() {
+ this.selectedSuggestionIndex = -1;
+ },
+ },
computed: {
isMicMuted() {
return this.activeCall?.is_mic_muted ?? false;
@@ -1350,6 +1485,53 @@ export default {
const duration = Math.floor(Date.now() / 1000 - this.lastCall.call_start_time);
return Utils.formatMinutesSeconds(duration);
},
+ newCallSuggestions() {
+ if (!this.isCallInputFocused) return [];
+
+ const search = this.destinationHash.toLowerCase().trim();
+ const suggestions = [];
+ const seenHashes = new Set();
+
+ // 1. Check contacts
+ this.contacts.forEach((c) => {
+ if (!seenHashes.has(c.remote_identity_hash)) {
+ if (
+ !search ||
+ c.name.toLowerCase().includes(search) ||
+ c.remote_identity_hash.toLowerCase().includes(search)
+ ) {
+ suggestions.push({
+ name: c.name,
+ hash: c.remote_identity_hash,
+ type: "contact",
+ icon: "account",
+ });
+ seenHashes.add(c.remote_identity_hash);
+ }
+ }
+ });
+
+ // 2. Check call history
+ this.callHistory.forEach((h) => {
+ if (!seenHashes.has(h.remote_identity_hash)) {
+ if (
+ !search ||
+ (h.remote_identity_name && h.remote_identity_name.toLowerCase().includes(search)) ||
+ h.remote_identity_hash.toLowerCase().includes(search)
+ ) {
+ suggestions.push({
+ name: h.remote_identity_name || h.remote_identity_hash.substring(0, 8),
+ hash: h.remote_identity_hash,
+ type: "history",
+ icon: "history",
+ });
+ seenHashes.add(h.remote_identity_hash);
+ }
+ }
+ });
+
+ return suggestions.slice(0, 8);
+ },
},
mounted() {
this.getConfig();
@@ -1467,8 +1649,14 @@ export default {
} else if (this.activeCall != null) {
// if a new call starts, clear ended state
this.isCallEnded = false;
+ this.wasDeclined = false;
this.lastCall = null;
if (this.endedTimeout) clearTimeout(this.endedTimeout);
+ } else if (!this.endedTimeout) {
+ // If no call and no ended state timeout active, ensure everything is reset
+ this.isCallEnded = false;
+ this.wasDeclined = false;
+ this.lastCall = null;
}
} catch (e) {
console.log(e);
@@ -1804,6 +1992,15 @@ export default {
ToastUtils.error("Failed to delete contact");
}
},
+ async copyHash(hash) {
+ try {
+ await navigator.clipboard.writeText(hash);
+ ToastUtils.success("Hash copied to clipboard");
+ } catch (e) {
+ console.error(e);
+ ToastUtils.error("Failed to copy hash");
+ }
+ },
async generateGreeting() {
this.isGeneratingGreeting = true;
try {
@@ -1947,12 +2144,60 @@ export default {
ToastUtils.error("Enter an identity to call");
return;
}
+
+ let hashToCall = identityHash.trim();
+
+ // Try to resolve name from contacts
+ const contact = this.contacts.find((c) => c.name.toLowerCase() === hashToCall.toLowerCase());
+ if (contact) {
+ hashToCall = contact.remote_identity_hash;
+ }
+
try {
- await window.axios.get(`/api/v1/telephone/call/${identityHash}`);
+ await window.axios.get(`/api/v1/telephone/call/${hashToCall}`);
} catch (e) {
ToastUtils.error(e.response?.data?.message || "Failed to initiate call");
}
},
+ handleCallInputUp() {
+ if (this.newCallSuggestions.length > 0) {
+ if (this.selectedSuggestionIndex > 0) {
+ this.selectedSuggestionIndex--;
+ } else {
+ this.selectedSuggestionIndex = this.newCallSuggestions.length - 1;
+ }
+ }
+ },
+ handleCallInputDown() {
+ if (this.newCallSuggestions.length > 0) {
+ if (this.selectedSuggestionIndex < this.newCallSuggestions.length - 1) {
+ this.selectedSuggestionIndex++;
+ } else {
+ this.selectedSuggestionIndex = 0;
+ }
+ }
+ },
+ handleCallInputEnter() {
+ if (this.selectedSuggestionIndex >= 0 && this.selectedSuggestionIndex < this.newCallSuggestions.length) {
+ const suggestion = this.newCallSuggestions[this.selectedSuggestionIndex];
+ this.selectSuggestion(suggestion);
+ } else {
+ this.call(this.destinationHash);
+ }
+ },
+ selectSuggestion(suggestion) {
+ this.destinationHash = suggestion.hash;
+ this.isCallInputFocused = false;
+ this.selectedSuggestionIndex = -1;
+ this.call(this.destinationHash);
+ },
+ onCallInputBlur() {
+ // Delay blur to allow mousedown on suggestions
+ setTimeout(() => {
+ this.isCallInputFocused = false;
+ this.selectedSuggestionIndex = -1;
+ }, 200);
+ },
async answerCall() {
try {
await window.axios.get("/api/v1/telephone/answer");
diff --git a/meshchatx/src/frontend/components/messages/ConversationViewer.vue b/meshchatx/src/frontend/components/messages/ConversationViewer.vue
index 9f059d9..cc2b34e 100644
--- a/meshchatx/src/frontend/components/messages/ConversationViewer.vue
+++ b/meshchatx/src/frontend/components/messages/ConversationViewer.vue
@@ -56,7 +56,13 @@
-
<{{ selectedPeer.destination_hash }}>
+
+ {{ formatDestinationHash(selectedPeer.destination_hash) }}
+
@@ -757,7 +763,7 @@
- {{ $t("messages.location") }}
+ {{ $t("messages.location") }}
+
+
+
+
+
+
+
+
+ {{ $t("tools.micron_editor.title") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/meshchatx/src/frontend/components/tools/ToolsPage.vue b/meshchatx/src/frontend/components/tools/ToolsPage.vue
index 195261b..01daa92 100644
--- a/meshchatx/src/frontend/components/tools/ToolsPage.vue
+++ b/meshchatx/src/frontend/components/tools/ToolsPage.vue
@@ -98,6 +98,19 @@
+
+
+
+
+
+
{{ $t("tools.micron_editor.title") }}
+
+ {{ $t("tools.micron_editor.description") }}
+
+
+
+
+