From 1e547bc49cbc1be61b7b5bd7da2c7d0892dd3546 Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Sun, 18 Jan 2026 19:48:43 -0500
Subject: [PATCH] Add direct message composer to profile modal
---
components/profile-modal.html | 27 ++++++
js/ui/profileModalController.js | 146 ++++++++++++++++++++++++++++++++
2 files changed, 173 insertions(+)
diff --git a/components/profile-modal.html b/components/profile-modal.html
index 6a6103cb..997ef0c6 100644
--- a/components/profile-modal.html
+++ b/components/profile-modal.html
@@ -1067,6 +1067,33 @@
aria-live="polite"
aria-label="Direct message threads"
>
+
+
+
+
+
+ Sign in to send messages.
+
+
+
+
{
+ if (!(element instanceof HTMLElement) || !("disabled" in element)) {
+ return;
+ }
+
+ element.disabled = shouldDisable;
+ if (shouldDisable) {
+ element.setAttribute("aria-disabled", "true");
+ } else {
+ element.removeAttribute("aria-disabled");
+ }
+ };
+
+ applyDisabledState(input);
+ applyDisabledState(button);
+
+ if (helper instanceof HTMLElement) {
+ if (shouldDisable) {
+ helper.textContent = "Sign in to send messages.";
+ helper.classList.remove("hidden");
+ helper.removeAttribute("hidden");
+ } else {
+ helper.classList.add("hidden");
+ helper.setAttribute("hidden", "");
+ }
+ }
+ }
+
setMessagesAnnouncement(message) {
if (!(this.profileMessagesStatus instanceof HTMLElement)) {
return;
@@ -2000,6 +2044,93 @@ export class ProfileModalController {
}
}
+ describeDirectMessageSendError(code) {
+ switch (code) {
+ case "sign-event-unavailable":
+ return "Connect a Nostr signer to send messages.";
+ case "encryption-unsupported":
+ return "Your signer does not support NIP-44 or NIP-04 encryption.";
+ case "extension-permission-denied":
+ return "Please grant your Nostr extension permission to send messages.";
+ case "missing-actor-pubkey":
+ return "We couldn’t determine your public key to send this message.";
+ case "nostr-uninitialized":
+ return "Direct messages are still connecting to relays. Please try again.";
+ case "signature-failed":
+ return "We couldn’t sign the message. Please reconnect your signer and try again.";
+ case "encryption-failed":
+ return "We couldn’t encrypt the message. Please try again.";
+ case "publish-failed":
+ return "Failed to deliver this message to any relay. Please try again.";
+ case "invalid-target":
+ return "Select a valid recipient before sending.";
+ case "empty-message":
+ return "Please enter a message.";
+ default:
+ return "Unable to send message. Please try again.";
+ }
+ }
+
+ async handleSendProfileMessage() {
+ const input = this.profileMessageInput;
+ if (!(input instanceof HTMLTextAreaElement)) {
+ return;
+ }
+
+ const message = typeof input.value === "string" ? input.value.trim() : "";
+ if (!message) {
+ this.showError("Please enter a message.");
+ return;
+ }
+
+ const targetCandidate =
+ this.resolveActiveDmActor() || this.directMessagesLastActor;
+ const target =
+ typeof targetCandidate === "string" ? targetCandidate.trim() : "";
+ if (!target) {
+ this.showError("Please select a message recipient.");
+ return;
+ }
+
+ if (
+ !this.services.nostrClient ||
+ typeof this.services.nostrClient.sendDirectMessage !== "function"
+ ) {
+ this.showError("Direct message service unavailable.");
+ return;
+ }
+
+ const sendButton = this.profileMessageSendButton;
+ if (sendButton instanceof HTMLElement && "disabled" in sendButton) {
+ sendButton.disabled = true;
+ sendButton.setAttribute("aria-disabled", "true");
+ }
+
+ try {
+ const result = await this.services.nostrClient.sendDirectMessage(
+ target,
+ message,
+ );
+
+ if (result?.ok) {
+ input.value = "";
+ this.showSuccess("Message sent.");
+ void this.populateProfileMessages({ force: true, reason: "send-message" });
+ return;
+ }
+
+ const errorCode =
+ typeof result?.error === "string" ? result.error : "unknown";
+ userLogger.warn("[profileModal] Failed to send direct message:", errorCode);
+ this.showError(this.describeDirectMessageSendError(errorCode));
+ } catch (error) {
+ userLogger.error("[profileModal] Unexpected DM send failure:", error);
+ this.showError("Unable to send message. Please try again.");
+ } finally {
+ this.updateMessageComposerState();
+ }
+ }
+
clearProfileMessages({ message } = {}) {
this.directMessagesCache = [];
this.directMessagesLastActor = this.resolveActiveDmActor();
@@ -2673,6 +2804,21 @@ export class ProfileModalController {
});
}
+ if (this.profileMessageSendButton instanceof HTMLElement) {
+ this.profileMessageSendButton.addEventListener("click", () => {
+ void this.handleSendProfileMessage();
+ });
+ }
+
+ if (this.profileMessageInput instanceof HTMLElement) {
+ this.profileMessageInput.addEventListener("keydown", (event) => {
+ if (event.key === "Enter" && !event.shiftKey) {
+ event.preventDefault();
+ void this.handleSendProfileMessage();
+ }
+ });
+ }
+
if (this.walletUriInput instanceof HTMLElement) {
this.walletUriInput.addEventListener("focus", () => {
this.revealSecretInputValue(this.walletUriInput);