mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 12:52:26 +00:00
Merge pull request #336 from PR0M3TH3AN/codex/refactor-profilemodalcontroller-event-handling
Refactor profile modal event handling to use controller
This commit is contained in:
2 files changed
+448
-470
No files matched your search
@@ -1531,18 +1531,11 @@ class Application {
|
||||
*/
|
||||
async initProfileModal() {
|
||||
try {
|
||||
console.log("Starting profile modal initialization...");
|
||||
const resp = await fetch("components/profile-modal.html");
|
||||
if (!resp.ok) {
|
||||
// If you don't have a profile modal, comment this entire method out.
|
||||
throw new Error(`HTTP error! status: ${resp.status}`);
|
||||
}
|
||||
const html = await resp.text();
|
||||
|
||||
const modalContainer = document.getElementById("modalContainer");
|
||||
if (!modalContainer) {
|
||||
throw new Error("Modal container element not found!");
|
||||
}
|
||||
|
||||
if (!this.profileModalController) {
|
||||
this.profileModalController = new ProfileModalController({
|
||||
modalContainer,
|
||||
@@ -1556,376 +1549,99 @@ class Application {
|
||||
state: this.getProfileModalStateBridge(),
|
||||
});
|
||||
}
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.innerHTML = html;
|
||||
removeTrackingScripts(wrapper);
|
||||
modalContainer.appendChild(wrapper);
|
||||
|
||||
// Now references
|
||||
this.profileModal = document.getElementById("profileModal") || null;
|
||||
this.closeProfileModal =
|
||||
document.getElementById("closeProfileModal") || null;
|
||||
this.profileLogoutBtn =
|
||||
document.getElementById("profileLogoutBtn") || null;
|
||||
this.profileModalAvatar =
|
||||
document.getElementById("profileModalAvatar") || null;
|
||||
this.profileModalName =
|
||||
document.getElementById("profileModalName") || null;
|
||||
this.profileModalNpub =
|
||||
document.getElementById("profileModalNpub") || null;
|
||||
this.profileChannelLink =
|
||||
document.getElementById("profileChannelLink") || null;
|
||||
this.profileSwitcherList =
|
||||
document.getElementById("profileSwitcherList") || null;
|
||||
this.profileAddAccountBtn =
|
||||
document.getElementById("profileAddAccountBtn") || null;
|
||||
const topLevelProfileAvatar =
|
||||
document.getElementById("profileAvatar") || null;
|
||||
if (topLevelProfileAvatar) {
|
||||
this.profileAvatar = topLevelProfileAvatar;
|
||||
}
|
||||
this.profileNavButtons.account =
|
||||
document.getElementById("profileNavAccount") || null;
|
||||
this.profileNavButtons.relays =
|
||||
document.getElementById("profileNavRelays") || null;
|
||||
this.profileNavButtons.wallet =
|
||||
document.getElementById("profileNavWallet") || null;
|
||||
this.profileNavButtons.blocked =
|
||||
document.getElementById("profileNavBlocked") || null;
|
||||
this.profileNavButtons.history =
|
||||
document.getElementById("profileNavHistory") || null;
|
||||
this.profileNavButtons.admin =
|
||||
document.getElementById("profileNavAdmin") || null;
|
||||
this.profilePaneElements.account =
|
||||
document.getElementById("profilePaneAccount") || null;
|
||||
this.profilePaneElements.relays =
|
||||
document.getElementById("profilePaneRelays") || null;
|
||||
this.profilePaneElements.wallet =
|
||||
document.getElementById("profilePaneWallet") || null;
|
||||
this.profilePaneElements.blocked =
|
||||
document.getElementById("profilePaneBlocked") || null;
|
||||
this.profilePaneElements.history =
|
||||
document.getElementById("profilePaneHistory") || null;
|
||||
this.profilePaneElements.admin =
|
||||
document.getElementById("profilePaneAdmin") || null;
|
||||
this.profileRelayList = document.getElementById("relayList") || null;
|
||||
this.profileBlockedList = document.getElementById("blockedList") || null;
|
||||
this.profileBlockedEmpty =
|
||||
document.getElementById("blockedEmpty") || null;
|
||||
this.profileBlockedInput =
|
||||
document.getElementById("blockedInput") || null;
|
||||
this.profileAddBlockedBtn =
|
||||
document.getElementById("addBlockedBtn") || null;
|
||||
this.profileRelayInput = document.getElementById("relayInput") || null;
|
||||
this.profileAddRelayBtn = document.getElementById("addRelayBtn") || null;
|
||||
this.profileRestoreRelaysBtn =
|
||||
document.getElementById("restoreRelaysBtn") || null;
|
||||
this.profileWalletUriInput =
|
||||
document.getElementById("profileWalletUri") || null;
|
||||
this.profileWalletDefaultZapInput =
|
||||
document.getElementById("profileWalletDefaultZap") || null;
|
||||
this.profileWalletSaveButton =
|
||||
document.getElementById("profileWalletSave") || null;
|
||||
this.profileWalletTestButton =
|
||||
document.getElementById("profileWalletTest") || null;
|
||||
this.profileWalletDisconnectButton =
|
||||
document.getElementById("profileWalletDisconnect") || null;
|
||||
this.profileWalletStatusText =
|
||||
document.getElementById("profileWalletStatus") || null;
|
||||
if (!this.profileHistoryRenderer) {
|
||||
this.profileHistoryRenderer = createWatchHistoryRenderer({
|
||||
viewSelector: "#profilePaneHistory",
|
||||
gridSelector: "#profileHistoryGrid",
|
||||
loadingSelector: "#profileHistoryLoading",
|
||||
statusSelector: "#profileHistoryStatus",
|
||||
emptySelector: "#profileHistoryEmpty",
|
||||
sentinelSelector: "#profileHistorySentinel",
|
||||
scrollContainerSelector: "#profileHistoryScroll",
|
||||
errorBannerSelector: "#profileHistoryError",
|
||||
clearButtonSelector: "#profileHistoryClear",
|
||||
republishButtonSelector: "#profileHistoryRepublish",
|
||||
featureBannerSelector: "#profileHistoryFeatureBanner",
|
||||
toastRegionSelector: "#profileHistoryToastRegion",
|
||||
sessionWarningSelector: "#profileHistorySessionWarning",
|
||||
metadataToggleSelector: "#profileHistoryMetadataToggle",
|
||||
metadataThumbSelector: "#profileHistoryMetadataThumb",
|
||||
metadataLabelSelector: "#profileHistoryMetadataLabel",
|
||||
metadataDescriptionSelector: "#profileHistoryMetadataDescription",
|
||||
emptyCopy: "You haven’t watched any videos yet.",
|
||||
remove: (payload) => this.handleWatchHistoryRemoval(payload),
|
||||
getActor: async () => {
|
||||
if (this.pubkey) {
|
||||
return this.pubkey;
|
||||
}
|
||||
if (
|
||||
typeof nostrClient?.sessionActor?.pubkey === "string" &&
|
||||
nostrClient.sessionActor.pubkey
|
||||
) {
|
||||
return nostrClient.sessionActor.pubkey;
|
||||
}
|
||||
return this.pubkey || undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
this.adminModeratorsSection =
|
||||
document.getElementById("adminModeratorsSection") || null;
|
||||
this.adminModeratorsEmpty =
|
||||
document.getElementById("adminModeratorsEmpty") || null;
|
||||
this.adminModeratorList =
|
||||
document.getElementById("adminModeratorList") || null;
|
||||
this.adminModeratorInput =
|
||||
document.getElementById("adminModeratorInput") || null;
|
||||
this.adminAddModeratorBtn =
|
||||
document.getElementById("adminAddModeratorBtn") || null;
|
||||
this.adminWhitelistSection =
|
||||
document.getElementById("adminWhitelistSection") || null;
|
||||
this.adminWhitelistEmpty =
|
||||
document.getElementById("adminWhitelistEmpty") || null;
|
||||
this.adminWhitelistList =
|
||||
document.getElementById("adminWhitelistList") || null;
|
||||
this.adminWhitelistInput =
|
||||
document.getElementById("adminWhitelistInput") || null;
|
||||
this.adminAddWhitelistBtn =
|
||||
document.getElementById("adminAddWhitelistBtn") || null;
|
||||
this.adminBlacklistSection =
|
||||
document.getElementById("adminBlacklistSection") || null;
|
||||
this.adminBlacklistEmpty =
|
||||
document.getElementById("adminBlacklistEmpty") || null;
|
||||
this.adminBlacklistList =
|
||||
document.getElementById("adminBlacklistList") || null;
|
||||
this.adminBlacklistInput =
|
||||
document.getElementById("adminBlacklistInput") || null;
|
||||
this.adminAddBlacklistBtn =
|
||||
document.getElementById("adminAddBlacklistBtn") || null;
|
||||
|
||||
// Wire up
|
||||
if (this.closeProfileModal && !this.closeProfileModal.dataset.bound) {
|
||||
this.closeProfileModal.dataset.bound = "true";
|
||||
this.closeProfileModal.addEventListener("click", () => {
|
||||
this.hideProfileModal();
|
||||
});
|
||||
}
|
||||
if (this.profileLogoutBtn && !this.profileLogoutBtn.dataset.bound) {
|
||||
this.profileLogoutBtn.dataset.bound = "true";
|
||||
this.profileLogoutBtn.addEventListener("click", async () => {
|
||||
try {
|
||||
await this.authService.logout();
|
||||
} catch (error) {
|
||||
console.error("Logout failed:", error);
|
||||
this.showError("Failed to logout. Please try again.");
|
||||
}
|
||||
this.hideProfileModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.profileChannelLink && !this.profileChannelLink.dataset.bound) {
|
||||
this.profileChannelLink.dataset.bound = "true";
|
||||
this.profileChannelLink.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
const targetNpub = this.profileChannelLink?.dataset?.targetNpub;
|
||||
if (!targetNpub) {
|
||||
return;
|
||||
}
|
||||
this.hideProfileModal();
|
||||
window.location.hash = `#view=channel-profile&npub=${targetNpub}`;
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
this.profileAddAccountBtn &&
|
||||
this.profileAddAccountBtn.dataset.bound !== "true"
|
||||
) {
|
||||
this.profileAddAccountBtn.dataset.bound = "true";
|
||||
this.profileAddAccountBtn.addEventListener("click", () => {
|
||||
this.handleAddProfile();
|
||||
});
|
||||
}
|
||||
|
||||
Object.entries(this.profileNavButtons).forEach(([name, button]) => {
|
||||
if (!button || button.dataset.navBound === "true") {
|
||||
return;
|
||||
const alreadyLoaded =
|
||||
this.profileModalController.profileModal instanceof HTMLElement;
|
||||
if (!alreadyLoaded) {
|
||||
const loaded = await this.profileModalController.load();
|
||||
if (!loaded) {
|
||||
return false;
|
||||
}
|
||||
button.dataset.navBound = "true";
|
||||
button.addEventListener("click", () => {
|
||||
this.selectProfilePane(name);
|
||||
});
|
||||
} else {
|
||||
this.profileModalController.cacheDomReferences();
|
||||
}
|
||||
|
||||
this.profileModal = this.profileModalController.profileModal;
|
||||
this.closeProfileModal = this.profileModalController.closeButton;
|
||||
this.profileLogoutBtn = this.profileModalController.logoutButton;
|
||||
this.profileChannelLink = this.profileModalController.channelLink;
|
||||
this.profileModalAvatar = this.profileModalController.profileModalAvatar;
|
||||
this.profileModalName = this.profileModalController.profileModalName;
|
||||
this.profileModalNpub = this.profileModalController.profileModalNpub;
|
||||
this.profileSwitcherList = this.profileModalController.profileSwitcherList;
|
||||
this.profileAddAccountBtn = this.profileModalController.addAccountButton;
|
||||
this.profileAvatar = this.profileModalController.profileAvatar;
|
||||
|
||||
Object.entries(this.profileNavButtons).forEach(([key]) => {
|
||||
this.profileNavButtons[key] =
|
||||
this.profileModalController.navButtons[key] || null;
|
||||
});
|
||||
|
||||
if (this.profileAddRelayBtn && !this.profileAddRelayBtn.dataset.bound) {
|
||||
this.profileAddRelayBtn.dataset.bound = "true";
|
||||
this.profileAddRelayBtn.addEventListener("click", () => {
|
||||
this.handleAddRelay();
|
||||
});
|
||||
}
|
||||
if (
|
||||
this.profileRestoreRelaysBtn &&
|
||||
this.profileRestoreRelaysBtn.dataset.bound !== "true"
|
||||
) {
|
||||
this.profileRestoreRelaysBtn.dataset.bound = "true";
|
||||
this.profileRestoreRelaysBtn.addEventListener("click", () => {
|
||||
this.handleRestoreRelays();
|
||||
});
|
||||
}
|
||||
if (
|
||||
this.profileRelayInput &&
|
||||
this.profileRelayInput.dataset.bound !== "true"
|
||||
) {
|
||||
this.profileRelayInput.dataset.bound = "true";
|
||||
this.profileRelayInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
this.handleAddRelay();
|
||||
}
|
||||
});
|
||||
}
|
||||
Object.entries(this.profilePaneElements).forEach(([key]) => {
|
||||
this.profilePaneElements[key] =
|
||||
this.profileModalController.panes[key] || null;
|
||||
});
|
||||
|
||||
if (
|
||||
this.profileAddBlockedBtn &&
|
||||
this.profileAddBlockedBtn.dataset.bound !== "true"
|
||||
) {
|
||||
this.profileAddBlockedBtn.dataset.bound = "true";
|
||||
this.profileAddBlockedBtn.addEventListener("click", () => {
|
||||
this.handleAddBlockedCreator();
|
||||
});
|
||||
}
|
||||
if (
|
||||
this.profileBlockedInput &&
|
||||
this.profileBlockedInput.dataset.bound !== "true"
|
||||
) {
|
||||
this.profileBlockedInput.dataset.bound = "true";
|
||||
this.profileBlockedInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
this.handleAddBlockedCreator();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.profileRelayList = this.profileModalController.profileRelayList;
|
||||
this.profileRelayInput = this.profileModalController.profileRelayInput;
|
||||
this.profileAddRelayBtn = this.profileModalController.profileAddRelayBtn;
|
||||
this.profileRestoreRelaysBtn =
|
||||
this.profileModalController.profileRestoreRelaysBtn;
|
||||
|
||||
if (
|
||||
this.profileWalletUriInput &&
|
||||
this.profileWalletUriInput.dataset.bound !== "true"
|
||||
) {
|
||||
this.profileWalletUriInput.dataset.bound = "true";
|
||||
this.profileWalletUriInput.addEventListener("input", () => {
|
||||
this.applyWalletControlState();
|
||||
});
|
||||
}
|
||||
if (
|
||||
this.profileWalletDefaultZapInput &&
|
||||
this.profileWalletDefaultZapInput.dataset.bound !== "true"
|
||||
) {
|
||||
this.profileWalletDefaultZapInput.dataset.bound = "true";
|
||||
this.profileWalletDefaultZapInput.addEventListener("input", () => {
|
||||
this.applyWalletControlState();
|
||||
});
|
||||
}
|
||||
if (
|
||||
this.profileWalletSaveButton &&
|
||||
this.profileWalletSaveButton.dataset.bound !== "true"
|
||||
) {
|
||||
this.profileWalletSaveButton.dataset.bound = "true";
|
||||
this.profileWalletSaveButton.addEventListener("click", () => {
|
||||
this.handleWalletSave();
|
||||
});
|
||||
}
|
||||
if (
|
||||
this.profileWalletTestButton &&
|
||||
this.profileWalletTestButton.dataset.bound !== "true"
|
||||
) {
|
||||
this.profileWalletTestButton.dataset.bound = "true";
|
||||
this.profileWalletTestButton.addEventListener("click", () => {
|
||||
this.handleWalletTest();
|
||||
});
|
||||
}
|
||||
if (
|
||||
this.profileWalletDisconnectButton &&
|
||||
this.profileWalletDisconnectButton.dataset.bound !== "true"
|
||||
) {
|
||||
this.profileWalletDisconnectButton.dataset.bound = "true";
|
||||
this.profileWalletDisconnectButton.addEventListener("click", () => {
|
||||
this.handleWalletDisconnect();
|
||||
});
|
||||
}
|
||||
this.profileBlockedList = this.profileModalController.profileBlockedList;
|
||||
this.profileBlockedEmpty = this.profileModalController.profileBlockedEmpty;
|
||||
this.profileBlockedInput = this.profileModalController.profileBlockedInput;
|
||||
this.profileAddBlockedBtn = this.profileModalController.profileAddBlockedBtn;
|
||||
|
||||
if (
|
||||
this.adminAddModeratorBtn &&
|
||||
this.adminAddModeratorBtn.dataset.bound !== "true"
|
||||
) {
|
||||
this.adminAddModeratorBtn.dataset.bound = "true";
|
||||
this.adminAddModeratorBtn.addEventListener("click", () => {
|
||||
this.handleAddModerator();
|
||||
});
|
||||
}
|
||||
if (
|
||||
this.adminModeratorInput &&
|
||||
this.adminModeratorInput.dataset.bound !== "true"
|
||||
) {
|
||||
this.adminModeratorInput.dataset.bound = "true";
|
||||
this.adminModeratorInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
this.handleAddModerator();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.profileWalletUriInput = this.profileModalController.walletUriInput;
|
||||
this.profileWalletDefaultZapInput =
|
||||
this.profileModalController.walletDefaultZapInput;
|
||||
this.profileWalletSaveButton = this.profileModalController.walletSaveButton;
|
||||
this.profileWalletTestButton = this.profileModalController.walletTestButton;
|
||||
this.profileWalletDisconnectButton =
|
||||
this.profileModalController.walletDisconnectButton;
|
||||
this.profileWalletStatusText =
|
||||
this.profileModalController.profileWalletStatusText;
|
||||
|
||||
if (
|
||||
this.adminAddWhitelistBtn &&
|
||||
this.adminAddWhitelistBtn.dataset.bound !== "true"
|
||||
) {
|
||||
this.adminAddWhitelistBtn.dataset.bound = "true";
|
||||
this.adminAddWhitelistBtn.addEventListener("click", () => {
|
||||
this.handleAdminListMutation("whitelist", "add");
|
||||
});
|
||||
}
|
||||
if (
|
||||
this.adminWhitelistInput &&
|
||||
this.adminWhitelistInput.dataset.bound !== "true"
|
||||
) {
|
||||
this.adminWhitelistInput.dataset.bound = "true";
|
||||
this.adminWhitelistInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
this.handleAdminListMutation("whitelist", "add");
|
||||
}
|
||||
});
|
||||
}
|
||||
this.profileHistoryRenderer =
|
||||
this.profileModalController.profileHistoryRenderer;
|
||||
|
||||
if (
|
||||
this.adminAddBlacklistBtn &&
|
||||
this.adminAddBlacklistBtn.dataset.bound !== "true"
|
||||
) {
|
||||
this.adminAddBlacklistBtn.dataset.bound = "true";
|
||||
this.adminAddBlacklistBtn.addEventListener("click", () => {
|
||||
this.handleAdminListMutation("blacklist", "add");
|
||||
});
|
||||
}
|
||||
if (
|
||||
this.adminBlacklistInput &&
|
||||
this.adminBlacklistInput.dataset.bound !== "true"
|
||||
) {
|
||||
this.adminBlacklistInput.dataset.bound = "true";
|
||||
this.adminBlacklistInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
this.handleAdminListMutation("blacklist", "add");
|
||||
}
|
||||
});
|
||||
}
|
||||
this.adminModeratorsSection =
|
||||
this.profileModalController.adminModeratorsSection;
|
||||
this.adminModeratorsEmpty =
|
||||
this.profileModalController.adminModeratorsEmpty;
|
||||
this.adminModeratorList =
|
||||
this.profileModalController.adminModeratorList;
|
||||
this.adminModeratorInput =
|
||||
this.profileModalController.adminModeratorInput;
|
||||
this.adminAddModeratorBtn =
|
||||
this.profileModalController.adminAddModeratorButton;
|
||||
|
||||
this.selectProfilePane("account");
|
||||
this.populateProfileRelays();
|
||||
this.populateBlockedList();
|
||||
this.refreshWalletPaneState();
|
||||
await this.refreshAdminPaneState();
|
||||
this.adminWhitelistSection =
|
||||
this.profileModalController.adminWhitelistSection;
|
||||
this.adminWhitelistEmpty =
|
||||
this.profileModalController.adminWhitelistEmpty;
|
||||
this.adminWhitelistList =
|
||||
this.profileModalController.adminWhitelistList;
|
||||
this.adminWhitelistInput =
|
||||
this.profileModalController.adminWhitelistInput;
|
||||
this.adminAddWhitelistBtn =
|
||||
this.profileModalController.adminAddWhitelistButton;
|
||||
|
||||
this.renderSavedProfiles();
|
||||
this.adminBlacklistSection =
|
||||
this.profileModalController.adminBlacklistSection;
|
||||
this.adminBlacklistEmpty =
|
||||
this.profileModalController.adminBlacklistEmpty;
|
||||
this.adminBlacklistList =
|
||||
this.profileModalController.adminBlacklistList;
|
||||
this.adminBlacklistInput =
|
||||
this.profileModalController.adminBlacklistInput;
|
||||
this.adminAddBlacklistBtn =
|
||||
this.profileModalController.adminAddBlacklistButton;
|
||||
|
||||
console.log("Profile modal initialization successful");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("initProfileModal failed:", error);
|
||||
// Not critical if missing
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+369
-107
@@ -446,10 +446,17 @@ export class ProfileModalController {
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.innerHTML = html;
|
||||
this.removeTrackingScripts(wrapper);
|
||||
this.modalContainer.appendChild(wrapper);
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = html;
|
||||
this.removeTrackingScripts(template.content);
|
||||
|
||||
const modalRoot = template.content.querySelector("#profileModal");
|
||||
if (!(modalRoot instanceof HTMLElement)) {
|
||||
throw new Error("profile modal markup missing expected #profileModal root");
|
||||
}
|
||||
|
||||
this.modalContainer.appendChild(template.content);
|
||||
this.profileModal = modalRoot;
|
||||
|
||||
this.cacheDomReferences();
|
||||
this.registerEventListeners();
|
||||
@@ -1166,38 +1173,55 @@ export class ProfileModalController {
|
||||
skipPublishIfUnchanged = true,
|
||||
unchangedMessage = null,
|
||||
} = {}) {
|
||||
const operationContext = {
|
||||
ok: false,
|
||||
changed: false,
|
||||
reason: null,
|
||||
error: null,
|
||||
publishResult: null,
|
||||
operationResult: null,
|
||||
};
|
||||
|
||||
const activePubkey = this.normalizeHexPubkey(this.getActivePubkey());
|
||||
if (!activePubkey) {
|
||||
this.showError("Please login to manage your relays.");
|
||||
return;
|
||||
operationContext.reason = "no-active-pubkey";
|
||||
return operationContext;
|
||||
}
|
||||
|
||||
if (typeof operation !== "function") {
|
||||
return;
|
||||
operationContext.reason = "invalid-operation";
|
||||
return operationContext;
|
||||
}
|
||||
|
||||
const previous = this.services.relayManager.snapshot();
|
||||
let result;
|
||||
try {
|
||||
result = operation();
|
||||
operationContext.operationResult = result;
|
||||
} catch (error) {
|
||||
const message =
|
||||
error && typeof error.message === "string" && error.message.trim()
|
||||
? error.message.trim()
|
||||
: "Failed to update relay preferences.";
|
||||
operationContext.reason = "operation-error";
|
||||
operationContext.error = error;
|
||||
this.showError(message);
|
||||
return;
|
||||
return operationContext;
|
||||
}
|
||||
|
||||
const changed = !!result?.changed;
|
||||
operationContext.changed = changed;
|
||||
if (!changed && skipPublishIfUnchanged) {
|
||||
const reason = result?.reason || "unchanged";
|
||||
operationContext.reason = reason;
|
||||
if (result?.reason === "duplicate") {
|
||||
this.showSuccess("Relay is already configured.");
|
||||
} else if (typeof unchangedMessage === "string" && unchangedMessage) {
|
||||
this.showSuccess(unchangedMessage);
|
||||
}
|
||||
this.populateProfileRelays();
|
||||
return;
|
||||
return operationContext;
|
||||
}
|
||||
|
||||
this.populateProfileRelays();
|
||||
@@ -1209,9 +1233,12 @@ export class ProfileModalController {
|
||||
if (!publishResult?.ok) {
|
||||
throw new Error("No relays accepted the update.");
|
||||
}
|
||||
operationContext.ok = true;
|
||||
operationContext.publishResult = publishResult;
|
||||
if (successMessage) {
|
||||
this.showSuccess(successMessage);
|
||||
}
|
||||
return operationContext;
|
||||
} catch (error) {
|
||||
this.services.relayManager.setEntries(previous, { allowEmpty: false });
|
||||
this.populateProfileRelays();
|
||||
@@ -1219,28 +1246,37 @@ export class ProfileModalController {
|
||||
error && typeof error.message === "string" && error.message.trim()
|
||||
? error.message.trim()
|
||||
: "Failed to publish relay configuration. Please try again.";
|
||||
operationContext.reason = "publish-failed";
|
||||
operationContext.error = error;
|
||||
this.showError(message);
|
||||
return operationContext;
|
||||
}
|
||||
}
|
||||
|
||||
async handleAddRelay() {
|
||||
const activePubkey = this.normalizeHexPubkey(this.getActivePubkey());
|
||||
if (!activePubkey) {
|
||||
this.showError("Please login to manage your relays.");
|
||||
return;
|
||||
}
|
||||
|
||||
const rawValue =
|
||||
typeof this.profileRelayInput?.value === "string"
|
||||
? this.profileRelayInput.value
|
||||
: "";
|
||||
const trimmed = rawValue.trim();
|
||||
|
||||
const context = {
|
||||
input: this.profileRelayInput,
|
||||
rawValue,
|
||||
url: trimmed,
|
||||
result: null,
|
||||
success: false,
|
||||
reason: null,
|
||||
};
|
||||
|
||||
if (!trimmed) {
|
||||
this.showError("Enter a relay URL to add.");
|
||||
return;
|
||||
context.reason = "empty";
|
||||
this.callbacks.onAddRelay(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
await this.handleRelayOperation(
|
||||
const operationResult = await this.handleRelayOperation(
|
||||
() => this.services.relayManager.addRelay(trimmed),
|
||||
{
|
||||
successMessage: "Relay saved.",
|
||||
@@ -1252,22 +1288,31 @@ export class ProfileModalController {
|
||||
this.profileRelayInput.value = "";
|
||||
}
|
||||
|
||||
this.callbacks.onAddRelay(this.profileRelayInput, this);
|
||||
context.result = operationResult;
|
||||
context.success = !!operationResult?.ok;
|
||||
context.reason = operationResult?.reason || null;
|
||||
|
||||
this.callbacks.onAddRelay(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
async handleRestoreRelays() {
|
||||
const activePubkey = this.normalizeHexPubkey(this.getActivePubkey());
|
||||
if (!activePubkey) {
|
||||
this.showError("Please login to manage your relays.");
|
||||
return;
|
||||
}
|
||||
const context = {
|
||||
confirmed: false,
|
||||
result: null,
|
||||
success: false,
|
||||
reason: null,
|
||||
};
|
||||
|
||||
const confirmed = window.confirm("Restore the recommended relay defaults?");
|
||||
context.confirmed = confirmed;
|
||||
if (!confirmed) {
|
||||
return;
|
||||
context.reason = "cancelled";
|
||||
this.callbacks.onRestoreRelays(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
await this.handleRelayOperation(
|
||||
const operationResult = await this.handleRelayOperation(
|
||||
() => this.services.relayManager.restoreDefaults(),
|
||||
{
|
||||
successMessage: "Relay defaults restored.",
|
||||
@@ -1275,7 +1320,12 @@ export class ProfileModalController {
|
||||
},
|
||||
);
|
||||
|
||||
this.callbacks.onRestoreRelays(this);
|
||||
context.result = operationResult;
|
||||
context.success = !!operationResult?.ok;
|
||||
context.reason = operationResult?.reason || null;
|
||||
|
||||
this.callbacks.onRestoreRelays(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
async handleRelayModeToggle(url) {
|
||||
@@ -1428,22 +1478,38 @@ export class ProfileModalController {
|
||||
}
|
||||
|
||||
async handleAddBlockedCreator() {
|
||||
if (!this.profileBlockedInput) {
|
||||
return;
|
||||
}
|
||||
const input = this.profileBlockedInput || null;
|
||||
const rawValue = typeof input?.value === "string" ? input.value : "";
|
||||
const trimmed = rawValue.trim();
|
||||
|
||||
const rawValue = this.profileBlockedInput.value;
|
||||
const trimmed = typeof rawValue === "string" ? rawValue.trim() : "";
|
||||
const context = {
|
||||
input,
|
||||
rawValue,
|
||||
value: trimmed,
|
||||
success: false,
|
||||
reason: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
if (!input) {
|
||||
context.reason = "missing-input";
|
||||
this.callbacks.onAddBlocked(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
if (!trimmed) {
|
||||
this.showError("Enter an npub to block.");
|
||||
return;
|
||||
context.reason = "empty";
|
||||
this.callbacks.onAddBlocked(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
const activePubkey = this.normalizeHexPubkey(this.getActivePubkey());
|
||||
if (!activePubkey) {
|
||||
this.showError("Please login to manage your block list.");
|
||||
return;
|
||||
context.reason = "no-active-pubkey";
|
||||
this.callbacks.onAddBlocked(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
const actorHex = activePubkey;
|
||||
@@ -1453,30 +1519,41 @@ export class ProfileModalController {
|
||||
targetHex = this.safeDecodeNpub(trimmed) || "";
|
||||
if (!targetHex) {
|
||||
this.showError("Invalid npub. Please double-check and try again.");
|
||||
return;
|
||||
context.reason = "invalid-npub";
|
||||
this.callbacks.onAddBlocked(context, this);
|
||||
return context;
|
||||
}
|
||||
} else if (/^[0-9a-f]{64}$/i.test(trimmed)) {
|
||||
targetHex = trimmed.toLowerCase();
|
||||
} else {
|
||||
this.showError("Enter a valid npub or hex pubkey.");
|
||||
return;
|
||||
context.reason = "invalid-value";
|
||||
this.callbacks.onAddBlocked(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
if (targetHex === actorHex) {
|
||||
this.showError("You cannot block yourself.");
|
||||
return;
|
||||
context.reason = "self";
|
||||
this.callbacks.onAddBlocked(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
context.targetHex = targetHex;
|
||||
|
||||
try {
|
||||
await this.services.userBlocks.ensureLoaded(actorHex);
|
||||
|
||||
if (this.services.userBlocks.isBlocked(targetHex)) {
|
||||
this.showSuccess("You already blocked this creator.");
|
||||
context.reason = "already-blocked";
|
||||
} else {
|
||||
await this.services.userBlocks.addBlock(targetHex, actorHex);
|
||||
this.showSuccess(
|
||||
"Creator blocked. You won't see their videos anymore.",
|
||||
);
|
||||
context.success = true;
|
||||
context.reason = "blocked";
|
||||
}
|
||||
|
||||
this.profileBlockedInput.value = "";
|
||||
@@ -1484,6 +1561,8 @@ export class ProfileModalController {
|
||||
await this.services.loadVideos();
|
||||
} catch (error) {
|
||||
console.error("Failed to add creator to personal block list:", error);
|
||||
context.error = error;
|
||||
context.reason = error?.code || "service-error";
|
||||
const message =
|
||||
error?.code === "nip04-missing"
|
||||
? "Your Nostr extension must support NIP-04 to manage private lists."
|
||||
@@ -1491,7 +1570,8 @@ export class ProfileModalController {
|
||||
this.showError(message);
|
||||
}
|
||||
|
||||
this.callbacks.onAddBlocked(this.profileBlockedInput, this);
|
||||
this.callbacks.onAddBlocked(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
async handleRemoveBlockedCreator(candidate) {
|
||||
@@ -1751,28 +1831,47 @@ export class ProfileModalController {
|
||||
}
|
||||
|
||||
async handleWalletSave() {
|
||||
const { uri, defaultZap, error } = this.getWalletFormValues();
|
||||
const context = {
|
||||
uri,
|
||||
defaultZap: defaultZap ?? null,
|
||||
sanitizedUri: null,
|
||||
success: false,
|
||||
reason: null,
|
||||
error: error || null,
|
||||
status: null,
|
||||
variant: null,
|
||||
};
|
||||
|
||||
if (this.isWalletBusy()) {
|
||||
return;
|
||||
context.reason = "busy";
|
||||
this.callbacks.onWalletSave(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
const { uri, defaultZap, error } = this.getWalletFormValues();
|
||||
if (error) {
|
||||
this.updateWalletStatus(error, "error");
|
||||
this.showError(error);
|
||||
context.reason = "invalid-default-zap";
|
||||
if (this.walletDefaultZapInput instanceof HTMLElement) {
|
||||
this.walletDefaultZapInput.focus();
|
||||
}
|
||||
return;
|
||||
this.callbacks.onWalletSave(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
const { valid, sanitized, message } = this.validateWalletUri(uri);
|
||||
context.sanitizedUri = sanitized;
|
||||
if (!valid) {
|
||||
this.updateWalletStatus(message, "error");
|
||||
this.showError(message);
|
||||
context.reason = "invalid-uri";
|
||||
context.error = message;
|
||||
if (this.walletUriInput instanceof HTMLElement) {
|
||||
this.walletUriInput.focus();
|
||||
}
|
||||
return;
|
||||
this.callbacks.onWalletSave(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
const normalizedActive = this.normalizeHexPubkey(this.getActivePubkey());
|
||||
@@ -1780,7 +1879,10 @@ export class ProfileModalController {
|
||||
const loginMessage = "Sign in to save wallet settings.";
|
||||
this.updateWalletStatus(loginMessage, "error");
|
||||
this.showError(loginMessage);
|
||||
return;
|
||||
context.reason = "no-active-pubkey";
|
||||
context.error = loginMessage;
|
||||
this.callbacks.onWalletSave(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
this.setWalletPaneBusy(true);
|
||||
@@ -1796,11 +1898,14 @@ export class ProfileModalController {
|
||||
finalStatus = "Wallet settings saved.";
|
||||
finalVariant = "success";
|
||||
this.showSuccess("Wallet settings saved.");
|
||||
context.reason = "saved";
|
||||
} else {
|
||||
finalStatus = "Wallet connection removed.";
|
||||
finalVariant = "info";
|
||||
this.showStatus("Wallet connection removed.");
|
||||
context.reason = "cleared";
|
||||
}
|
||||
context.success = true;
|
||||
} catch (error) {
|
||||
const fallbackMessage = "Failed to save wallet settings.";
|
||||
const detail =
|
||||
@@ -1809,6 +1914,8 @@ export class ProfileModalController {
|
||||
: fallbackMessage;
|
||||
finalStatus = detail;
|
||||
finalVariant = "error";
|
||||
context.error = detail;
|
||||
context.reason = error?.code || "service-error";
|
||||
this.showError(detail);
|
||||
} finally {
|
||||
this.setWalletPaneBusy(false);
|
||||
@@ -1816,34 +1923,59 @@ export class ProfileModalController {
|
||||
if (finalStatus) {
|
||||
this.updateWalletStatus(finalStatus, finalVariant);
|
||||
}
|
||||
context.status = finalStatus;
|
||||
context.variant = finalVariant;
|
||||
this.callbacks.onWalletSave(context, this);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async handleWalletTest() {
|
||||
const { uri, defaultZap, error } = this.getWalletFormValues();
|
||||
const context = {
|
||||
uri,
|
||||
defaultZap: defaultZap ?? null,
|
||||
sanitizedUri: null,
|
||||
success: false,
|
||||
reason: null,
|
||||
error: error || null,
|
||||
status: null,
|
||||
variant: null,
|
||||
result: null,
|
||||
};
|
||||
|
||||
if (this.isWalletBusy()) {
|
||||
return;
|
||||
context.reason = "busy";
|
||||
this.callbacks.onWalletTest(context, this);
|
||||
return context.result;
|
||||
}
|
||||
|
||||
const { uri, defaultZap, error } = this.getWalletFormValues();
|
||||
if (error) {
|
||||
this.updateWalletStatus(error, "error");
|
||||
this.showError(error);
|
||||
context.reason = "invalid-default-zap";
|
||||
if (this.walletDefaultZapInput instanceof HTMLElement) {
|
||||
this.walletDefaultZapInput.focus();
|
||||
}
|
||||
return;
|
||||
this.callbacks.onWalletTest(context, this);
|
||||
return context.result;
|
||||
}
|
||||
|
||||
const { valid, sanitized, message } = this.validateWalletUri(uri, {
|
||||
requireValue: true,
|
||||
});
|
||||
context.sanitizedUri = sanitized;
|
||||
if (!valid) {
|
||||
this.updateWalletStatus(message, "error");
|
||||
this.showError(message);
|
||||
context.reason = "invalid-uri";
|
||||
context.error = message;
|
||||
if (this.walletUriInput instanceof HTMLElement) {
|
||||
this.walletUriInput.focus();
|
||||
}
|
||||
return;
|
||||
this.callbacks.onWalletTest(context, this);
|
||||
return context.result;
|
||||
}
|
||||
|
||||
const normalizedActive = this.normalizeHexPubkey(this.getActivePubkey());
|
||||
@@ -1851,7 +1983,10 @@ export class ProfileModalController {
|
||||
const loginMessage = "Sign in to test your wallet connection.";
|
||||
this.updateWalletStatus(loginMessage, "error");
|
||||
this.showError(loginMessage);
|
||||
return;
|
||||
context.reason = "no-active-pubkey";
|
||||
context.error = loginMessage;
|
||||
this.callbacks.onWalletTest(context, this);
|
||||
return context.result;
|
||||
}
|
||||
|
||||
this.setWalletPaneBusy(true);
|
||||
@@ -1865,12 +2000,14 @@ export class ProfileModalController {
|
||||
finalStatus = "Wallet connection confirmed.";
|
||||
finalVariant = "success";
|
||||
this.showSuccess("Wallet connection confirmed.");
|
||||
context.result = result;
|
||||
context.success = true;
|
||||
context.reason = "tested";
|
||||
|
||||
const currentSettings = this.services.getActiveNwcSettings();
|
||||
if (currentSettings.nwcUri === sanitized) {
|
||||
await this.services.updateActiveNwcSettings({ lastChecked: Date.now() });
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
const fallbackMessage = "Failed to reach wallet.";
|
||||
const detail =
|
||||
@@ -1879,20 +2016,36 @@ export class ProfileModalController {
|
||||
: fallbackMessage;
|
||||
finalStatus = detail;
|
||||
finalVariant = "error";
|
||||
context.error = detail;
|
||||
context.reason = error?.code || "service-error";
|
||||
this.showError(detail);
|
||||
return null;
|
||||
} finally {
|
||||
this.setWalletPaneBusy(false);
|
||||
this.refreshWalletPaneState();
|
||||
if (finalStatus) {
|
||||
this.updateWalletStatus(finalStatus, finalVariant);
|
||||
}
|
||||
context.status = finalStatus;
|
||||
context.variant = finalVariant;
|
||||
this.callbacks.onWalletTest(context, this);
|
||||
}
|
||||
|
||||
return context.result;
|
||||
}
|
||||
|
||||
async handleWalletDisconnect() {
|
||||
const context = {
|
||||
success: false,
|
||||
reason: null,
|
||||
error: null,
|
||||
status: null,
|
||||
variant: null,
|
||||
};
|
||||
|
||||
if (this.isWalletBusy()) {
|
||||
return;
|
||||
context.reason = "busy";
|
||||
this.callbacks.onWalletDisconnect(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
const normalizedActive = this.normalizeHexPubkey(this.getActivePubkey());
|
||||
@@ -1900,7 +2053,10 @@ export class ProfileModalController {
|
||||
const loginMessage = "Sign in to disconnect your wallet.";
|
||||
this.updateWalletStatus(loginMessage, "error");
|
||||
this.showError(loginMessage);
|
||||
return;
|
||||
context.reason = "no-active-pubkey";
|
||||
context.error = loginMessage;
|
||||
this.callbacks.onWalletDisconnect(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
this.setWalletPaneBusy(true);
|
||||
@@ -1912,6 +2068,8 @@ export class ProfileModalController {
|
||||
);
|
||||
finalStatus = "Wallet disconnected.";
|
||||
this.showStatus("Wallet disconnected.");
|
||||
context.success = true;
|
||||
context.reason = "disconnected";
|
||||
} catch (error) {
|
||||
const fallbackMessage = "Failed to disconnect wallet.";
|
||||
const detail =
|
||||
@@ -1920,6 +2078,8 @@ export class ProfileModalController {
|
||||
: fallbackMessage;
|
||||
finalStatus = detail;
|
||||
finalVariant = "error";
|
||||
context.error = detail;
|
||||
context.reason = error?.code || "service-error";
|
||||
this.showError(detail);
|
||||
} finally {
|
||||
this.setWalletPaneBusy(false);
|
||||
@@ -1927,7 +2087,12 @@ export class ProfileModalController {
|
||||
if (finalStatus) {
|
||||
this.updateWalletStatus(finalStatus, finalVariant);
|
||||
}
|
||||
context.status = finalStatus;
|
||||
context.variant = finalVariant;
|
||||
this.callbacks.onWalletDisconnect(context, this);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
storeAdminEmptyMessages() {
|
||||
@@ -2211,6 +2376,20 @@ export class ProfileModalController {
|
||||
}
|
||||
|
||||
async handleAddModerator() {
|
||||
const input = this.adminModeratorInput || null;
|
||||
const rawValue = typeof input?.value === "string" ? input.value : "";
|
||||
const trimmed = rawValue.trim();
|
||||
const context = {
|
||||
input,
|
||||
rawValue,
|
||||
value: trimmed,
|
||||
actorNpub: null,
|
||||
success: false,
|
||||
reason: null,
|
||||
error: null,
|
||||
result: null,
|
||||
};
|
||||
|
||||
let preloadError = null;
|
||||
try {
|
||||
await this.services.accessControl.ensureReady();
|
||||
@@ -2221,18 +2400,25 @@ export class ProfileModalController {
|
||||
|
||||
if (preloadError) {
|
||||
this.showError(this.describeAdminError(preloadError.code || "storage-error"));
|
||||
return;
|
||||
context.reason = preloadError.code || "storage-error";
|
||||
context.error = preloadError;
|
||||
this.callbacks.onAdminAddModerator(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
const actorNpub = this.ensureAdminActor(true);
|
||||
if (!actorNpub || !this.adminModeratorInput) {
|
||||
return;
|
||||
context.actorNpub = actorNpub;
|
||||
if (!actorNpub || !input) {
|
||||
context.reason = actorNpub ? "missing-input" : "unauthorized";
|
||||
this.callbacks.onAdminAddModerator(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
const value = this.adminModeratorInput.value.trim();
|
||||
if (!value) {
|
||||
if (!trimmed) {
|
||||
this.showError("Enter an npub to add as a moderator.");
|
||||
return;
|
||||
context.reason = "empty";
|
||||
this.callbacks.onAdminAddModerator(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
if (this.adminAddModeratorButton) {
|
||||
@@ -2241,24 +2427,51 @@ export class ProfileModalController {
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.services.accessControl.addModerator(actorNpub, value);
|
||||
const result = await this.services.accessControl.addModerator(
|
||||
actorNpub,
|
||||
trimmed,
|
||||
);
|
||||
context.result = result;
|
||||
if (!result.ok) {
|
||||
this.showError(this.describeAdminError(result.error));
|
||||
return;
|
||||
context.reason = result.error || "service-error";
|
||||
return context;
|
||||
}
|
||||
|
||||
this.adminModeratorInput.value = "";
|
||||
this.showSuccess("Moderator added successfully.");
|
||||
await this.services.onAccessControlUpdated();
|
||||
context.success = true;
|
||||
context.reason = "added";
|
||||
} finally {
|
||||
if (this.adminAddModeratorButton) {
|
||||
this.adminAddModeratorButton.disabled = false;
|
||||
this.adminAddModeratorButton.removeAttribute("aria-busy");
|
||||
}
|
||||
this.callbacks.onAdminAddModerator(context, this);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async handleRemoveModerator(npub, button) {
|
||||
const context = {
|
||||
npub,
|
||||
button,
|
||||
actorNpub: null,
|
||||
success: false,
|
||||
reason: null,
|
||||
error: null,
|
||||
result: null,
|
||||
};
|
||||
|
||||
const releaseButton = () => {
|
||||
if (button instanceof HTMLElement) {
|
||||
button.disabled = false;
|
||||
button.removeAttribute("aria-busy");
|
||||
}
|
||||
};
|
||||
|
||||
let preloadError = null;
|
||||
try {
|
||||
await this.services.accessControl.ensureReady();
|
||||
@@ -2269,40 +2482,97 @@ export class ProfileModalController {
|
||||
|
||||
if (preloadError) {
|
||||
this.showError(this.describeAdminError(preloadError.code || "storage-error"));
|
||||
if (button instanceof HTMLElement) {
|
||||
button.disabled = false;
|
||||
button.removeAttribute("aria-busy");
|
||||
}
|
||||
return;
|
||||
context.reason = preloadError.code || "storage-error";
|
||||
context.error = preloadError;
|
||||
releaseButton();
|
||||
this.callbacks.onAdminRemoveModerator(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
const actorNpub = this.ensureAdminActor(true);
|
||||
context.actorNpub = actorNpub;
|
||||
if (!actorNpub) {
|
||||
if (button instanceof HTMLElement) {
|
||||
button.disabled = false;
|
||||
button.removeAttribute("aria-busy");
|
||||
}
|
||||
return;
|
||||
context.reason = "unauthorized";
|
||||
releaseButton();
|
||||
this.callbacks.onAdminRemoveModerator(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
const result = await this.services.accessControl.removeModerator(
|
||||
actorNpub,
|
||||
npub,
|
||||
);
|
||||
context.result = result;
|
||||
if (!result.ok) {
|
||||
this.showError(this.describeAdminError(result.error));
|
||||
if (button instanceof HTMLElement) {
|
||||
button.disabled = false;
|
||||
button.removeAttribute("aria-busy");
|
||||
}
|
||||
return;
|
||||
context.reason = result.error || "service-error";
|
||||
releaseButton();
|
||||
this.callbacks.onAdminRemoveModerator(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
this.showSuccess("Moderator removed.");
|
||||
await this.services.onAccessControlUpdated();
|
||||
context.success = true;
|
||||
context.reason = "removed";
|
||||
|
||||
releaseButton();
|
||||
this.callbacks.onAdminRemoveModerator(context, this);
|
||||
return context;
|
||||
}
|
||||
|
||||
async handleAdminListMutation(listType, action, explicitNpub = null, sourceButton = null) {
|
||||
const isWhitelist = listType === "whitelist";
|
||||
const input = isWhitelist ? this.adminWhitelistInput : this.adminBlacklistInput;
|
||||
const addButton = isWhitelist ? this.adminAddWhitelistButton : this.adminAddBlacklistButton;
|
||||
const isAdd = action === "add";
|
||||
let buttonToToggle = sourceButton || (isAdd ? addButton : null);
|
||||
|
||||
const context = {
|
||||
listType,
|
||||
action,
|
||||
explicitNpub,
|
||||
sourceButton,
|
||||
actorNpub: null,
|
||||
targetNpub: null,
|
||||
success: false,
|
||||
reason: null,
|
||||
error: null,
|
||||
result: null,
|
||||
notificationResult: null,
|
||||
notificationError: null,
|
||||
};
|
||||
|
||||
const callbackMap = {
|
||||
whitelist: {
|
||||
add: this.callbacks.onAdminAddWhitelist,
|
||||
remove: this.callbacks.onAdminRemoveWhitelist,
|
||||
},
|
||||
blacklist: {
|
||||
add: this.callbacks.onAdminAddBlacklist,
|
||||
remove: this.callbacks.onAdminRemoveBlacklist,
|
||||
},
|
||||
};
|
||||
|
||||
const adminCallback = callbackMap[listType]?.[action] || noop;
|
||||
|
||||
const setBusy = (element, busy) => {
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
element.disabled = !!busy;
|
||||
if (busy) {
|
||||
element.setAttribute("aria-busy", "true");
|
||||
} else {
|
||||
element.removeAttribute("aria-busy");
|
||||
}
|
||||
};
|
||||
|
||||
const finalize = () => {
|
||||
setBusy(buttonToToggle, false);
|
||||
adminCallback(context, this);
|
||||
};
|
||||
|
||||
let preloadError = null;
|
||||
try {
|
||||
await this.services.accessControl.ensureReady();
|
||||
@@ -2313,46 +2583,35 @@ export class ProfileModalController {
|
||||
|
||||
if (preloadError) {
|
||||
this.showError(this.describeAdminError(preloadError.code || "storage-error"));
|
||||
if (sourceButton instanceof HTMLElement) {
|
||||
sourceButton.disabled = false;
|
||||
sourceButton.removeAttribute("aria-busy");
|
||||
}
|
||||
return;
|
||||
context.reason = preloadError.code || "storage-error";
|
||||
context.error = preloadError;
|
||||
finalize();
|
||||
return context;
|
||||
}
|
||||
|
||||
const actorNpub = this.ensureAdminActor(false);
|
||||
context.actorNpub = actorNpub;
|
||||
if (!actorNpub) {
|
||||
if (sourceButton instanceof HTMLElement) {
|
||||
sourceButton.disabled = false;
|
||||
sourceButton.removeAttribute("aria-busy");
|
||||
}
|
||||
return;
|
||||
context.reason = "unauthorized";
|
||||
finalize();
|
||||
return context;
|
||||
}
|
||||
|
||||
const isWhitelist = listType === "whitelist";
|
||||
const input = isWhitelist ? this.adminWhitelistInput : this.adminBlacklistInput;
|
||||
const addButton = isWhitelist ? this.adminAddWhitelistButton : this.adminAddBlacklistButton;
|
||||
const isAdd = action === "add";
|
||||
|
||||
let target = typeof explicitNpub === "string" ? explicitNpub.trim() : "";
|
||||
if (!target && input instanceof HTMLInputElement) {
|
||||
target = input.value.trim();
|
||||
}
|
||||
context.targetNpub = target;
|
||||
|
||||
if (isAdd && !target) {
|
||||
this.showError("Enter an npub before adding it to the list.");
|
||||
if (sourceButton instanceof HTMLElement) {
|
||||
sourceButton.disabled = false;
|
||||
sourceButton.removeAttribute("aria-busy");
|
||||
}
|
||||
return;
|
||||
context.reason = "empty";
|
||||
finalize();
|
||||
return context;
|
||||
}
|
||||
|
||||
const buttonToToggle = sourceButton || (isAdd ? addButton : null);
|
||||
if (buttonToToggle instanceof HTMLElement) {
|
||||
buttonToToggle.disabled = true;
|
||||
buttonToToggle.setAttribute("aria-busy", "true");
|
||||
}
|
||||
buttonToToggle = buttonToToggle || (isAdd ? addButton : null);
|
||||
setBusy(buttonToToggle, true);
|
||||
|
||||
let result;
|
||||
if (isWhitelist) {
|
||||
@@ -2365,13 +2624,13 @@ export class ProfileModalController {
|
||||
: await this.services.accessControl.removeFromBlacklist(actorNpub, target);
|
||||
}
|
||||
|
||||
context.result = result;
|
||||
|
||||
if (!result.ok) {
|
||||
this.showError(this.describeAdminError(result.error));
|
||||
if (buttonToToggle instanceof HTMLElement) {
|
||||
buttonToToggle.disabled = false;
|
||||
buttonToToggle.removeAttribute("aria-busy");
|
||||
}
|
||||
return;
|
||||
context.reason = result.error || "service-error";
|
||||
finalize();
|
||||
return context;
|
||||
}
|
||||
|
||||
if (isAdd && input instanceof HTMLInputElement) {
|
||||
@@ -2388,10 +2647,8 @@ export class ProfileModalController {
|
||||
this.showSuccess(successMessage);
|
||||
await this.services.onAccessControlUpdated();
|
||||
|
||||
if (buttonToToggle instanceof HTMLElement) {
|
||||
buttonToToggle.disabled = false;
|
||||
buttonToToggle.removeAttribute("aria-busy");
|
||||
}
|
||||
context.success = true;
|
||||
context.reason = isAdd ? "added" : "removed";
|
||||
|
||||
if (isAdd) {
|
||||
try {
|
||||
@@ -2400,6 +2657,7 @@ export class ProfileModalController {
|
||||
actorNpub,
|
||||
targetNpub: target,
|
||||
});
|
||||
context.notificationResult = notifyResult;
|
||||
if (!notifyResult?.ok) {
|
||||
const errorMessage = this.describeNotificationError(notifyResult?.error);
|
||||
if (errorMessage) {
|
||||
@@ -2413,6 +2671,7 @@ export class ProfileModalController {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
context.notificationError = error;
|
||||
console.error("Failed to send list notification DM:", error);
|
||||
if (isDevMode) {
|
||||
console.warn(
|
||||
@@ -2422,6 +2681,9 @@ export class ProfileModalController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finalize();
|
||||
return context;
|
||||
}
|
||||
|
||||
describeAdminError(code) {
|
||||
|
||||
Reference in new issue
Block a user