Merge pull request #690 from PR0M3TH3AN/codex/restructure-profile-modal-for-mobile-views

Improve profile modal mobile navigation
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-19 22:14:54 -04:00
commit a12e50f63d
6 files changed
+826 -105

No files matched your search

+119 -79
View File
@@ -7,13 +7,43 @@
aria-labelledby="profileModalTitle"
tabindex="-1"
data-ds="new"
data-mobile-view="menu"
>
<div class="bv-modal-backdrop"></div>
<div class="bv-modal__panel modal-sheet profile-modal" tabindex="-1">
<div
class="bv-modal__panel modal-sheet profile-modal"
tabindex="-1"
data-mobile-view="menu"
>
<div class="profile-modal__content">
<!-- Modal Header -->
<div class="modal-header">
<h2 id="profileModalTitle" class="text-lg font-bold text-text">Profile</h2>
<div class="modal-header profile-modal__header">
<div class="profile-modal__header-title">
<button
id="profileModalBack"
type="button"
class="modal-back btn-ghost focus-ring hidden"
aria-hidden="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 19l-7-7 7-7"
/>
</svg>
<span class="text-xs font-medium">Menu</span>
</button>
<h2 id="profileModalTitle" class="text-lg font-bold text-text">Profile</h2>
</div>
<button id="closeProfileModal" class="modal-close">
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -34,68 +64,79 @@
<!-- Modal Body -->
<div class="profile-modal__body">
<div class="profile-modal__layout">
<div
class="profile-modal__layout"
data-profile-layout
data-mobile-view="menu"
>
<!-- Left navigation column -->
<nav class="profile-modal__nav" aria-label="Profile sections">
<button
id="profileNavAccount"
type="button"
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneAccount"
aria-selected="true"
data-state="active"
>
Account
</button>
<button
id="profileNavRelays"
type="button"
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneRelays"
aria-selected="false"
>
Relays
</button>
<button
id="profileNavWallet"
type="button"
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneWallet"
aria-selected="false"
>
Wallet
</button>
<button
id="profileNavBlocked"
type="button"
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneBlocked"
aria-selected="false"
>
Blocked
</button>
<button
id="profileNavHistory"
type="button"
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneHistory"
aria-selected="false"
>
Watch history
</button>
<button
id="profileNavAdmin"
type="button"
class="btn-ghost focus-ring hidden w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneAdmin"
aria-selected="false"
>
Admin (Moderation)
</button>
</nav>
<div class="profile-modal__menu" data-profile-mobile-menu aria-hidden="false">
<nav class="profile-modal__nav" aria-label="Profile sections">
<button
id="profileNavAccount"
type="button"
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneAccount"
aria-selected="true"
data-state="active"
>
Account
</button>
<button
id="profileNavRelays"
type="button"
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneRelays"
aria-selected="false"
>
Relays
</button>
<button
id="profileNavWallet"
type="button"
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneWallet"
aria-selected="false"
>
Wallet
</button>
<button
id="profileNavBlocked"
type="button"
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneBlocked"
aria-selected="false"
>
Blocked
</button>
<button
id="profileNavHistory"
type="button"
class="btn-ghost focus-ring w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneHistory"
aria-selected="false"
>
Watch history
</button>
<button
id="profileNavAdmin"
type="button"
class="btn-ghost focus-ring hidden w-full justify-start gap-2 px-3 py-2 text-xs font-medium"
aria-controls="profilePaneAdmin"
aria-selected="false"
>
Admin (Moderation)
</button>
</nav>
</div>
<!-- Right content panes -->
<div class="profile-modal__panes">
<div
class="profile-modal__pane-wrapper"
data-profile-mobile-pane
aria-hidden="true"
>
<div class="profile-modal__panes">
<section
id="profilePaneAccount"
class="profile-pane space-y-6"
@@ -108,18 +149,17 @@
classes that may be purged or overridden by other builds.
-->
<div id="profileSwitcherTray" class="card space-y-5 p-5">
<div class="flex flex-wrap items-start justify-between gap-4">
<div class="flex flex-wrap items-start gap-4">
<div class="space-y-2">
<h3 class="text-base font-semibold text-text">Switch profiles</h3>
<p class="text-sm text-muted">
Save multiple Nostr profiles so you can swap identities without leaving bitvid.
</p>
</div>
<span class="badge" data-variant="info">Preview</span>
</div>
<div
class="card focus-ring flex items-center gap-4 p-4"
class="card profile-modal__active-profile"
aria-label="Current Nostr profile"
aria-pressed="true"
role="button"
@@ -136,22 +176,22 @@
class="h-full w-full object-cover"
/>
</span>
<div class="flex min-w-0 flex-1 flex-col gap-2">
<div class="flex flex-wrap items-center gap-3">
<div class="profile-modal__active-details">
<div class="profile-modal__active-meta">
<span class="text-3xs font-semibold uppercase tracking-extra-wide text-info">
Active profile
</span>
<a
id="profileChannelLink"
href="#"
class="btn-ghost focus-ring hidden text-xs"
>
View channel
</a>
</div>
<span id="profileModalName" class="truncate text-sm font-semibold text-text">Loading...</span>
<span id="profileModalNpub" class="break-all font-mono text-xs text-muted">npub1...</span>
</div>
<a
id="profileChannelLink"
href="#"
class="btn-ghost focus-ring profile-modal__channel-link hidden text-xs"
>
View channel
</a>
</div>
<div
@@ -166,13 +206,10 @@
<button
type="button"
id="profileAddAccountBtn"
class="btn focus-ring w-full justify-start gap-4 p-4"
class="profile-modal__add-profile"
aria-label="Add a new Nostr profile"
>
<span
class="flex h-10 w-10 items-center justify-center rounded-full bg-info/20 text-info"
aria-hidden="true"
>
<span class="profile-modal__add-profile-icon" aria-hidden="true">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
@@ -369,6 +406,7 @@
id="profileHistoryClear"
type="button"
class="btn-ghost focus-ring inline-flex items-center gap-2"
data-size="sm"
>
Clear local cache
</button>
@@ -376,6 +414,7 @@
id="profileHistoryRepublish"
type="button"
class="btn focus-ring inline-flex items-center gap-2"
data-size="sm"
>
Republish now
</button>
@@ -606,4 +645,5 @@
</button>
</div>
</div>
</div>
</div>
+1 -1
View File
@@ -5,4 +5,4 @@
--player-modal-inline-padding,clamp(var(--space-md-plus),4vw,var(--space-2xl))
)}#playerModal .video-modal__primary>.watch-container:first-child{padding-block-start:var(--player-modal-vertical-padding)}#playerModal .video-modal__secondary>.watch-container:last-child{padding-block-end:var(--player-modal-vertical-padding)}#playerModal .modal-nav .watch-container{max-width:none;margin-inline:0}#playerModal .modal-info-panel{display:flex;flex-direction:column;gap:var(--space-lg)}#playerModal h2#videoTitle{margin:0;font-size:clamp(var(--font-size-xl),calc(var(--font-size-modal-title-base) + .5vw),var(--font-size-xl-plus));font-weight:600;line-height:1.3;color:var(--color-text)}#playerModal .modal-meta{font-size:var(--font-size-sm);line-height:1.4;color:var(--color-text-muted)}#playerModal .modal-view-count{font-weight:600;color:var(--color-text)}#playerModal .channel-card{display:flex;align-items:center;gap:var(--space-md);padding:var(--space-md-plus) var(--space-lg);border-radius:var(--radius-xl);border:var(--border-width-hairline) solid var(--color-border-translucent);background-color:var(--color-surface);box-shadow:var(--shadow-sm)}@media (max-width:30rem){#playerModal .modal-nav .icon-button{width:var(--space-xl);height:var(--space-xl);min-width:var(--space-xl);min-height:var(--space-xl)}#playerModal .watch-container{padding-inline:var(--space-sm)}#playerModal .channel-card{gap:var(--space-sm);padding:var(--space-sm) var(--space-md)}}#playerModal .channel-card__avatar{flex-shrink:0;width:var(--avatar-size-lg);height:var(--avatar-size-lg);border-radius:var(--radius-full);overflow:hidden;background-color:var(--color-border-subtle)}#playerModal .channel-card__avatar img{display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}#playerModal .channel-card__name{margin:0;font-size:var(--font-size-lg);font-weight:600;color:var(--color-text)}#playerModal .channel-card__npub{margin:0;font-size:var(--font-size-sm);color:var(--color-text-muted)}#playerModal .description-panel{padding:var(--space-md-plus) var(--space-lg);border-radius:var(--radius-xl);border:var(--border-width-hairline) solid var(--color-border-translucent);background-color:var(--color-surface);box-shadow:var(--shadow-sm)}#playerModal .description-panel p{margin:0;font-size:var(--font-size-sm);line-height:1.6;color:var(--color-text)}.player-modal__content{position:relative;z-index:var(--z-modal-content);display:flex;flex:1 1 auto;width:100%;min-height:100%;flex-direction:column;overflow:visible;padding:0;margin:0;border:none;border-radius:0;background-color:var(--color-surface);box-shadow:none}@media (prefers-reduced-motion:reduce){.player-modal__content{transition-property:none}}#playerModal .modal-nav{position:sticky;top:0;z-index:var(--z-overlay-nav);--tw-translate-y:0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;background-color:var(--color-overlay-darker);background-image:linear-gradient(180deg,var(--color-overlay-panel-gradient-start) 0,var(--color-overlay-panel-gradient-end) 100%);border-bottom:var(--border-width-hairline) solid var(--color-border-translucent);backdrop-filter:blur(var(--modal-nav-backdrop-blur));-webkit-backdrop-filter:blur(var(--modal-nav-backdrop-blur))}#playerModal#playerModal .modal-nav__bar{display:block}#playerModal .modal-nav__bar{display:flex;width:100%;align-items:center;gap:.75rem;padding:.5rem}@media (min-width:640px){#playerModal .modal-nav__bar{padding-left:1rem;padding-right:1rem}}#playerModal .modal-nav__bar{padding-inline:var(
--player-modal-inline-padding,clamp(var(--space-md-plus),4vw,var(--space-2xl))
)}#playerModal#playerModal .modal-nav .modal-actions{display:block}#playerModal .modal-nav .modal-actions{display:flex;align-items:center;gap:.5rem;margin-left:auto}#playerModal .modal-nav .icon-button{display:inline-flex;align-items:center;justify-content:center;border-radius:var(--radius-full);color:var(--text-muted);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-timing-function:cubic-bezier(0,0,.2,1);width:var(--icon-size-lg);height:var(--icon-size-lg);background:var(--color-surface-tint-subtle);border:var(--border-width-hairline) solid var(--color-border-translucent-medium);color:var(--color-text-muted)}#playerModal .modal-nav .icon-button:focus-visible,#playerModal .modal-nav .icon-button:hover{background:var(--color-border-translucent-bright);border-color:var(--color-border-translucent-strong);color:var(--color-text)}.modal-nav--hidden{--tw-translate-y:-100%}.modal-nav--hidden,.modal-nav--visible{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.modal-nav--visible{--tw-translate-y:0px}.video-modal__layout{display:grid;gap:1.5rem}@media (min-width:1024px){.video-modal__layout{gap:2rem;grid-template-columns:minmax(0,var(--video-modal-primary-fr)) minmax(var(--video-modal-secondary-min),var(--video-modal-secondary-fr,1fr))}}.revert-modal__layout{display:grid;gap:1.5rem}@media (min-width:1024px){.revert-modal__layout{grid-template-columns:minmax(var(--revert-modal-sidebar-min),var(--revert-modal-sidebar-width)) minmax(0,1fr)}}#playerModal.video-modal__primary{display:block}.video-modal__primary{display:flex;flex-direction:column;gap:1.5rem}#playerModal.video-modal__secondary{display:block}.video-modal__secondary{display:flex;flex-direction:column;gap:1rem}.video-modal__video{position:relative;background-color:var(--color-black);padding-block:clamp(var(--space-xs),2.5vw,var(--space-md-plus))}@media (min-width:1024px){.video-modal__video{position:sticky;top:calc(var(--space-2xl) + var(--space-xl))}}#playerModal .video-modal__video video{border-radius:var(--radius-lg);box-shadow:var(--shadow-video-modal);max-height:min(80vh,56.25vw);-o-object-fit:contain;object-fit:contain}.video-modal__media{display:block;aspect-ratio:16/9;width:100%;-o-object-fit:contain;object-fit:contain}#playerModal.video-modal__details{display:block}.video-modal__details{display:flex;flex-direction:column;gap:1.5rem}.video-modal__meta{border-bottom-width:1px;border-color:var(--border-default);--tw-border-opacity:0.2;padding-bottom:1rem}#playerModal.video-modal__actions-row{display:block}.video-modal__actions-row{display:flex;flex-wrap:wrap;gap:.75rem}.video-modal__action-button,.video-modal__actions-row{align-items:center}.video-modal__action-button{display:inline-flex;height:2.75rem;justify-content:center;gap:.5rem;border-radius:var(--radius-full);font-size:var(--font-size-sm);line-height:var(--line-height-snug);font-weight:600;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-base);transition-timing-function:cubic-bezier(0,0,.2,1);min-width:var(--video-modal-action-min-width);padding-inline:var(--space-md);border-width:1px;border-style:solid}.bv-modal.video-modal__action-label,.profile-pane.video-modal__action-label,.video-modal__action-label{display:none}@media (min-width:640px){.video-modal__action-label{display:inline}}.video-modal__action-icon{height:1.25rem;width:1.25rem}.video-modal__channel,.video-modal__description,.video-modal__stats{border-radius:1rem;border-width:1px;border-color:var(--border-default);--tw-border-opacity:0.2;background-color:var(--surface-base);padding:1.25rem 1.5rem;--tw-shadow:var(--shadow-sm);--tw-shadow-colored:var(--shadow-sm);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.video-modal__description-body{overflow-wrap:anywhere;word-break:break-word}.video-modal__description-body a{color:var(--color-accent-strong);text-decoration:underline;text-decoration-color:currentColor;word-break:break-word}.video-modal__description-body a:focus-visible,.video-modal__description-body a:hover{color:var(--color-accent-pressed)}.video-modal__progress{height:.5rem;width:100%;overflow:hidden;border-radius:var(--radius-full);background-color:var(--border-subtle)}#playerModal.video-modal__stat-metrics{display:block}.video-modal__stat-metrics{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:.75rem;font-size:var(--font-size-sm);line-height:var(--line-height-snug);color:var(--text-muted)}.profile-modal{display:flexLine truncated
)}#playerModal#playerModal .modal-nav .modal-actions{display:block}#playerModal .modal-nav .modal-actions{display:flex;align-items:center;gap:.5rem;margin-left:auto}#playerModal .modal-nav .icon-button{display:inline-flex;align-items:center;justify-content:center;border-radius:var(--radius-full);color:var(--text-muted);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-timing-function:cubic-bezier(0,0,.2,1);width:var(--icon-size-lg);height:var(--icon-size-lg);background:var(--color-surface-tint-subtle);border:var(--border-width-hairline) solid var(--color-border-translucent-medium);color:var(--color-text-muted)}#playerModal .modal-nav .icon-button:focus-visible,#playerModal .modal-nav .icon-button:hover{background:var(--color-border-translucent-bright);border-color:var(--color-border-translucent-strong);color:var(--color-text)}.modal-nav--hidden{--tw-translate-y:-100%}.modal-nav--hidden,.modal-nav--visible{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.modal-nav--visible{--tw-translate-y:0px}.video-modal__layout{display:grid;gap:1.5rem}@media (min-width:1024px){.video-modal__layout{gap:2rem;grid-template-columns:minmax(0,var(--video-modal-primary-fr)) minmax(var(--video-modal-secondary-min),var(--video-modal-secondary-fr,1fr))}}.revert-modal__layout{display:grid;gap:1.5rem}@media (min-width:1024px){.revert-modal__layout{grid-template-columns:minmax(var(--revert-modal-sidebar-min),var(--revert-modal-sidebar-width)) minmax(0,1fr)}}#playerModal.video-modal__primary{display:block}.video-modal__primary{display:flex;flex-direction:column;gap:1.5rem}#playerModal.video-modal__secondary{display:block}.video-modal__secondary{display:flex;flex-direction:column;gap:1rem}.video-modal__video{position:relative;background-color:var(--color-black);padding-block:clamp(var(--space-xs),2.5vw,var(--space-md-plus))}@media (min-width:1024px){.video-modal__video{position:sticky;top:calc(var(--space-2xl) + var(--space-xl))}}#playerModal .video-modal__video video{border-radius:var(--radius-lg);box-shadow:var(--shadow-video-modal);max-height:min(80vh,56.25vw);-o-object-fit:contain;object-fit:contain}.video-modal__media{display:block;aspect-ratio:16/9;width:100%;-o-object-fit:contain;object-fit:contain}#playerModal.video-modal__details{display:block}.video-modal__details{display:flex;flex-direction:column;gap:1.5rem}.video-modal__meta{border-bottom-width:1px;border-color:var(--border-default);--tw-border-opacity:0.2;padding-bottom:1rem}#playerModal.video-modal__actions-row{display:block}.video-modal__actions-row{display:flex;flex-wrap:wrap;gap:.75rem}.video-modal__action-button,.video-modal__actions-row{align-items:center}.video-modal__action-button{display:inline-flex;height:2.75rem;justify-content:center;gap:.5rem;border-radius:var(--radius-full);font-size:var(--font-size-sm);line-height:var(--line-height-snug);font-weight:600;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-base);transition-timing-function:cubic-bezier(0,0,.2,1);min-width:var(--video-modal-action-min-width);padding-inline:var(--space-md);border-width:1px;border-style:solid}.bv-modal.video-modal__action-label,.profile-pane.video-modal__action-label,.video-modal__action-label{display:none}@media (min-width:640px){.video-modal__action-label{display:inline}}.video-modal__action-icon{height:1.25rem;width:1.25rem}.video-modal__channel,.video-modal__description,.video-modal__stats{border-radius:1rem;border-width:1px;border-color:var(--border-default);--tw-border-opacity:0.2;background-color:var(--surface-base);padding:1.25rem 1.5rem;--tw-shadow:var(--shadow-sm);--tw-shadow-colored:var(--shadow-sm);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.video-modal__description-body{overflow-wrap:anywhere;word-break:break-word}.video-modal__description-body a{color:var(--color-accent-strong);text-decoration:underline;text-decoration-color:currentColor;word-break:break-word}.video-modal__description-body a:focus-visible,.video-modal__description-body a:hover{color:var(--color-accent-pressed)}.video-modal__progress{height:.5rem;width:100%;overflow:hidden;border-radius:var(--radius-full);background-color:var(--border-subtle)}#playerModal.video-modal__stat-metrics{display:block}.video-modal__stat-metrics{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:.75rem;font-size:var(--font-size-sm);line-height:var(--line-height-snug);color:var(--text-muted)}.profile-modal{display:flexLine truncated
+146 -7
View File
@@ -479,6 +479,16 @@
@apply focus-ring inline-flex items-center justify-center gap-2 rounded-full border border-transparent bg-accent-strong px-5 py-2.5 text-base font-semibold text-white shadow-lg transition duration-base ease-in-out;
}
.btn[data-size="sm"],
.btn-ghost[data-size="sm"] {
@apply px-3 py-1.5 text-sm;
}
.btn[data-size="sm"],
.btn-ghost[data-size="sm"] {
@apply gap-1.5;
}
.btn:hover {
@apply bg-accent;
}
@@ -1071,6 +1081,16 @@
color: var(--color-text);
}
.modal-back {
@apply inline-flex items-center gap-2;
}
@media (min-width: theme("screens.lg")) {
.modal-back {
display: none !important;
}
}
.modal-body {
@apply space-y-5 px-8 pb-8 pt-2;
overscroll-behavior: contain;
@@ -1538,13 +1558,88 @@
display: flex;
flex-direction: column;
inline-size: 100%;
max-block-size: var(--modal-shell-max-height);
block-size: 100%;
min-block-size: 100%;
max-block-size: none;
overflow: hidden;
--profile-modal-gap: calc(var(--space-lg) - var(--space-xs));
--profile-modal-padding-inline: calc(var(--space-lg) - var(--space-xs));
--profile-modal-padding-block-start: var(--space-md);
--profile-modal-padding-block-end: calc(var(--space-lg) - var(--space-xs));
--profile-modal-nav-gap: var(--space-xs);
--profile-modal-scale: 0.9;
--profile-modal-gap:
calc((var(--space-md) - var(--space-3xs)) * var(--profile-modal-scale));
--profile-modal-padding-inline:
calc((var(--space-lg) - var(--space-md)) * var(--profile-modal-scale));
--profile-modal-padding-block-start:
calc((var(--space-md) - var(--space-2xs)) * var(--profile-modal-scale));
--profile-modal-padding-block-end:
calc((var(--space-lg) - var(--space-sm)) * var(--profile-modal-scale));
--profile-modal-nav-gap: calc(var(--space-xs) * var(--profile-modal-scale));
}
.profile-modal__content {
display: flex;
flex: 1 1 auto;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
transform: scale(var(--profile-modal-scale));
transform-origin: top center;
}
.profile-modal__active-profile {
@apply focus-ring flex flex-wrap items-center gap-4 p-4;
}
.profile-modal__active-details {
display: flex;
flex: 1 1 12rem;
flex-direction: column;
gap: var(--space-2xs);
min-inline-size: 0;
}
.profile-modal__active-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-xs);
}
.profile-modal__channel-link {
margin-inline-start: auto;
align-self: center;
white-space: nowrap;
}
.profile-modal__add-profile {
--tw-ring-color: var(--color-ring-alert);
@apply focus-ring relative flex w-full items-center justify-start gap-4 rounded-xl px-5 py-4 text-left text-text transition duration-base ease-in-out;
border: var(--outline-thick-width) solid var(--color-ring-alert);
background-color: var(--surface-panel-hover);
}
.profile-modal__add-profile:hover {
background-color: color-mix(
in srgb,
var(--surface-panel-hover) 88%,
var(--color-ring-alert) 12%
);
}
.profile-modal__add-profile:focus-visible {
background-color: color-mix(
in srgb,
var(--surface-panel-hover) 80%,
var(--color-ring-alert) 20%
);
}
.profile-modal__add-profile-icon {
@apply flex h-10 w-10 items-center justify-center rounded-full;
background-color: color-mix(
in srgb,
var(--color-ring-alert) 18%,
var(--surface-panel)
);
color: var(--color-ring-alert);
}
.profile-modal__body {
@@ -1570,6 +1665,29 @@
min-block-size: 0;
}
.profile-modal__header {
align-items: center;
}
.profile-modal__header-title {
display: flex;
align-items: center;
gap: var(--space-sm);
}
.profile-modal__menu {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.profile-modal__pane-wrapper {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-block-size: 0;
}
.profile-modal__nav {
display: flex;
flex-wrap: wrap;
@@ -1609,7 +1727,23 @@
}
}
@media (max-width: calc(theme("screens.lg") - 0.02px)) {
.profile-modal__layout[data-mobile-view="menu"]
.profile-modal__pane-wrapper {
display: none;
}
.profile-modal__layout[data-mobile-view="pane"]
.profile-modal__menu {
display: none;
}
}
@media (min-width: theme("screens.lg")) {
.profile-modal {
max-block-size: var(--modal-shell-max-height);
}
.profile-modal__body {
gap: calc(var(--profile-modal-gap) + var(--space-2xs));
padding-inline: calc(var(--profile-modal-padding-inline) + var(--space-xs));
@@ -1617,7 +1751,7 @@
.profile-modal__layout {
display: grid;
align-items: flex-start;
align-items: stretch;
block-size: 100%;
gap: calc(var(--profile-modal-gap) + var(--space-2xs));
grid-template-columns:
@@ -1626,6 +1760,7 @@
var(--profile-modal-nav-max-width)
)
minmax(0, 1fr);
grid-auto-rows: minmax(0, 1fr);
}
.profile-modal__nav {
@@ -1636,6 +1771,10 @@
inline-size: var(--profile-modal-nav-max-width);
min-inline-size: var(--profile-modal-nav-min-width);
}
.profile-modal__pane-wrapper {
block-size: 100%;
}
}
/* Torrent helper classes */
+5
View File
@@ -843,6 +843,11 @@ export function createWatchHistoryRenderer(config = {}) {
"aria-checked",
enabled ? "true" : "false"
);
elements.metadataToggle.setAttribute(
"data-enabled",
enabled ? "true" : "false"
);
elements.metadataToggle.dataset.state = enabled ? "on" : "off";
elements.metadataToggle.classList.toggle("bg-info", enabled);
elements.metadataToggle.classList.toggle("border-info", enabled);
elements.metadataToggle.classList.toggle("bg-panel", !enabled);
+304 -15
View File
@@ -670,6 +670,10 @@ export class ProfileModalController {
this.profileModalRoot = null;
this.profileModalPanel = null;
this.profileModalBackdrop = null;
this.profileModalLayout = null;
this.profileModalMenu = null;
this.profileModalPaneWrapper = null;
this.profileModalBackButton = null;
this.profileAvatar = null;
this.profileName = null;
this.profileNpub = null;
@@ -763,6 +767,11 @@ export class ProfileModalController {
this.focusTrapContainer = null;
this.profileSwitcherSelectionPubkey = null;
this.previouslyFocusedElement = null;
this.largeLayoutQuery = null;
this.largeLayoutQueryListener = null;
this.isLargeLayoutActiveFlag = false;
this.mobileViewState = "menu";
this.lastMobileViewState = "menu";
this.setActivePane(this.getActivePane());
this.setWalletPaneBusy(this.isWalletBusy());
this.adminEmptyMessages = new Map();
@@ -797,9 +806,11 @@ export class ProfileModalController {
modalRoot.querySelector(".bv-modal-backdrop") || null;
this.cacheDomReferences();
this.setupLayoutBreakpointObserver();
this.applyModalStackingOverrides();
this.registerEventListeners();
this.selectPane(this.getActivePane());
const preserveMenu = this.isMobileLayoutActive();
this.selectPane(this.getActivePane(), { keepMenuView: preserveMenu });
return true;
}
@@ -810,8 +821,16 @@ export class ProfileModalController {
this.profileModalRoot?.querySelector(".bv-modal__panel") || null;
this.profileModalBackdrop =
this.profileModalRoot?.querySelector(".bv-modal-backdrop") || null;
this.profileModalLayout =
this.profileModalRoot?.querySelector("[data-profile-layout]") || null;
this.profileModalMenu =
this.profileModalRoot?.querySelector("[data-profile-mobile-menu]") || null;
this.profileModalPaneWrapper =
this.profileModalRoot?.querySelector("[data-profile-mobile-pane]") || null;
this.profileModal = this.profileModalRoot;
this.closeButton = document.getElementById("closeProfileModal") || null;
this.profileModalBackButton =
document.getElementById("profileModalBack") || null;
this.logoutButton = document.getElementById("profileLogoutBtn") || null;
this.channelLink = document.getElementById("profileChannelLink") || null;
this.addAccountButton =
@@ -1006,6 +1025,12 @@ export class ProfileModalController {
});
}
if (this.profileModalBackButton instanceof HTMLElement) {
this.profileModalBackButton.addEventListener("click", () => {
this.setMobileView("menu", { focusMenu: true });
});
}
if (this.logoutButton instanceof HTMLElement) {
this.logoutButton.addEventListener("click", async () => {
try {
@@ -1469,7 +1494,211 @@ export class ProfileModalController {
return container;
}
selectPane(name = "account") {
setupLayoutBreakpointObserver() {
if (
typeof window === "undefined" ||
typeof window.matchMedia !== "function"
) {
this.largeLayoutQuery = null;
this.largeLayoutQueryListener = null;
this.isLargeLayoutActiveFlag = false;
return;
}
if (this.largeLayoutQuery && this.largeLayoutQueryListener) {
this.teardownLayoutBreakpointObserver();
}
try {
const query = window.matchMedia("(min-width: 1024px)");
const handler = (event) => {
const matches =
typeof event?.matches === "boolean" ? event.matches : query.matches;
this.handleLayoutBreakpointChange(matches);
};
this.largeLayoutQuery = query;
this.largeLayoutQueryListener = handler;
if (typeof query.addEventListener === "function") {
query.addEventListener("change", handler);
} else if (typeof query.addListener === "function") {
query.addListener(handler);
}
this.handleLayoutBreakpointChange(query.matches);
} catch (error) {
this.largeLayoutQuery = null;
this.largeLayoutQueryListener = null;
devLogger.warn(
"[profileModal] Failed to initialize responsive breakpoint observer:",
error,
);
this.handleLayoutBreakpointChange(false);
}
}
teardownLayoutBreakpointObserver() {
const query = this.largeLayoutQuery;
const handler = this.largeLayoutQueryListener;
if (query && handler) {
if (typeof query.removeEventListener === "function") {
query.removeEventListener("change", handler);
} else if (typeof query.removeListener === "function") {
query.removeListener(handler);
}
}
this.largeLayoutQuery = null;
this.largeLayoutQueryListener = null;
}
handleLayoutBreakpointChange(matches) {
const isLarge = Boolean(matches);
this.isLargeLayoutActiveFlag = isLarge;
if (isLarge) {
this.setMobileView("pane", { skipFocusTrap: false });
return;
}
const targetView = this.lastMobileViewState || this.mobileViewState || "menu";
this.setMobileView(targetView, { skipFocusTrap: false });
}
isLargeLayoutActive() {
return Boolean(this.isLargeLayoutActiveFlag);
}
isMobileLayoutActive() {
return !this.isLargeLayoutActive();
}
focusActiveNavButton() {
const active = this.getActivePane();
const candidates = [];
if (active && this.navButtons[active] instanceof HTMLElement) {
candidates.push(this.navButtons[active]);
}
Object.values(this.navButtons).forEach((button) => {
if (
button instanceof HTMLElement &&
!candidates.includes(button) &&
!button.classList.contains("hidden")
) {
candidates.push(button);
}
});
const target = candidates.find((button) => button instanceof HTMLElement);
if (!target) {
return;
}
window.requestAnimationFrame(() => {
try {
target.focus();
} catch (error) {
devLogger.warn(
"[profileModal] Failed to focus active navigation button:",
error,
);
}
});
}
setMobileView(view = "menu", options = {}) {
const normalizedView = view === "pane" ? "pane" : "menu";
const settings =
options && typeof options === "object" ? options : { skipFocusTrap: false };
const skipFocusTrap = Boolean(settings.skipFocusTrap);
const focusMenu = Boolean(settings.focusMenu);
const layoutElement =
this.profileModalLayout instanceof HTMLElement
? this.profileModalLayout
: null;
const paneWrapper =
this.profileModalPaneWrapper instanceof HTMLElement
? this.profileModalPaneWrapper
: null;
const menuWrapper =
this.profileModalMenu instanceof HTMLElement
? this.profileModalMenu
: null;
const panelElement =
this.profileModalPanel instanceof HTMLElement
? this.profileModalPanel
: null;
const rootElement =
this.profileModalRoot instanceof HTMLElement ? this.profileModalRoot : null;
const backButton =
this.profileModalBackButton instanceof HTMLElement
? this.profileModalBackButton
: null;
const isLarge = this.isLargeLayoutActive();
this.mobileViewState = normalizedView;
if (!isLarge) {
this.lastMobileViewState = normalizedView;
}
if (layoutElement) {
layoutElement.dataset.mobileView = normalizedView;
}
if (panelElement) {
panelElement.dataset.mobileView = normalizedView;
}
if (rootElement) {
rootElement.dataset.mobileView = normalizedView;
}
const menuHidden = !isLarge && normalizedView === "pane";
const paneHidden = !isLarge && normalizedView === "menu";
if (menuWrapper) {
menuWrapper.setAttribute("aria-hidden", menuHidden ? "true" : "false");
if (menuHidden) {
menuWrapper.classList.add("hidden");
menuWrapper.setAttribute("hidden", "");
} else {
menuWrapper.classList.remove("hidden");
menuWrapper.removeAttribute("hidden");
}
}
if (paneWrapper) {
paneWrapper.setAttribute("aria-hidden", paneHidden ? "true" : "false");
if (paneHidden) {
paneWrapper.classList.add("hidden");
paneWrapper.setAttribute("hidden", "");
} else {
paneWrapper.classList.remove("hidden");
paneWrapper.removeAttribute("hidden");
}
}
if (backButton) {
if (isLarge || normalizedView === "menu") {
backButton.classList.add("hidden");
backButton.setAttribute("aria-hidden", "true");
} else {
backButton.classList.remove("hidden");
backButton.setAttribute("aria-hidden", "false");
}
}
if (!skipFocusTrap) {
this.updateFocusTrap();
}
if (focusMenu && normalizedView === "menu") {
this.focusActiveNavButton();
}
return normalizedView;
}
selectPane(name = "account", options = {}) {
const { keepMenuView = false } =
options && typeof options === "object" ? options : {};
const normalized = typeof name === "string" ? name.toLowerCase() : "account";
const previous = this.getActivePane();
const availableKeys = Object.keys(this.panes).filter((key) => {
@@ -1522,7 +1751,9 @@ export class ProfileModalController {
});
this.setActivePane(target);
this.updateFocusTrap();
const isMobile = this.isMobileLayoutActive();
const shouldStayInMenu = keepMenuView && isMobile;
this.setMobileView(shouldStayInMenu ? "menu" : "pane");
if (target === "history") {
void this.populateProfileWatchHistory();
@@ -2824,6 +3055,44 @@ export class ProfileModalController {
}
}
normalizeAdminListEntries(entries) {
const collected = [];
const seen = new Set();
const append = (value) => {
if (typeof value !== "string") {
return;
}
const trimmed = value.trim();
if (!trimmed || seen.has(trimmed)) {
return;
}
seen.add(trimmed);
collected.push(trimmed);
};
if (Array.isArray(entries)) {
entries.forEach(append);
} else if (entries && typeof entries?.[Symbol.iterator] === "function") {
for (const entry of entries) {
append(entry);
}
} else if (entries && typeof entries === "object") {
Object.values(entries).forEach(append);
}
try {
collected.sort((a, b) => a.localeCompare(b));
} catch (error) {
devLogger.warn(
"[profileModal] Failed to sort admin list entries, using fallback order.",
error,
);
}
return collected;
}
renderAdminList(listEl, emptyEl, entries, options = {}) {
if (!(listEl instanceof HTMLElement) || !(emptyEl instanceof HTMLElement)) {
return;
@@ -2840,17 +3109,31 @@ export class ProfileModalController {
const entriesNeedingFetch = new Set();
listEl.innerHTML = "";
const values = Array.isArray(entries) ? [...entries] : [];
values.sort((a, b) => a.localeCompare(b));
const values = this.normalizeAdminListEntries(entries);
const toggleHiddenState = (element, shouldHide) => {
if (!(element instanceof HTMLElement)) {
return;
}
if (shouldHide) {
element.classList.add("hidden");
element.setAttribute("hidden", "");
} else {
element.classList.remove("hidden");
element.removeAttribute("hidden");
}
};
if (!values.length) {
emptyEl.classList.remove("hidden");
listEl.classList.add("hidden");
toggleHiddenState(emptyEl, false);
toggleHiddenState(listEl, true);
return;
}
emptyEl.classList.add("hidden");
listEl.classList.remove("hidden");
toggleHiddenState(emptyEl, true);
toggleHiddenState(listEl, false);
values.forEach((npub) => {
const item = document.createElement("li");
@@ -2931,11 +3214,15 @@ export class ProfileModalController {
}
const isSuperAdmin = this.services.accessControl.isSuperAdmin(actorNpub);
const editors = this.services.accessControl
.getEditors()
.filter((npub) => npub && npub !== this.adminSuperNpub);
const whitelist = this.services.accessControl.getWhitelist();
const blacklist = this.services.accessControl.getBlacklist();
const editors = this.normalizeAdminListEntries(
this.services.accessControl.getEditors(),
).filter((npub) => npub && npub !== this.adminSuperNpub);
const whitelist = this.normalizeAdminListEntries(
this.services.accessControl.getWhitelist(),
);
const blacklist = this.normalizeAdminListEntries(
this.services.accessControl.getBlacklist(),
);
this.renderAdminList(
this.adminModeratorList,
@@ -4197,7 +4484,8 @@ export class ProfileModalController {
modalRoot.classList.remove("hidden");
modalRoot.setAttribute("aria-hidden", "false");
this.setGlobalModalState("profile", true);
this.selectPane(pane);
const preserveMenu = this.isMobileLayoutActive();
this.selectPane(pane, { keepMenuView: preserveMenu });
const focusTarget =
this.focusableElements[0] ||
@@ -4225,6 +4513,7 @@ export class ProfileModalController {
modalElement.classList.add("hidden");
modalElement.setAttribute("aria-hidden", "true");
this.setGlobalModalState("profile", false);
this.setMobileView("menu", { skipFocusTrap: true });
if (this.boundKeydown && this.focusTrapContainer) {
this.focusTrapContainer.removeEventListener(
+251 -3
View File
@@ -3,6 +3,8 @@ import { strict as assert } from 'node:assert';
import { readFile } from 'node:fs/promises';
import { JSDOM } from 'jsdom';
import ProfileModalController from '../js/ui/profileModalController.js';
import { createWatchHistoryRenderer } from '../js/historyView.js';
import watchHistoryService from '../js/watchHistoryService.js';
import { formatShortNpub } from '../js/utils/formatters.js';
import { resetRuntimeFlags } from '../js/constants.js';
import { applyDesignSystemAttributes } from '../js/designSystem.js';
@@ -24,6 +26,65 @@ async function waitForAnimationFrame(window, cycles = 1) {
}
}
function createMatchMediaList(query, initialMatches = false) {
let matches = Boolean(initialMatches);
const listeners = new Set();
const notify = () => {
const event = { matches, media: query, type: 'change' };
listeners.forEach((listener) => {
try {
listener(event);
} catch {}
});
};
return {
get matches() {
return matches;
},
set matches(value) {
matches = Boolean(value);
},
media: query,
addEventListener(type, listener) {
if (type === 'change' && typeof listener === 'function') {
listeners.add(listener);
}
},
removeEventListener(type, listener) {
if (type === 'change' && typeof listener === 'function') {
listeners.delete(listener);
}
},
addListener(listener) {
if (typeof listener === 'function') {
listeners.add(listener);
}
},
removeListener(listener) {
if (typeof listener === 'function') {
listeners.delete(listener);
}
},
dispatchEvent(event) {
if (!event || event.type !== 'change') {
return true;
}
listeners.forEach((listener) => {
try {
listener(event);
} catch {}
});
return true;
},
setMatches(value) {
matches = Boolean(value);
notify();
},
};
}
beforeEach(() => {
dom = new JSDOM(
'<!DOCTYPE html><html><body><div id="modalMount"></div></body></html>',
@@ -53,6 +114,14 @@ beforeEach(() => {
window.confirm = () => true;
global.confirm = window.confirm;
window.__matchMediaMocks = [];
window.matchMedia = (query) => {
const mql = createMatchMediaList(query, false);
window.__matchMediaMocks.push(mql);
return mql;
};
global.matchMedia = window.matchMedia;
container = document.getElementById('modalMount');
global.fetch = async (resource) => {
@@ -87,11 +156,16 @@ afterEach(() => {
delete global.HTMLElement;
delete global.document;
delete global.window;
delete global.matchMedia;
if (dom) {
dom.window.close();
dom = null;
}
if (typeof window !== 'undefined') {
delete window.matchMedia;
delete window.__matchMediaMocks;
}
container = null;
});
@@ -291,6 +365,74 @@ for (const _ of [0]) {
},
);
test('Profile modal toggles mobile menu and pane views', async (t) => {
const controller = createController();
await controller.load();
applyDesignSystemAttributes(document);
let cleanupRan = false;
const cleanup = () => {
try {
controller.hide({ silent: true });
} catch {}
cleanupRan = true;
};
try {
await controller.show('account');
await waitForAnimationFrame(window, 2);
const layout = document.querySelector('[data-profile-layout]');
const menuWrapper = document.querySelector('[data-profile-mobile-menu]');
const paneWrapper = document.querySelector('[data-profile-mobile-pane]');
const backButton = document.getElementById('profileModalBack');
assert.ok(layout && menuWrapper && paneWrapper && backButton);
assert.equal(layout?.dataset.mobileView, 'menu');
assert.equal(menuWrapper?.getAttribute('aria-hidden'), 'false');
assert.equal(menuWrapper?.classList.contains('hidden'), false);
assert.equal(menuWrapper?.hasAttribute('hidden'), false);
assert.equal(paneWrapper?.getAttribute('aria-hidden'), 'true');
assert.equal(paneWrapper?.classList.contains('hidden'), true);
assert.equal(paneWrapper?.hasAttribute('hidden'), true);
assert.equal(backButton?.classList.contains('hidden'), true);
controller.selectPane('relays');
await waitForAnimationFrame(window, 1);
assert.equal(layout?.dataset.mobileView, 'pane');
assert.equal(menuWrapper?.getAttribute('aria-hidden'), 'true');
assert.equal(menuWrapper?.classList.contains('hidden'), true);
assert.equal(menuWrapper?.hasAttribute('hidden'), true);
assert.equal(paneWrapper?.getAttribute('aria-hidden'), 'false');
assert.equal(paneWrapper?.classList.contains('hidden'), false);
assert.equal(paneWrapper?.hasAttribute('hidden'), false);
assert.equal(backButton?.classList.contains('hidden'), false);
backButton?.click();
await waitForAnimationFrame(window, 1);
assert.equal(layout?.dataset.mobileView, 'menu');
assert.equal(menuWrapper?.getAttribute('aria-hidden'), 'false');
assert.equal(menuWrapper?.classList.contains('hidden'), false);
assert.equal(menuWrapper?.hasAttribute('hidden'), false);
assert.equal(paneWrapper?.getAttribute('aria-hidden'), 'true');
assert.equal(paneWrapper?.classList.contains('hidden'), true);
assert.equal(paneWrapper?.hasAttribute('hidden'), true);
assert.equal(backButton?.classList.contains('hidden'), true);
} finally {
cleanup();
}
t.after(() => {
if (!cleanupRan) {
cleanup();
}
resetRuntimeFlags();
});
});
test('Profile modal uses abbreviated npub display', async () => {
const sampleProfiles = [
{
@@ -454,9 +596,9 @@ test('admin mutations invoke accessControl stubs and update admin DOM', async ()
},
canEditAdminLists: () => true,
isSuperAdmin: () => true,
getEditors: () => ['npub1moderator'],
getWhitelist: () => ['npub1allow'],
getBlacklist: () => ['npub1block'],
getEditors: () => new Set(['npub1moderator']),
getWhitelist: () => new Set(['npub1allow']),
getBlacklist: () => new Set(['npub1block']),
addModerator: async (actor, target) => {
accessEvents.push(['addModerator', actor, target]);
return { ok: true };
@@ -504,10 +646,13 @@ test('admin mutations invoke accessControl stubs and update admin DOM', async ()
assert.equal(controller.navButtons.admin.classList.contains('hidden'), false);
assert.equal(controller.moderatorSection.classList.contains('hidden'), false);
assert.equal(controller.adminModeratorList.querySelectorAll('li').length, 1);
assert.equal(controller.adminModeratorList.hasAttribute('hidden'), false);
assert.equal(
controller.whitelistList.querySelectorAll('button').length > 0,
true,
);
assert.equal(controller.whitelistList.classList.contains('hidden'), false);
assert.equal(controller.whitelistList.hasAttribute('hidden'), false);
controller.moderatorInput.value = 'npub1newmoderator';
await controller.handleAddModerator();
@@ -616,3 +761,106 @@ test('history pane lazily initializes the watch history renderer', async () => {
assert.equal(controller.profileHistoryRenderer, null);
assert.equal(controller.boundProfileHistoryVisibility, null);
});
test('history metadata toggle updates stored preference', async (t) => {
let storedPreference = true;
let clearCalls = 0;
const originalSetPreference = watchHistoryService.setMetadataPreference;
const originalShouldStore = watchHistoryService.shouldStoreMetadata;
const originalClearMetadata = watchHistoryService.clearLocalMetadata;
const originalSubscribe = watchHistoryService.subscribe;
const originalIsEnabled = watchHistoryService.isEnabled;
const originalSupportsLocal = watchHistoryService.supportsLocalHistory;
const originalIsLocalOnly = watchHistoryService.isLocalOnly;
watchHistoryService.setMetadataPreference = (value) => {
storedPreference = value !== false;
};
watchHistoryService.shouldStoreMetadata = () => storedPreference;
watchHistoryService.clearLocalMetadata = () => {
clearCalls += 1;
};
watchHistoryService.subscribe = () => () => {};
watchHistoryService.isEnabled = () => true;
watchHistoryService.supportsLocalHistory = () => true;
watchHistoryService.isLocalOnly = () => false;
t.after(() => {
watchHistoryService.setMetadataPreference = originalSetPreference;
watchHistoryService.shouldStoreMetadata = originalShouldStore;
watchHistoryService.clearLocalMetadata = originalClearMetadata;
watchHistoryService.subscribe = originalSubscribe;
watchHistoryService.isEnabled = originalIsEnabled;
watchHistoryService.supportsLocalHistory = originalSupportsLocal;
watchHistoryService.isLocalOnly = originalIsLocalOnly;
});
const controller = createController({
createWatchHistoryRenderer: (config) =>
createWatchHistoryRenderer({
...config,
fetchHistory: async () => ({ items: [], metadata: {} }),
snapshot: async () => ({}),
}),
services: {
nostrClient: { sessionActor: { pubkey: defaultActorHex } },
getCurrentUserNpub: () => defaultActorNpub,
},
});
await controller.load();
applyDesignSystemAttributes(document);
controller.refreshAdminPaneState = async () => {};
controller.populateProfileRelays = () => {};
controller.populateBlockedList = () => {};
controller.refreshWalletPaneState = () => {};
controller.setActivePubkey(defaultActorHex);
let cleanupRan = false;
const cleanup = () => {
try {
controller.hide({ silent: true });
} catch {}
cleanupRan = true;
};
try {
await controller.show('history');
await waitForAnimationFrame(window, 3);
const toggle = document.getElementById('profileHistoryMetadataToggle');
assert.ok(toggle);
assert.equal(toggle.getAttribute('aria-checked'), 'true');
assert.equal(toggle.getAttribute('data-enabled'), 'true');
toggle.dispatchEvent(
new window.MouseEvent('click', { bubbles: true, cancelable: true }),
);
await waitForAnimationFrame(window, 2);
assert.equal(storedPreference, false);
assert.equal(toggle.getAttribute('aria-checked'), 'false');
assert.equal(toggle.getAttribute('data-enabled'), 'false');
assert.equal(clearCalls, 1);
toggle.dispatchEvent(
new window.MouseEvent('click', { bubbles: true, cancelable: true }),
);
await waitForAnimationFrame(window, 2);
assert.equal(storedPreference, true);
assert.equal(toggle.getAttribute('aria-checked'), 'true');
assert.equal(toggle.getAttribute('data-enabled'), 'true');
} finally {
cleanup();
}
t.after(() => {
if (!cleanupRan) {
cleanup();
}
});
});