diff --git a/components/profile-modal.html b/components/profile-modal.html index cbe1f87a..317d2ebe 100644 --- a/components/profile-modal.html +++ b/components/profile-modal.html @@ -1355,50 +1355,6 @@ role="tabpanel" aria-labelledby="profileNavMessages" > -
-
-

DM relay hints

-

- Publish relay hints for direct messages as kind:10050 events. Use WSS-only relay URLs so encrypted DM delivery stays secure. -

-
-
- - -
-
- - -
- -
{ if (!popover.contains(event.target) && event.target !== settingsBtn && !settingsBtn.contains(event.target)) { diff --git a/js/ui/dm/DMRelaySettings.js b/js/ui/dm/DMRelaySettings.js new file mode 100644 index 00000000..6f2331e2 --- /dev/null +++ b/js/ui/dm/DMRelaySettings.js @@ -0,0 +1,80 @@ +export function DMRelaySettings({ document: doc } = {}) { + if (!doc) { + throw new Error("DMRelaySettings requires a document reference."); + } + + const root = doc.createElement("div"); + root.id = "profileMessagesRelayPanel"; + root.className = "dm-relay-settings card space-y-4 p-5"; + + const heading = doc.createElement("div"); + heading.className = "space-y-2"; + + const title = doc.createElement("h3"); + title.className = "text-base font-semibold text-text"; + title.textContent = "DM relay hints"; + heading.appendChild(title); + + const description = doc.createElement("p"); + description.className = "text-sm text-muted"; + description.innerHTML = + 'Publish relay hints for direct messages as kind:10050 events. Use WSS-only relay URLs so encrypted DM delivery stays secure.'; + heading.appendChild(description); + root.appendChild(heading); + + const addRow = doc.createElement("div"); + addRow.className = "flex flex-col gap-3 sm:flex-row sm:items-end"; + + const label = doc.createElement("label"); + label.className = "flex-1 text-sm text-muted-strong"; + label.setAttribute("for", "profileMessagesRelayInput"); + + const labelTitle = doc.createElement("span"); + labelTitle.className = "mb-2 block font-medium"; + labelTitle.textContent = "Add a DM relay"; + label.appendChild(labelTitle); + + const input = doc.createElement("input"); + input.id = "profileMessagesRelayInput"; + input.type = "text"; + input.inputMode = "url"; + input.autocomplete = "off"; + input.className = "input"; + input.placeholder = "wss://relay.example.com"; + label.appendChild(input); + addRow.appendChild(label); + + const addButton = doc.createElement("button"); + addButton.id = "profileMessagesRelayAdd"; + addButton.type = "button"; + addButton.className = "btn focus-ring"; + addButton.textContent = "Add relay"; + addRow.appendChild(addButton); + root.appendChild(addRow); + + const publishRow = doc.createElement("div"); + publishRow.className = "flex flex-wrap items-center gap-2"; + + const publishButton = doc.createElement("button"); + publishButton.id = "profileMessagesRelayPublish"; + publishButton.type = "button"; + publishButton.className = "btn-ghost focus-ring text-xs"; + publishButton.textContent = "Publish relay hints"; + publishRow.appendChild(publishButton); + + const status = doc.createElement("span"); + status.id = "profileMessagesRelayStatus"; + status.className = "text-xs text-muted"; + status.setAttribute("role", "status"); + status.setAttribute("aria-live", "polite"); + publishRow.appendChild(status); + root.appendChild(publishRow); + + const list = doc.createElement("ul"); + list.id = "profileMessagesRelayList"; + list.className = "grid-stack"; + list.dataset.orientation = "vertical"; + root.appendChild(list); + + return root; +} diff --git a/js/ui/profileModalController.js b/js/ui/profileModalController.js index c6fef21c..8f25b020 100644 --- a/js/ui/profileModalController.js +++ b/js/ui/profileModalController.js @@ -1646,16 +1646,7 @@ export class ProfileModalController { document.getElementById("profileMessagesPrivacyToggle") || null; this.profileMessagesPrivacyMode = document.getElementById("profileMessagesPrivacyMode") || null; - this.profileMessagesRelayList = - document.getElementById("profileMessagesRelayList") || null; - this.profileMessagesRelayInput = - document.getElementById("profileMessagesRelayInput") || null; - this.profileMessagesRelayAddButton = - document.getElementById("profileMessagesRelayAdd") || null; - this.profileMessagesRelayPublishButton = - document.getElementById("profileMessagesRelayPublish") || null; - this.profileMessagesRelayStatus = - document.getElementById("profileMessagesRelayStatus") || null; + this.cacheDmRelayElements(); this.profileMessagesUnreadDot = document.getElementById("profileMessagesUnreadDot") || null; this.dmAppShellContainer = @@ -2251,6 +2242,63 @@ export class ProfileModalController { setLinkPreviewAutoFetch(Boolean(enabled)); } + cacheDmRelayElements() { + this.profileMessagesRelayList = + document.getElementById("profileMessagesRelayList") || null; + this.profileMessagesRelayInput = + document.getElementById("profileMessagesRelayInput") || null; + this.profileMessagesRelayAddButton = + document.getElementById("profileMessagesRelayAdd") || null; + this.profileMessagesRelayPublishButton = + document.getElementById("profileMessagesRelayPublish") || null; + this.profileMessagesRelayStatus = + document.getElementById("profileMessagesRelayStatus") || null; + } + + bindDmRelayControls() { + const bindOnce = (element, eventName, handler, key) => { + if (!(element instanceof HTMLElement)) { + return; + } + const datasetKey = key || "dmRelayBound"; + if (element.dataset[datasetKey] === "true") { + return; + } + element.dataset[datasetKey] = "true"; + element.addEventListener(eventName, handler); + }; + + bindOnce( + this.profileMessagesRelayAddButton, + "click", + () => { + void this.handleAddDmRelayPreference(); + }, + "dmRelayAddBound", + ); + + bindOnce( + this.profileMessagesRelayInput, + "keydown", + (event) => { + if (event.key === "Enter") { + event.preventDefault(); + void this.handleAddDmRelayPreference(); + } + }, + "dmRelayInputBound", + ); + + bindOnce( + this.profileMessagesRelayPublishButton, + "click", + () => { + void this.handlePublishDmRelayPreferences(); + }, + "dmRelayPublishBound", + ); + } + async refreshDmRelayPreferences({ force = false } = {}) { const owner = this.resolveActiveDmRelayOwner(); if (!owner) { @@ -4545,6 +4593,11 @@ export class ProfileModalController { onToggleTypingIndicators: (enabled) => { this.handleTypingIndicatorsToggle(enabled); }, + onOpenSettings: () => { + this.cacheDmRelayElements(); + this.bindDmRelayControls(); + this.populateDmRelayPreferences(); + }, }); } catch (error) { this.dmAppShell = null; @@ -5428,26 +5481,7 @@ export class ProfileModalController { }); } - if (this.profileMessagesRelayAddButton instanceof HTMLElement) { - this.profileMessagesRelayAddButton.addEventListener("click", () => { - void this.handleAddDmRelayPreference(); - }); - } - - if (this.profileMessagesRelayInput instanceof HTMLElement) { - this.profileMessagesRelayInput.addEventListener("keydown", (event) => { - if (event.key === "Enter") { - event.preventDefault(); - void this.handleAddDmRelayPreference(); - } - }); - } - - if (this.profileMessagesRelayPublishButton instanceof HTMLElement) { - this.profileMessagesRelayPublishButton.addEventListener("click", () => { - void this.handlePublishDmRelayPreferences(); - }); - } + this.bindDmRelayControls(); if (this.profileMessageSendButton instanceof HTMLElement) { this.profileMessageSendButton.addEventListener("click", () => {