mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-30 09:22:24 +00:00
refactor: adopt tokenized color utilities
This commit is contained in:
1 parent
3f8ad80db5
commit
19d971b49d
20 files changed
+798
-640
No files matched your search
@@ -1,3 +1,3 @@
|
||||
:root{--color-page:#fff;--color-page-alt:#f9fafb;--color-surface:#0f172a;--color-surface-alt:#111827;--color-bg:var(--color-surface);--color-card:#1e293b;--color-card-private:#3b1d74;--color-card-critical:#3f0b13;--color-panel:#1f2937;--color-panel-hover:#334155;--color-border:#374151;--color-border-subtle:#d1d5db;--color-primary:#fe0032;--color-secondary:#ff93a5;--color-info:#60a5fa;--color-info-strong:#2563eb;--color-info-pressed:#1d4ed8;--color-muted-strong:#6b7280;--color-text:#f8fafc;--color-text-strong:#0f172a;--color-text-muted:var(--color-muted);--color-muted:#94a3b8;--color-muted-alt:#9ca3af;--color-subtle-text:#e2e8f0;--color-subtle:#e5e7eb;--color-critical:#7f1d1d;--color-critical-strong:#991b1b;--color-warning:#facc15;--color-warning-strong:#d97706;--color-warning-surface:rgba(146,64,14,.24);--color-neutral-700:#3f3f46;--color-neutral-600:#52525b;--color-white:#fff;--color-black:#000;--color-overlay:#1a2234;--color-overlay-black:rgba(0,0,0,.9);--color-overlay-muted:rgba(15,23,42,.55);--color-overlay-strong:rgba(15,23,42,.75);--color-overlay-darker:rgba(15,23,42,.88);--color-overlay-panel:rgba(31,41,55,.9);--color-overlay-panel-soft:rgba(31,41,55,.75);--color-overlay-panel-tint:rgba(31,41,55,.5);--color-overlay-panel-glass:rgba(30,41,59,.85);--color-overlay-veil:rgba(11,17,32,.96);--color-overlay-banner:rgba(51,65,85,.95);--color-overlay-panel-gradient-start:rgba(30,41,59,.92);--color-overlay-panel-gradient-end:rgba(15,23,42,.92);--color-surface-tint-subtle:rgba(148,163,184,.08);--color-border-translucent:rgba(148,163,184,.18);--color-border-translucent-strong:rgba(148,163,184,.25);--color-border-translucent-bright:rgba(148,163,184,.32);--color-border-translucent-stronger:rgba(148,163,184,.35);--color-border-translucent-strongest:rgba(148,163,184,.4);--color-border-translucent-glow:rgba(148,163,184,.55);--color-border-translucent-medium:rgba(148,163,184,.2);--color-border-neutral-soft:rgba(229,231,235,.35);--color-border-info-soft:rgba(96,165,250,.35);--color-border-sidebar-strong:rgba(55,65,81,.9);--color-ring-private:rgba(168,85,247,.55);--color-ring-private-hover:rgba(192,132,252,.65);--color-ring-critical:rgba(239,68,68,.4);--color-ring-critical-soft:rgba(239,68,68,.35);--color-ring-critical-hover:rgba(239,68,68,.45);--color-ring-critical-strong:rgba(239,68,68,.55);--color-ring-alert:rgba(220,38,38,.6);--color-gradient-visibility-start:rgba(129,140,248,.4);--color-gradient-visibility-end:rgba(192,132,252,.32);--color-white-soft:hsla(0,0%,100%,.1);--color-white-softer:hsla(0,0%,100%,.3);--color-white-softest:hsla(0,0%,100%,.4);--color-shadow-floating:rgba(8,11,30,.55);--color-shadow-elevated:rgba(8,11,30,.45);--color-shadow-panel:rgba(15,23,42,.35);--color-shadow-notification:rgba(15,23,42,.3);--color-shadow-sidebar:rgba(0,0,0,.45);--color-divider-strong:rgba(15,23,42,.35);--color-text-frosted-strong:rgba(226,232,240,.94);--color-text-frosted:rgba(226,232,240,.9);--color-text-frosted-soft:rgba(226,232,240,.82);--space-2xs:0.25rem;--space-xs:0.5rem;--space-sm:0.75rem;--space-md:1rem;--space-lg:1.5rem;--space-xl:2rem;--space-2xl:3rem;--radius-sm:0.25rem;--radius-md:0.5rem;--radius-lg:0.75rem;--radius-xl:1rem;--radius-full:9999px;--shadow-sm:0 0.0625rem 0.125rem 0 rgb(0 0 0/5%);--shadow-md:0 0.25rem 0.375rem -0.0625rem rgba(0,0,0,.1);--shadow-lg:0 0.625rem 0.9375rem -0.1875rem rgba(0,0,0,.1);--shadow-modal:0 1.25rem 2.5rem -0.375rem rgba(15,23,42,.55);--font-family-sans:"Inter","Segoe UI",system-ui,-apple-system,sans-serif;--font-family-mono:"Fira Code","SFMono-Regular",ui-monospace,monospace;--font-size-xs:0.75rem;--font-size-sm:0.875rem;--font-size-base:1rem;--font-size-lg:1.125rem;--font-size-xl:1.5rem;--font-size-2xl:2rem;--line-height-tight:1.1;--line-height-snug:1.25;--line-height-base:1.5;--line-height-relaxed:1.75;--motion-duration-fast:120ms;--motion-duration-base:160ms;--motion-duration-slow:280ms;--motion-ease-out:cubic-bezier(0.16,1,0.3,1);--motion-ease-in-out:cubic-bezier(0.4,0,0.2,1);--modal-motion-duration:var(--motion-duration-base)}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tLine truncated
|
||||
:root{--color-page:#fff;--color-page-alt:#f9fafb;--color-surface:#0f172a;--color-surface-alt:#111827;--color-bg:var(--color-surface);--color-card:#1e293b;--color-card-private:#3b1d74;--color-card-critical:#3f0b13;--color-panel:#1f2937;--color-panel-hover:#334155;--color-border:#374151;--color-border-subtle:#d1d5db;--color-primary:#fe0032;--color-secondary:#ff93a5;--color-info:#60a5fa;--color-info-strong:#2563eb;--color-info-pressed:#1d4ed8;--color-muted-strong:#6b7280;--color-text:#f8fafc;--color-text-strong:#0f172a;--color-text-muted:var(--color-muted);--color-muted:#94a3b8;--color-muted-alt:#9ca3af;--color-subtle-text:#e2e8f0;--color-subtle:#e5e7eb;--color-critical:#7f1d1d;--color-critical-strong:#991b1b;--color-warning:#facc15;--color-warning-strong:#d97706;--color-warning-surface:rgba(146,64,14,.24);--color-neutral-700:#3f3f46;--color-neutral-600:#52525b;--color-white:#fff;--color-black:#000;--color-overlay:#1a2234;--color-overlay-black:rgba(0,0,0,.9);--color-overlay-muted:rgba(15,23,42,.55);--color-overlay-strong:rgba(15,23,42,.75);--color-overlay-darker:rgba(15,23,42,.88);--color-overlay-panel:rgba(31,41,55,.9);--color-overlay-panel-soft:rgba(31,41,55,.75);--color-overlay-panel-tint:rgba(31,41,55,.5);--color-overlay-panel-glass:rgba(30,41,59,.85);--color-overlay-veil:rgba(11,17,32,.96);--color-overlay-banner:rgba(51,65,85,.95);--color-overlay-panel-gradient-start:rgba(30,41,59,.92);--color-overlay-panel-gradient-end:rgba(15,23,42,.92);--color-surface-tint-subtle:rgba(148,163,184,.08);--color-border-translucent:rgba(148,163,184,.18);--color-border-translucent-strong:rgba(148,163,184,.25);--color-border-translucent-bright:rgba(148,163,184,.32);--color-border-translucent-stronger:rgba(148,163,184,.35);--color-border-translucent-strongest:rgba(148,163,184,.4);--color-border-translucent-glow:rgba(148,163,184,.55);--color-border-translucent-medium:rgba(148,163,184,.2);--color-border-neutral-soft:rgba(229,231,235,.35);--color-border-info-soft:rgba(96,165,250,.35);--color-border-sidebar-strong:rgba(55,65,81,.9);--color-ring-private:rgba(168,85,247,.55);--color-ring-private-hover:rgba(192,132,252,.65);--color-ring-critical:rgba(239,68,68,.4);--color-ring-critical-soft:rgba(239,68,68,.35);--color-ring-critical-hover:rgba(239,68,68,.45);--color-ring-critical-strong:rgba(239,68,68,.55);--color-ring-alert:rgba(220,38,38,.6);--color-gradient-visibility-start:rgba(129,140,248,.4);--color-gradient-visibility-end:rgba(192,132,252,.32);--color-white-soft:hsla(0,0%,100%,.1);--color-white-softer:hsla(0,0%,100%,.3);--color-white-softest:hsla(0,0%,100%,.4);--color-shadow-floating:rgba(8,11,30,.55);--color-shadow-elevated:rgba(8,11,30,.45);--color-shadow-panel:rgba(15,23,42,.35);--color-shadow-notification:rgba(15,23,42,.3);--color-shadow-sidebar:rgba(0,0,0,.45);--color-divider-strong:rgba(15,23,42,.35);--color-text-frosted-strong:rgba(226,232,240,.94);--color-text-frosted:rgba(226,232,240,.9);--color-text-frosted-soft:rgba(226,232,240,.82);--color-blog-transparent:transparent;--color-blog-cyan-500:#06b6d4;--color-blog-sky-500:#0bf;--color-blog-teal-600:#0e7490;--color-blog-green-800:#14532d;--color-blog-green-700:#15803d;--color-blog-teal-700:#164e63;--color-blog-gray-900:#171717;--color-blog-coffee-900:#1c1917;--color-blog-green-500:#22c55e;--color-blog-gray-800:#2b2b2b;--color-blog-gray-700:#3b3b3b;--color-blog-teal-accent:#3e8b8a;--color-blog-gray-650:#404040;--color-blog-brown-700:#44403c;--color-blog-purple-900:#581c87;--color-blog-gray-500:#666;--color-blog-gray-450:#737373;--color-blog-gray-440:#747474;--color-blog-brown-500:#78716c;--color-blog-rust-700:#7c2d12;--color-blog-purple-700:#7e22ce;--color-blog-purple-accent:#92379c;--color-blog-purple-muted:#96609c;--color-blog-gray-300:#999;--color-blog-purple-500:#a855f7;--color-blog-gray-275:#a9a9a9;--color-blog-red-600:#b91c1c;--color-blog-rust-600:#c2410c;--color-blog-error:#ca1717;--color-blog-gray-220:#cbcbcb;--color-blog-gray-210:#ccc;--color-blog-gray-200:#dedede;--color-blog-amber-500:#e4984d;--color-blog-gray-190:#e0e0e0;--color-blog-orange-600:#e35428;--color-blog-gray-180:#e9e9e9;--color-blog-gray-170:#ebebeb;--color-blog-red-500:#ef4444;--color-blog-amber-400:#f6ad55;--color-blog-neutral-100:#f5f5f5;--color-blog-neutral-120:#f6f6f6;--color-blog-orange-500:#f97316;--color-blog-neutral-50:#fdfdfd;--space-2xs:0.25rem;--space-xs:0.5rem;--space-sm:0.75rem;--space-md:1rem;--space-lg:1.5rem;--space-xl:2rem;--space-2xl:3rem;--radius-sm:0.25rem;--radius-md:0.5rem;--radius-lg:0.75rem;--radius-xl:1rem;--radius-full:9999px;--shadow-sm:0 0.0625rem 0.125rem 0 rgb(0 0 0/5%);--shadow-md:0 0.25rem 0.375rem -0.0625rem rgba(0,0,0,.1);--shadow-lg:0 0.625rem 0.9375rem -0.1875rem rgba(0,0,0,.1);--shadow-modal:0 1.25rem 2.5rem -0.375rem rgba(15,23,42,.55);--font-family-sans:"Inter","Segoe UI",system-ui,-apple-system,sans-serif;--font-family-mono:"Fira Code","SFMono-Regular",ui-monospace,monospace;--font-size-xs:0.75rem;--font-size-sm:0.875rem;--font-size-base:1rem;--font-size-lLine truncated
|
||||
|
||||
/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family-sans);font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--font-family-mono);font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.focus-ring{outline:2px solid transparent;outline-offset:2px}.focus-ring:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:var(--color-info);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--color-surface);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-fast);transition-timing-function:cubic-bezier(0,0,.2,1)}.btn{outline:2px solid transparent;outline-offset:2px}.btn:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:var(--color-info);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--color-surface);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-fast);transition-timing-function:cubic-bezier(0,0,.2,1)}.btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;border-radius:var(--radius-lg);border-width:1px;border-color:transparent;background-color:var(--color-primary);padding:.5rem 1rem;font-size:var(--font-size-sm);line-height:var(--line-height-snug);font-weight:600;text-transform:uppercase;letter-spacing:.025em;color:var(--color-white);--tw-shadow:var(--shadow-md);--tw-shadow-colored:var(--shadow-md);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-duration:var(--motion-duration-base);transition-timing-funLine truncated
|
||||
/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family-sans);font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--font-family-mono);font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.focus-ring{outline:2px solid transparent;outline-offset:2px}.focus-ring:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:var(--color-info);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--color-surface);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-fast);transition-timing-function:cubic-bezier(0,0,.2,1)}.popover{position:relative;display:inline-flex}.popover__panel{outline:2px solid transparent;outline-offset:2px}.popover__panel:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);--tw-ring-color:var(--color-info);--tw-ring-opacity:0.7;--tw-ring-offset-width:2px;--tw-ring-offset-color:var(--color-surface);transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:var(--motion-duration-fast);transition-timing-function:cubic-bezier(0,0,.2,1)}.popover__panel{pointer-events:none;position:absolute;left:0;top:100%;z-index:70;margin-top:.5rem;min-width:12rem;transform-origin:top;--tw-scale-x:.95;--tw-scale-y:.95;border-radius:var(--radius-lg);border-width:1px;border-color:var(--color-border);--tw-border-opacity:0.4;background-color:var(--color-panel);padding:.75rem 1rem;font-size:var(--font-size-sm);line-height:var(--line-height-snug);color:var(--color-text);opacity:0;--tw-shadow:var(--shadow-lg);--tw-shadow-colored:var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)Line truncated
|
||||
+25
-23
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -81,11 +81,11 @@
|
||||
|
||||
<div
|
||||
id="app"
|
||||
class="px-4 py-8 min-h-screen flex flex-col fade-in"
|
||||
class="fade-in flex min-h-screen flex-col px-4 py-8"
|
||||
data-sidebar-state="collapsed"
|
||||
>
|
||||
<!-- Header -->
|
||||
<header class="mb-8 flex items-center w-full">
|
||||
<header class="mb-8 flex w-full items-center">
|
||||
<!-- Mobile hamburger button (hidden on md+) -->
|
||||
<!-- New button -->
|
||||
<!--
|
||||
@@ -95,7 +95,7 @@
|
||||
-->
|
||||
<button
|
||||
id="mobileMenuBtn"
|
||||
class="md:hidden ml-2 mr-4 flex items-center justify-center w-10 h-10 rounded-full bg-transparent hover:bg-transparent focus-ring z-[60] fade-in"
|
||||
class="focus-ring fade-in z-[60] ml-2 mr-4 flex h-10 w-10 items-center justify-center rounded-full bg-transparent hover:bg-transparent md:hidden"
|
||||
type="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="sidebar"
|
||||
@@ -104,7 +104,7 @@
|
||||
<img
|
||||
src="assets/svg/mobile-sidebar-menu-icon.svg"
|
||||
alt="Mobile Sidebar Menu Icon"
|
||||
class="w-6 h-6"
|
||||
class="h-6 w-6"
|
||||
/>
|
||||
</button>
|
||||
<!-- Logo -->
|
||||
@@ -125,7 +125,9 @@
|
||||
|
||||
<!-- Header action buttons share the fade-in treatment for consistency. -->
|
||||
|
||||
<div class="ml-auto flex items-center space-x-4 fade-in fade-in-delay-200">
|
||||
<div
|
||||
class="fade-in fade-in-delay-200 ml-auto flex items-center space-x-4"
|
||||
>
|
||||
<!-- Login Button -->
|
||||
<button
|
||||
id="loginButton"
|
||||
@@ -134,14 +136,14 @@
|
||||
<img
|
||||
src="assets/svg/default-profile.svg"
|
||||
alt="Profile Icon"
|
||||
class="w-9 h-9"
|
||||
class="h-9 w-9"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<!-- Upload Button (hidden by default) -->
|
||||
<button
|
||||
id="uploadButton"
|
||||
class="hidden btn focus-ring h-12 w-12 rounded-full p-0 text-xl font-bold leading-none"
|
||||
class="btn focus-ring hidden h-12 w-12 rounded-full p-0 text-xl font-bold leading-none"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
@@ -149,14 +151,14 @@
|
||||
<!-- Profile Button (hidden by default) -->
|
||||
<button
|
||||
id="profileButton"
|
||||
class="hidden btn-ghost focus-ring h-12 w-12 rounded-full p-0"
|
||||
class="btn-ghost focus-ring hidden h-12 w-12 rounded-full p-0"
|
||||
>
|
||||
<div class="w-10 h-10 rounded-full overflow-hidden">
|
||||
<div class="h-10 w-10 overflow-hidden rounded-full">
|
||||
<img
|
||||
id="profileAvatar"
|
||||
src="assets/svg/default-profile.svg"
|
||||
alt="Profile"
|
||||
class="w-full h-full object-cover"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
@@ -173,13 +175,13 @@
|
||||
<!-- Error Container -->
|
||||
<div
|
||||
id="errorContainer"
|
||||
class="hidden bg-red-100 text-red-900 p-4 rounded-md mb-4"
|
||||
class="bg-critical/10 mb-4 hidden rounded-md p-4 text-critical-strong"
|
||||
></div>
|
||||
|
||||
<!-- Status Container -->
|
||||
<div
|
||||
id="statusContainer"
|
||||
class="hidden status-banner"
|
||||
class="status-banner hidden"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
@@ -190,12 +192,12 @@
|
||||
<!-- Success Container -->
|
||||
<div
|
||||
id="successContainer"
|
||||
class="hidden bg-green-100 text-green-900 p-4 rounded-md mb-4"
|
||||
class="bg-info/10 mb-4 hidden rounded-md p-4 text-info-strong"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic views (like most-recent-videos.html, etc.) -->
|
||||
<main id="viewContainer" class="flex-grow mb-8"></main>
|
||||
<main id="viewContainer" class="mb-8 flex-grow"></main>
|
||||
|
||||
<!-- Modal Container (external modals) -->
|
||||
<div id="modalContainer"></div>
|
||||
@@ -204,8 +206,8 @@
|
||||
|
||||
<!-- Tagline fade matches the cards. -->
|
||||
|
||||
<div class="text-center mb-8 fade-in fade-in-delay-300">
|
||||
<h2 class="text-2xl font-bold text-gray-500 tracking-wide">
|
||||
<div class="fade-in fade-in-delay-300 mb-8 text-center">
|
||||
<h2 class="text-2xl font-bold tracking-wide text-muted-strong">
|
||||
seed. zap. subscribe.
|
||||
</h2>
|
||||
</div>
|
||||
@@ -213,12 +215,12 @@
|
||||
|
||||
<!-- Additional Scripts -->
|
||||
<!-- Bootstraps nostr-tools before other modules rely on nostrToolsReady -->
|
||||
<script type="module" src="js/nostrToolsBootstrap.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/config.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/lists.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/accessControl.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/webtorrent.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/nostr.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/nostrToolsBootstrap.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/config.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/lists.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/accessControl.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/webtorrent.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/nostr.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/viewManager.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/bootstrap.js?v=2024.10.29"></script>
|
||||
<script type="module" src="js/disclaimer.js?v=2024.10.29"></script>
|
||||
|
||||
+181
-124
@@ -3,7 +3,7 @@
|
||||
import {
|
||||
nostrClient,
|
||||
DEFAULT_RELAY_URLS,
|
||||
convertEventToVideo as sharedConvertEventToVideo,
|
||||
convertEventToVideo as sharedConvertEventToVideo
|
||||
} from "./nostr.js";
|
||||
import { subscriptions } from "./subscriptions.js"; // <-- NEW import
|
||||
import { attachHealthBadges } from "./gridHealth.js";
|
||||
@@ -25,16 +25,19 @@ import {
|
||||
normalizeLightningAddressKey,
|
||||
rememberLightningMetadata,
|
||||
setCachedPlatformLightningAddress,
|
||||
validateInvoiceAmount,
|
||||
validateInvoiceAmount
|
||||
} from "./payments/zapSharedState.js";
|
||||
import { splitAndZap } from "./payments/zapSplit.js";
|
||||
import {
|
||||
resolveLightningAddress,
|
||||
fetchPayServiceData,
|
||||
requestInvoice,
|
||||
requestInvoice
|
||||
} from "./payments/lnurl.js";
|
||||
import { getPlatformLightningAddress } from "./payments/platformAddress.js";
|
||||
import { ensureWallet, sendPayment as sendWalletPayment } from "./payments/nwcClient.js";
|
||||
import {
|
||||
ensureWallet,
|
||||
sendPayment as sendWalletPayment
|
||||
} from "./payments/nwcClient.js";
|
||||
|
||||
const getApp = () => getApplication();
|
||||
|
||||
@@ -57,8 +60,8 @@ const summarizeZapTracker = (tracker) =>
|
||||
typeof entry?.error?.message === "string"
|
||||
? entry.error.message
|
||||
: entry?.error
|
||||
? String(entry.error)
|
||||
: undefined,
|
||||
? String(entry.error)
|
||||
: undefined
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
@@ -84,21 +87,21 @@ function logZapError(stage, details = {}, error) {
|
||||
details.walletSettings.type ||
|
||||
details.walletSettings.name ||
|
||||
details.walletSettings.client ||
|
||||
undefined,
|
||||
undefined
|
||||
}
|
||||
: undefined,
|
||||
shares: details?.context?.shares
|
||||
? {
|
||||
total: details.context.shares.total,
|
||||
creator: details.context.shares.creatorShare,
|
||||
platform: details.context.shares.platformShare,
|
||||
platform: details.context.shares.platformShare
|
||||
}
|
||||
: undefined,
|
||||
tracker: summarizeZapTracker(details?.tracker),
|
||||
retryAttempt:
|
||||
Number.isInteger(details?.retryAttempt) && details.retryAttempt >= 0
|
||||
? details.retryAttempt
|
||||
: undefined,
|
||||
: undefined
|
||||
};
|
||||
console.error("[zap] Channel zap failure", summary, error);
|
||||
}
|
||||
@@ -208,7 +211,10 @@ function setChannelZapVisibility(visible) {
|
||||
}
|
||||
|
||||
function getChannelShareButton() {
|
||||
if (cachedChannelShareButton && !document.body.contains(cachedChannelShareButton)) {
|
||||
if (
|
||||
cachedChannelShareButton &&
|
||||
!document.body.contains(cachedChannelShareButton)
|
||||
) {
|
||||
cachedChannelShareButton = null;
|
||||
}
|
||||
if (!cachedChannelShareButton) {
|
||||
@@ -467,30 +473,30 @@ function setZapStatus(message, tone = "neutral") {
|
||||
const normalizedTone = typeof tone === "string" ? tone : "neutral";
|
||||
statusEl.textContent = message || "";
|
||||
statusEl.classList.remove(
|
||||
"text-gray-300",
|
||||
"text-gray-400",
|
||||
"text-green-300",
|
||||
"text-red-300",
|
||||
"text-yellow-300"
|
||||
"text-text",
|
||||
"text-muted",
|
||||
"text-info",
|
||||
"text-critical",
|
||||
"text-warning-strong"
|
||||
);
|
||||
|
||||
if (!message) {
|
||||
statusEl.classList.add("text-gray-400");
|
||||
statusEl.classList.add("text-muted");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (normalizedTone) {
|
||||
case "success":
|
||||
statusEl.classList.add("text-green-300");
|
||||
statusEl.classList.add("text-info");
|
||||
break;
|
||||
case "error":
|
||||
statusEl.classList.add("text-red-300");
|
||||
statusEl.classList.add("text-critical");
|
||||
break;
|
||||
case "warning":
|
||||
statusEl.classList.add("text-yellow-300");
|
||||
statusEl.classList.add("text-warning-strong");
|
||||
break;
|
||||
default:
|
||||
statusEl.classList.add("text-gray-300");
|
||||
statusEl.classList.add("text-text");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -514,7 +520,8 @@ function renderZapReceipts(receipts, { partial = false } = {}) {
|
||||
if (!Array.isArray(receipts) || receipts.length === 0) {
|
||||
if (partial) {
|
||||
const emptyItem = document.createElement("li");
|
||||
emptyItem.className = "rounded border border-gray-700 bg-gray-800/70 p-3 text-gray-300";
|
||||
emptyItem.className =
|
||||
"rounded border border-border bg-panel/70 p-3 text-text";
|
||||
emptyItem.textContent = "No receipts were returned for this attempt.";
|
||||
list.appendChild(emptyItem);
|
||||
}
|
||||
@@ -527,10 +534,11 @@ function renderZapReceipts(receipts, { partial = false } = {}) {
|
||||
}
|
||||
|
||||
const li = document.createElement("li");
|
||||
li.className = "rounded border border-gray-700 bg-gray-800/70 p-3";
|
||||
li.className = "rounded border border-border bg-panel/70 p-3";
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-gray-200";
|
||||
header.className =
|
||||
"flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-text";
|
||||
|
||||
const shareType = receipt.recipientType || receipt.type || "creator";
|
||||
const shareLabel = document.createElement("span");
|
||||
@@ -544,14 +552,14 @@ function renderZapReceipts(receipts, { partial = false } = {}) {
|
||||
? receipt.status === "success"
|
||||
: !receipt.error;
|
||||
status.textContent = isSuccess ? "Success" : "Failed";
|
||||
status.className = isSuccess ? "text-green-300" : "text-red-300";
|
||||
status.className = isSuccess ? "text-info" : "text-critical";
|
||||
|
||||
header.appendChild(shareLabel);
|
||||
header.appendChild(status);
|
||||
li.appendChild(header);
|
||||
|
||||
const address = document.createElement("p");
|
||||
address.className = "mt-1 text-xs text-gray-300 break-all";
|
||||
address.className = "mt-1 text-xs text-text break-all";
|
||||
const addressValue = receipt.address || "";
|
||||
if (addressValue) {
|
||||
address.textContent = addressValue;
|
||||
@@ -559,7 +567,7 @@ function renderZapReceipts(receipts, { partial = false } = {}) {
|
||||
}
|
||||
|
||||
const detail = document.createElement("p");
|
||||
detail.className = "mt-2 text-xs text-gray-400";
|
||||
detail.className = "mt-2 text-xs text-muted";
|
||||
if (isSuccess) {
|
||||
let detailMessage = "Invoice settled.";
|
||||
const preimage = receipt.payment?.result?.preimage;
|
||||
@@ -636,7 +644,7 @@ function getZapVideoEvent() {
|
||||
tags: Array.isArray(baseEvent.tags) ? [...baseEvent.tags] : [],
|
||||
content: typeof baseEvent.content === "string" ? baseEvent.content : "",
|
||||
created_at: baseEvent.created_at || Math.floor(Date.now() / 1000),
|
||||
lightningAddress,
|
||||
lightningAddress
|
||||
};
|
||||
}
|
||||
|
||||
@@ -650,7 +658,9 @@ async function prepareLightningContext({ amount, overrideFee = null }) {
|
||||
throw new Error("Enter a zap amount greater than zero.");
|
||||
}
|
||||
|
||||
const creatorEntry = await fetchLightningMetadata(currentChannelLightningAddress);
|
||||
const creatorEntry = await fetchLightningMetadata(
|
||||
currentChannelLightningAddress
|
||||
);
|
||||
if (shares.creatorShare > 0) {
|
||||
try {
|
||||
validateInvoiceAmount(creatorEntry.metadata, shares.creatorShare);
|
||||
@@ -665,7 +675,9 @@ async function prepareLightningContext({ amount, overrideFee = null }) {
|
||||
if (shares.platformShare > 0) {
|
||||
platformAddress = getCachedPlatformLightningAddress();
|
||||
if (!platformAddress) {
|
||||
platformAddress = await getPlatformLightningAddress({ forceRefresh: false });
|
||||
platformAddress = await getPlatformLightningAddress({
|
||||
forceRefresh: false
|
||||
});
|
||||
setCachedPlatformLightningAddress(platformAddress || "");
|
||||
}
|
||||
if (!platformAddress) {
|
||||
@@ -687,7 +699,7 @@ async function prepareLightningContext({ amount, overrideFee = null }) {
|
||||
shares,
|
||||
creatorEntry,
|
||||
platformEntry,
|
||||
platformAddress,
|
||||
platformAddress
|
||||
};
|
||||
}
|
||||
|
||||
@@ -696,7 +708,7 @@ function createZapDependencies({
|
||||
platformEntry,
|
||||
shares,
|
||||
shareTracker,
|
||||
walletSettings,
|
||||
walletSettings
|
||||
}) {
|
||||
const creatorKey = normalizeLightningAddressKey(
|
||||
creatorEntry?.address || currentChannelLightningAddress
|
||||
@@ -731,7 +743,7 @@ function createZapDependencies({
|
||||
key: normalized,
|
||||
address: resolved.address || value,
|
||||
resolved,
|
||||
fetchedAt: Date.now(),
|
||||
fetchedAt: Date.now()
|
||||
});
|
||||
}
|
||||
return resolved;
|
||||
@@ -747,13 +759,13 @@ function createZapDependencies({
|
||||
rememberLightningMetadata({
|
||||
...cached,
|
||||
metadata,
|
||||
fetchedAt: Date.now(),
|
||||
fetchedAt: Date.now()
|
||||
});
|
||||
}
|
||||
return metadata;
|
||||
},
|
||||
validateInvoiceAmount,
|
||||
requestInvoice,
|
||||
requestInvoice
|
||||
},
|
||||
wallet: {
|
||||
ensureWallet: async (options) => {
|
||||
@@ -765,7 +777,7 @@ function createZapDependencies({
|
||||
{
|
||||
tracker: shareTracker,
|
||||
context: { shares },
|
||||
walletSettings: options?.settings,
|
||||
walletSettings: options?.settings
|
||||
},
|
||||
error
|
||||
);
|
||||
@@ -775,17 +787,18 @@ function createZapDependencies({
|
||||
sendPayment: async (bolt11, params) => {
|
||||
const shareType = activeShare || "unknown";
|
||||
const shareAmount =
|
||||
shareType === "platform"
|
||||
? shares.platformShare
|
||||
: shares.creatorShare;
|
||||
shareType === "platform" ? shares.platformShare : shares.creatorShare;
|
||||
const address =
|
||||
shareType === "platform"
|
||||
? platformEntry?.address || getCachedPlatformLightningAddress()
|
||||
: creatorEntry?.address || currentChannelLightningAddress;
|
||||
const normalizedParams = {
|
||||
...(params || {}),
|
||||
...(params || {})
|
||||
};
|
||||
if (!Number.isFinite(normalizedParams.amountSats) && Number.isFinite(shareAmount)) {
|
||||
if (
|
||||
!Number.isFinite(normalizedParams.amountSats) &&
|
||||
Number.isFinite(shareAmount)
|
||||
) {
|
||||
normalizedParams.amountSats = shareAmount;
|
||||
}
|
||||
if (!normalizedParams.settings && walletSettings) {
|
||||
@@ -799,7 +812,7 @@ function createZapDependencies({
|
||||
status: "success",
|
||||
amount: shareAmount,
|
||||
address,
|
||||
payment,
|
||||
payment
|
||||
});
|
||||
}
|
||||
return payment;
|
||||
@@ -810,7 +823,7 @@ function createZapDependencies({
|
||||
status: "error",
|
||||
amount: shareAmount,
|
||||
address,
|
||||
error,
|
||||
error
|
||||
});
|
||||
}
|
||||
logZapError(
|
||||
@@ -820,7 +833,7 @@ function createZapDependencies({
|
||||
amount: shareAmount,
|
||||
address,
|
||||
tracker: shareTracker,
|
||||
context: { shares },
|
||||
context: { shares }
|
||||
},
|
||||
error
|
||||
);
|
||||
@@ -828,7 +841,7 @@ function createZapDependencies({
|
||||
} finally {
|
||||
activeShare = null;
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
platformAddress: {
|
||||
getPlatformLightningAddress: async () => {
|
||||
@@ -839,11 +852,13 @@ function createZapDependencies({
|
||||
if (cachedAddress) {
|
||||
return cachedAddress;
|
||||
}
|
||||
const fallback = await getPlatformLightningAddress({ forceRefresh: false });
|
||||
const fallback = await getPlatformLightningAddress({
|
||||
forceRefresh: false
|
||||
});
|
||||
setCachedPlatformLightningAddress(fallback || "");
|
||||
return fallback;
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -864,7 +879,7 @@ function getWalletSettingsOrPrompt() {
|
||||
}
|
||||
return {
|
||||
...settings,
|
||||
nwcUri: normalizedUri,
|
||||
nwcUri: normalizedUri
|
||||
};
|
||||
}
|
||||
|
||||
@@ -883,7 +898,7 @@ async function runZapAttempt({ amount, overrideFee = null, walletSettings }) {
|
||||
{
|
||||
amount,
|
||||
overrideFee,
|
||||
walletSettings: settings,
|
||||
walletSettings: settings
|
||||
},
|
||||
error
|
||||
);
|
||||
@@ -893,13 +908,17 @@ async function runZapAttempt({ amount, overrideFee = null, walletSettings }) {
|
||||
const dependencies = createZapDependencies({
|
||||
...context,
|
||||
shareTracker,
|
||||
walletSettings: settings,
|
||||
walletSettings: settings
|
||||
});
|
||||
const videoEvent = getZapVideoEvent();
|
||||
|
||||
let previousOverride;
|
||||
const hasGlobal = typeof globalThis !== "undefined";
|
||||
if (hasGlobal && typeof overrideFee === "number" && Number.isFinite(overrideFee)) {
|
||||
if (
|
||||
hasGlobal &&
|
||||
typeof overrideFee === "number" &&
|
||||
Number.isFinite(overrideFee)
|
||||
) {
|
||||
previousOverride = globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__;
|
||||
globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__ = overrideFee;
|
||||
}
|
||||
@@ -909,7 +928,7 @@ async function runZapAttempt({ amount, overrideFee = null, walletSettings }) {
|
||||
{
|
||||
videoEvent,
|
||||
amountSats: context.shares.total,
|
||||
walletSettings: settings,
|
||||
walletSettings: settings
|
||||
},
|
||||
dependencies
|
||||
);
|
||||
@@ -925,16 +944,23 @@ async function runZapAttempt({ amount, overrideFee = null, walletSettings }) {
|
||||
overrideFee,
|
||||
walletSettings: settings,
|
||||
context,
|
||||
tracker: shareTracker,
|
||||
tracker: shareTracker
|
||||
},
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
if (hasGlobal && typeof overrideFee === "number" && Number.isFinite(overrideFee)) {
|
||||
if (
|
||||
hasGlobal &&
|
||||
typeof overrideFee === "number" &&
|
||||
Number.isFinite(overrideFee)
|
||||
) {
|
||||
if (typeof previousOverride === "number") {
|
||||
globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__ = previousOverride;
|
||||
} else if (globalThis && "__BITVID_PLATFORM_FEE_OVERRIDE__" in globalThis) {
|
||||
} else if (
|
||||
globalThis &&
|
||||
"__BITVID_PLATFORM_FEE_OVERRIDE__" in globalThis
|
||||
) {
|
||||
delete globalThis.__BITVID_PLATFORM_FEE_OVERRIDE__;
|
||||
}
|
||||
}
|
||||
@@ -970,7 +996,7 @@ async function executePendingRetry({ walletSettings }) {
|
||||
const attempt = await runZapAttempt({
|
||||
amount: share.amount,
|
||||
overrideFee,
|
||||
walletSettings,
|
||||
walletSettings
|
||||
});
|
||||
if (!attempt) {
|
||||
setZapStatus("", "neutral");
|
||||
@@ -992,7 +1018,7 @@ async function executePendingRetry({ walletSettings }) {
|
||||
amount: share?.amount,
|
||||
address: share?.address,
|
||||
walletSettings,
|
||||
tracker,
|
||||
tracker
|
||||
},
|
||||
error
|
||||
);
|
||||
@@ -1006,9 +1032,12 @@ async function executePendingRetry({ walletSettings }) {
|
||||
const message = error?.message || "Retry failed.";
|
||||
setZapStatus(message, "error");
|
||||
app?.showError?.(message);
|
||||
renderZapReceipts(aggregatedTracker.length ? aggregatedTracker : tracker, {
|
||||
partial: true,
|
||||
});
|
||||
renderZapReceipts(
|
||||
aggregatedTracker.length ? aggregatedTracker : tracker,
|
||||
{
|
||||
partial: true
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1137,9 +1166,10 @@ async function handleZapSend(event) {
|
||||
walletSettings,
|
||||
tracker,
|
||||
retryAttempt:
|
||||
Array.isArray(pendingZapRetry?.shares) && pendingZapRetry.shares.length
|
||||
Array.isArray(pendingZapRetry?.shares) &&
|
||||
pendingZapRetry.shares.length
|
||||
? pendingZapRetry.shares.length
|
||||
: undefined,
|
||||
: undefined
|
||||
},
|
||||
error
|
||||
);
|
||||
@@ -1431,7 +1461,13 @@ function setupZapButton() {
|
||||
const amountInput = getZapAmountInput();
|
||||
const controls = getZapControlsContainer();
|
||||
const zapForm = getZapFormElement();
|
||||
if (!zapButton || !amountInput || !controls || !zapForm || !getZapSendButton()) {
|
||||
if (
|
||||
!zapButton ||
|
||||
!amountInput ||
|
||||
!controls ||
|
||||
!zapForm ||
|
||||
!getZapSendButton()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1460,7 +1496,10 @@ function setupZapButton() {
|
||||
typeof app?.getActiveNwcSettings === "function"
|
||||
? app.getActiveNwcSettings()
|
||||
: {};
|
||||
if (Number.isFinite(activeSettings?.defaultZap) && activeSettings.defaultZap > 0) {
|
||||
if (
|
||||
Number.isFinite(activeSettings?.defaultZap) &&
|
||||
activeSettings.defaultZap > 0
|
||||
) {
|
||||
amountInput.value = Math.max(0, Math.round(activeSettings.defaultZap));
|
||||
}
|
||||
|
||||
@@ -1542,7 +1581,9 @@ function normalizeChannelProfileMetadata(raw = {}) {
|
||||
trimProfileString(raw.name) ||
|
||||
"Unknown User";
|
||||
const picture =
|
||||
trimProfileString(raw.picture) || trimProfileString(raw.image) || FALLBACK_CHANNEL_AVATAR;
|
||||
trimProfileString(raw.picture) ||
|
||||
trimProfileString(raw.image) ||
|
||||
FALLBACK_CHANNEL_AVATAR;
|
||||
const about = trimProfileString(raw.about || raw.bio);
|
||||
const website = trimProfileString(raw.website || raw.url);
|
||||
const banner =
|
||||
@@ -1560,7 +1601,7 @@ function normalizeChannelProfileMetadata(raw = {}) {
|
||||
banner,
|
||||
lud16,
|
||||
lud06,
|
||||
lightningAddress,
|
||||
lightningAddress
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1572,7 +1613,7 @@ function rememberChannelProfile(pubkey, { profile = {}, event = null } = {}) {
|
||||
channelProfileMetadataCache.set(pubkey, {
|
||||
timestamp: Date.now(),
|
||||
profile: normalizeChannelProfileMetadata(profile),
|
||||
event: event ? { ...event } : null,
|
||||
event: event ? { ...event } : null
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1592,7 +1633,7 @@ function getCachedChannelProfile(pubkey) {
|
||||
|
||||
return {
|
||||
profile: entry.profile,
|
||||
event: entry.event ? { ...entry.event } : null,
|
||||
event: entry.event ? { ...entry.event } : null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1623,11 +1664,15 @@ function isValidRelayUrl(url) {
|
||||
}
|
||||
|
||||
function getCachedChannelVideoEvents(pubkey) {
|
||||
if (!nostrClient?.allEvents || typeof nostrClient.allEvents.forEach !== "function") {
|
||||
if (
|
||||
!nostrClient?.allEvents ||
|
||||
typeof nostrClient.allEvents.forEach !== "function"
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const normalized = typeof pubkey === "string" ? pubkey.trim().toLowerCase() : "";
|
||||
const normalized =
|
||||
typeof pubkey === "string" ? pubkey.trim().toLowerCase() : "";
|
||||
if (!normalized) {
|
||||
return [];
|
||||
}
|
||||
@@ -1670,7 +1715,8 @@ function buildRenderableChannelVideos({ events = [], app } = {}) {
|
||||
const uniqueVideos = Array.from(dedupedById.values());
|
||||
|
||||
const newestByRoot =
|
||||
app?.dedupeVideosByRoot?.(uniqueVideos) ?? dedupeToNewestByRoot(uniqueVideos);
|
||||
app?.dedupeVideosByRoot?.(uniqueVideos) ??
|
||||
dedupeToNewestByRoot(uniqueVideos);
|
||||
|
||||
let videos = newestByRoot.filter((video) => !video.deleted);
|
||||
videos = videos.filter((video) => {
|
||||
@@ -1698,7 +1744,7 @@ function renderChannelVideosFromList({
|
||||
container,
|
||||
app,
|
||||
loadToken,
|
||||
allowEmptyMessage = true,
|
||||
allowEmptyMessage = true
|
||||
} = {}) {
|
||||
if (!container || loadToken !== currentVideoLoadToken) {
|
||||
return false;
|
||||
@@ -1707,7 +1753,7 @@ function renderChannelVideosFromList({
|
||||
if (!Array.isArray(videos) || videos.length === 0) {
|
||||
if (allowEmptyMessage) {
|
||||
container.dataset.hasChannelVideos = "false";
|
||||
container.innerHTML = `<p class="text-gray-500">No videos to display.</p>`;
|
||||
container.innerHTML = `<p class="text-muted-strong">No videos to display.</p>`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1726,12 +1772,11 @@ function renderChannelVideosFromList({
|
||||
: "This magnet link is missing a compatible BitTorrent v1 info hash.";
|
||||
|
||||
const fallbackNormalizePubkey = (value) =>
|
||||
typeof value === "string" && value
|
||||
? value.trim().toLowerCase()
|
||||
: "";
|
||||
typeof value === "string" && value ? value.trim().toLowerCase() : "";
|
||||
const normalizePubkey =
|
||||
typeof app?.normalizeHexPubkey === "function"
|
||||
? (value) => app.normalizeHexPubkey(value) || fallbackNormalizePubkey(value)
|
||||
? (value) =>
|
||||
app.normalizeHexPubkey(value) || fallbackNormalizePubkey(value)
|
||||
: fallbackNormalizePubkey;
|
||||
const normalizedViewerPubkey = normalizePubkey(app?.pubkey);
|
||||
let renderIndex = 0;
|
||||
@@ -1745,16 +1790,21 @@ function renderChannelVideosFromList({
|
||||
}
|
||||
|
||||
const element = trigger instanceof HTMLElement ? trigger : null;
|
||||
const target = element?.closest("[data-play-url],[data-play-magnet]") || element;
|
||||
const target =
|
||||
element?.closest("[data-play-url],[data-play-magnet]") || element;
|
||||
|
||||
const rawUrlValue =
|
||||
(target?.dataset && typeof target.dataset.playUrl === "string"
|
||||
? target.dataset.playUrl
|
||||
: null) ?? target?.getAttribute?.("data-play-url") ?? "";
|
||||
: null) ??
|
||||
target?.getAttribute?.("data-play-url") ??
|
||||
"";
|
||||
const rawMagnetValue =
|
||||
(target?.dataset && typeof target.dataset.playMagnet === "string"
|
||||
? target.dataset.playMagnet
|
||||
: null) ?? target?.getAttribute?.("data-play-magnet") ?? "";
|
||||
: null) ??
|
||||
target?.getAttribute?.("data-play-magnet") ??
|
||||
"";
|
||||
|
||||
let url = "";
|
||||
if (rawUrlValue) {
|
||||
@@ -1792,14 +1842,14 @@ function renderChannelVideosFromList({
|
||||
url: detail.url,
|
||||
magnet: detail.magnet,
|
||||
title: detail.video?.title,
|
||||
description: detail.video?.description,
|
||||
description: detail.video?.description
|
||||
})
|
||||
).catch((error) => {
|
||||
console.error("Failed to play channel video via event id:", error);
|
||||
if (typeof app?.playVideoWithFallback === "function") {
|
||||
app.playVideoWithFallback({
|
||||
url: detail.url,
|
||||
magnet: detail.magnet,
|
||||
magnet: detail.magnet
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1810,7 +1860,10 @@ function renderChannelVideosFromList({
|
||||
Promise.resolve(
|
||||
app.playVideoWithFallback({ url: detail.url, magnet: detail.magnet })
|
||||
).catch((error) => {
|
||||
console.error("Failed to start fallback playback for channel video:", error);
|
||||
console.error(
|
||||
"Failed to start fallback playback for channel video:",
|
||||
error
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1848,7 +1901,10 @@ function renderChannelVideosFromList({
|
||||
typeof app?.deriveVideoPointerInfo === "function"
|
||||
? app.deriveVideoPointerInfo(video)
|
||||
: null;
|
||||
if (pointerInfo && typeof app?.persistWatchHistoryMetadataForVideo === "function") {
|
||||
if (
|
||||
pointerInfo &&
|
||||
typeof app?.persistWatchHistoryMetadataForVideo === "function"
|
||||
) {
|
||||
app.persistWatchHistoryMetadataForVideo(video, pointerInfo);
|
||||
}
|
||||
|
||||
@@ -1885,22 +1941,22 @@ function renderChannelVideosFromList({
|
||||
canManageBlacklist:
|
||||
typeof app?.canCurrentUserManageBlacklist === "function"
|
||||
? app.canCurrentUserManageBlacklist()
|
||||
: false,
|
||||
: false
|
||||
},
|
||||
nsfwContext: {
|
||||
isNsfw: video?.isNsfw === true,
|
||||
allowNsfw,
|
||||
viewerIsOwner: canEdit,
|
||||
viewerIsOwner: canEdit
|
||||
},
|
||||
helpers: {
|
||||
escapeHtml: (value) => escapeHTML(value),
|
||||
isMagnetSupported: (magnet) => app?.isMagnetUriSupported?.(magnet),
|
||||
toLocaleString: (value) =>
|
||||
typeof value === "number" ? value.toLocaleString() : value,
|
||||
typeof value === "number" ? value.toLocaleString() : value
|
||||
},
|
||||
assets: {
|
||||
fallbackThumbnailSrc: "assets/jpg/video-thumbnail-fallback.jpg",
|
||||
unsupportedBtihMessage,
|
||||
unsupportedBtihMessage
|
||||
},
|
||||
state: { loadedThumbnails },
|
||||
ensureGlobalMoreMenuHandlers: () => app?.ensureGlobalMoreMenuHandlers?.(),
|
||||
@@ -1909,8 +1965,8 @@ function renderChannelVideosFromList({
|
||||
formatTimeAgo: (ts) =>
|
||||
typeof app?.formatTimeAgo === "function"
|
||||
? app.formatTimeAgo(ts)
|
||||
: new Date(ts * 1000).toLocaleString(),
|
||||
},
|
||||
: new Date(ts * 1000).toLocaleString()
|
||||
}
|
||||
});
|
||||
|
||||
videoCard.onPlay = ({ event: domEvent, video: cardVideo }) => {
|
||||
@@ -1923,21 +1979,21 @@ function renderChannelVideosFromList({
|
||||
videoCard.onEdit = ({ video: editVideo, index: editIndex }) => {
|
||||
app?.handleEditVideo?.({
|
||||
eventId: editVideo?.id || "",
|
||||
index: Number.isFinite(editIndex) ? editIndex : null,
|
||||
index: Number.isFinite(editIndex) ? editIndex : null
|
||||
});
|
||||
};
|
||||
|
||||
videoCard.onRevert = ({ video: revertVideo, index: revertIndex }) => {
|
||||
app?.handleRevertVideo?.({
|
||||
eventId: revertVideo?.id || "",
|
||||
index: Number.isFinite(revertIndex) ? revertIndex : null,
|
||||
index: Number.isFinite(revertIndex) ? revertIndex : null
|
||||
});
|
||||
};
|
||||
|
||||
videoCard.onDelete = ({ video: deleteVideo, index: deleteIndex }) => {
|
||||
app?.handleFullDeleteVideo?.({
|
||||
eventId: deleteVideo?.id || "",
|
||||
index: Number.isFinite(deleteIndex) ? deleteIndex : null,
|
||||
index: Number.isFinite(deleteIndex) ? deleteIndex : null
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1946,7 +2002,7 @@ function renderChannelVideosFromList({
|
||||
const detail = {
|
||||
...dataset,
|
||||
eventId: dataset.eventId || video.id || "",
|
||||
context: dataset.context || "channel-grid",
|
||||
context: dataset.context || "channel-grid"
|
||||
};
|
||||
app?.handleMoreMenuAction?.(action || "copy-link", detail);
|
||||
};
|
||||
@@ -1967,7 +2023,7 @@ function renderChannelVideosFromList({
|
||||
if (renderIndex === 0) {
|
||||
if (allowEmptyMessage) {
|
||||
container.dataset.hasChannelVideos = "false";
|
||||
container.innerHTML = `<p class="text-gray-500">No videos to display.</p>`;
|
||||
container.innerHTML = `<p class="text-muted-strong">No videos to display.</p>`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1996,7 +2052,7 @@ function applyChannelProfileMetadata({
|
||||
event = null,
|
||||
pubkey = "",
|
||||
npub = "",
|
||||
loadToken = null,
|
||||
loadToken = null
|
||||
} = {}) {
|
||||
if (loadToken !== null && loadToken !== currentProfileLoadToken) {
|
||||
return;
|
||||
@@ -2054,8 +2110,7 @@ function applyChannelProfileMetadata({
|
||||
|
||||
const lnEl = document.getElementById("channelLightning");
|
||||
if (lnEl) {
|
||||
lnEl.textContent =
|
||||
lightningAddress || "No lightning address found.";
|
||||
lnEl.textContent = lightningAddress || "No lightning address found.";
|
||||
}
|
||||
|
||||
if (event) {
|
||||
@@ -2084,7 +2139,7 @@ async function fetchChannelProfileFromRelays(pubkey) {
|
||||
|
||||
try {
|
||||
const events = await nostrClient.pool.list(nostrClient.relays, [
|
||||
{ kinds: [0], authors: [pubkey], limit: 1 },
|
||||
{ kinds: [0], authors: [pubkey], limit: 1 }
|
||||
]);
|
||||
|
||||
let newestEvent = null;
|
||||
@@ -2129,7 +2184,7 @@ async function loadUserProfile(pubkey) {
|
||||
event: cachedEntry.event,
|
||||
pubkey,
|
||||
npub: currentChannelNpub,
|
||||
loadToken,
|
||||
loadToken
|
||||
});
|
||||
} else {
|
||||
const stateEntry =
|
||||
@@ -2142,7 +2197,7 @@ async function loadUserProfile(pubkey) {
|
||||
event: null,
|
||||
pubkey,
|
||||
npub: currentChannelNpub,
|
||||
loadToken,
|
||||
loadToken
|
||||
});
|
||||
} else {
|
||||
applyChannelProfileMetadata({
|
||||
@@ -2150,7 +2205,7 @@ async function loadUserProfile(pubkey) {
|
||||
event: null,
|
||||
pubkey,
|
||||
npub: currentChannelNpub,
|
||||
loadToken,
|
||||
loadToken
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2171,7 +2226,7 @@ async function loadUserProfile(pubkey) {
|
||||
result.profile?.lightningAddress ||
|
||||
result.profile?.lud16 ||
|
||||
result.profile?.lud06 ||
|
||||
"",
|
||||
""
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
@@ -2186,7 +2241,7 @@ async function loadUserProfile(pubkey) {
|
||||
event: result.event,
|
||||
pubkey,
|
||||
npub: currentChannelNpub,
|
||||
loadToken,
|
||||
loadToken
|
||||
});
|
||||
} catch (error) {
|
||||
if (loadToken === currentProfileLoadToken) {
|
||||
@@ -2219,7 +2274,7 @@ async function loadUserVideos(pubkey) {
|
||||
if (!hadExistingContent) {
|
||||
container.innerHTML = `
|
||||
<div class="py-16 flex justify-center">
|
||||
<span class="text-gray-400 animate-pulse">Loading videos…</span>
|
||||
<span class="text-muted animate-pulse">Loading videos…</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -2231,7 +2286,7 @@ async function loadUserVideos(pubkey) {
|
||||
if (cachedEvents.length) {
|
||||
const cachedVideos = buildRenderableChannelVideos({
|
||||
events: cachedEvents,
|
||||
app,
|
||||
app
|
||||
});
|
||||
if (cachedVideos.length) {
|
||||
renderedFromCache =
|
||||
@@ -2240,20 +2295,18 @@ async function loadUserVideos(pubkey) {
|
||||
container,
|
||||
app,
|
||||
loadToken,
|
||||
allowEmptyMessage: false,
|
||||
allowEmptyMessage: false
|
||||
}) || renderedFromCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ensureAccessPromise = accessControl
|
||||
.ensureReady()
|
||||
.catch((error) => {
|
||||
console.warn(
|
||||
"Failed to ensure admin lists were loaded before channel fetch:",
|
||||
error
|
||||
);
|
||||
});
|
||||
const ensureAccessPromise = accessControl.ensureReady().catch((error) => {
|
||||
console.warn(
|
||||
"Failed to ensure admin lists were loaded before channel fetch:",
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
// 1) Build filter for videos from this pubkey
|
||||
@@ -2261,7 +2314,7 @@ async function loadUserVideos(pubkey) {
|
||||
kinds: [30078],
|
||||
authors: [pubkey],
|
||||
"#t": ["video"],
|
||||
limit: 200,
|
||||
limit: 200
|
||||
};
|
||||
|
||||
// 2) Collect raw events from all relays
|
||||
@@ -2293,9 +2346,7 @@ async function loadUserVideos(pubkey) {
|
||||
const relayUrl = relayList[index];
|
||||
|
||||
if (result.status === "fulfilled") {
|
||||
const relayEvents = Array.isArray(result.value)
|
||||
? result.value
|
||||
: [];
|
||||
const relayEvents = Array.isArray(result.value) ? result.value : [];
|
||||
events.push(...relayEvents);
|
||||
return;
|
||||
}
|
||||
@@ -2323,15 +2374,19 @@ async function loadUserVideos(pubkey) {
|
||||
container,
|
||||
app,
|
||||
loadToken,
|
||||
allowEmptyMessage: true,
|
||||
allowEmptyMessage: true
|
||||
});
|
||||
|
||||
renderedFromCache = rendered || renderedFromCache;
|
||||
} catch (err) {
|
||||
if (loadToken === currentVideoLoadToken && container && !renderedFromCache) {
|
||||
if (
|
||||
loadToken === currentVideoLoadToken &&
|
||||
container &&
|
||||
!renderedFromCache
|
||||
) {
|
||||
container.dataset.hasChannelVideos = "false";
|
||||
container.innerHTML = `
|
||||
<p class="text-red-400">Failed to load videos. Please try again.</p>
|
||||
<p class="text-critical">Failed to load videos. Please try again.</p>
|
||||
`;
|
||||
}
|
||||
console.error("Error loading user videos:", err);
|
||||
@@ -2359,7 +2414,10 @@ window.addEventListener("bitvid:access-control-updated", () => {
|
||||
}
|
||||
|
||||
loadUserVideos(currentChannelHex).catch((error) => {
|
||||
console.error("Failed to refresh channel videos after admin update:", error);
|
||||
console.error(
|
||||
"Failed to refresh channel videos after admin update:",
|
||||
error
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2377,4 +2435,3 @@ function dedupeToNewestByRoot(videos) {
|
||||
}
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
+134
-102
@@ -5,11 +5,11 @@ import {
|
||||
pointerKey,
|
||||
normalizePointerInput,
|
||||
nostrClient,
|
||||
updateWatchHistoryList,
|
||||
updateWatchHistoryList
|
||||
} from "./nostr.js";
|
||||
import {
|
||||
WATCH_HISTORY_BATCH_RESOLVE,
|
||||
WATCH_HISTORY_BATCH_PAGE_SIZE,
|
||||
WATCH_HISTORY_BATCH_PAGE_SIZE
|
||||
} from "./config.js";
|
||||
import { getApplication } from "./applicationContext.js";
|
||||
|
||||
@@ -34,8 +34,7 @@ const WATCH_HISTORY_BATCH_SIZE = (() => {
|
||||
const FALLBACK_THUMBNAIL = "assets/svg/default-thumbnail.svg";
|
||||
const FALLBACK_AVATAR = "assets/svg/default-profile.svg";
|
||||
const isDevEnv =
|
||||
typeof process !== "undefined" &&
|
||||
process?.env?.NODE_ENV !== "production";
|
||||
typeof process !== "undefined" && process?.env?.NODE_ENV !== "production";
|
||||
|
||||
function escapeSelector(value) {
|
||||
if (typeof value !== "string") {
|
||||
@@ -95,12 +94,13 @@ function buildWatchHistoryFeedRuntime({ actor, cursor = 0 } = {}) {
|
||||
const runtime = {
|
||||
blacklistedEventIds: blacklist,
|
||||
isAuthorBlocked: (pubkey) =>
|
||||
(typeof app?.isAuthorBlocked === "function" && app.isAuthorBlocked(pubkey)) ||
|
||||
(typeof app?.isAuthorBlocked === "function" &&
|
||||
app.isAuthorBlocked(pubkey)) ||
|
||||
false,
|
||||
watchHistory: {
|
||||
actor: normalizedActor,
|
||||
cursor: Number.isFinite(cursor) ? cursor : 0,
|
||||
},
|
||||
cursor: Number.isFinite(cursor) ? cursor : 0
|
||||
}
|
||||
};
|
||||
|
||||
if (normalizedActor) {
|
||||
@@ -115,7 +115,7 @@ function safeLocaleDate(date) {
|
||||
return date.toLocaleDateString(undefined, {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
day: "numeric"
|
||||
});
|
||||
} catch (error) {
|
||||
return date.toDateString();
|
||||
@@ -160,7 +160,7 @@ function formatRelativeTime(timestampSeconds) {
|
||||
{ label: "week", seconds: 604800 },
|
||||
{ label: "day", seconds: 86400 },
|
||||
{ label: "hour", seconds: 3600 },
|
||||
{ label: "minute", seconds: 60 },
|
||||
{ label: "minute", seconds: 60 }
|
||||
];
|
||||
for (const unit of units) {
|
||||
const count = Math.floor(delta / unit.seconds);
|
||||
@@ -208,15 +208,15 @@ function normalizeHistoryItems(rawItems) {
|
||||
const watchedAtRaw = Number.isFinite(candidate?.watchedAt)
|
||||
? candidate.watchedAt
|
||||
: Number.isFinite(candidate?.timestamp)
|
||||
? candidate.timestamp
|
||||
: null;
|
||||
? candidate.timestamp
|
||||
: null;
|
||||
const watchedAt =
|
||||
watchedAtRaw !== null ? Math.max(0, Math.floor(watchedAtRaw)) : 0;
|
||||
normalized.push({
|
||||
pointer,
|
||||
pointerKey: key,
|
||||
watchedAt,
|
||||
raw: candidate,
|
||||
raw: candidate
|
||||
});
|
||||
}
|
||||
normalized.sort((a, b) => {
|
||||
@@ -232,7 +232,7 @@ async function defaultRemoveHandler({
|
||||
actor,
|
||||
items,
|
||||
snapshot,
|
||||
reason = "remove-item",
|
||||
reason = "remove-item"
|
||||
} = {}) {
|
||||
const sanitized = Array.isArray(items)
|
||||
? items
|
||||
@@ -259,7 +259,7 @@ async function defaultRemoveHandler({
|
||||
await updateWatchHistoryList(sanitized, {
|
||||
actorPubkey: actor,
|
||||
replace: true,
|
||||
source: reason,
|
||||
source: reason
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -338,12 +338,7 @@ function getPointerVideoId(video, pointer) {
|
||||
return "";
|
||||
}
|
||||
|
||||
export function buildHistoryCard({
|
||||
item,
|
||||
video,
|
||||
profile,
|
||||
metadataPreference,
|
||||
}) {
|
||||
export function buildHistoryCard({ item, video, profile, metadataPreference }) {
|
||||
const article = document.createElement("article");
|
||||
article.className = "watch-history-card";
|
||||
article.dataset.pointerKey = item.pointerKey;
|
||||
@@ -364,8 +359,8 @@ export function buildHistoryCard({
|
||||
typeof video.magnet === "string"
|
||||
? video.magnet.trim()
|
||||
: typeof video.infoHash === "string"
|
||||
? video.infoHash.trim()
|
||||
: "";
|
||||
? video.infoHash.trim()
|
||||
: "";
|
||||
return { url, magnet: magnetRaw };
|
||||
})();
|
||||
|
||||
@@ -391,7 +386,7 @@ export function buildHistoryCard({
|
||||
const thumbnailImg = document.createElement("img");
|
||||
thumbnailImg.alt = video?.title || "Video thumbnail";
|
||||
const thumbnailSrc =
|
||||
(video && typeof video.thumbnail === "string" && video.thumbnail.trim())
|
||||
video && typeof video.thumbnail === "string" && video.thumbnail.trim()
|
||||
? video.thumbnail.trim()
|
||||
: FALLBACK_THUMBNAIL;
|
||||
thumbnailImg.src = thumbnailSrc;
|
||||
@@ -421,7 +416,9 @@ export function buildHistoryCard({
|
||||
|
||||
const created = document.createElement("p");
|
||||
created.className = "watch-history-card__created";
|
||||
const createdAt = Number.isFinite(video?.created_at) ? video.created_at : null;
|
||||
const createdAt = Number.isFinite(video?.created_at)
|
||||
? video.created_at
|
||||
: null;
|
||||
created.textContent = createdAt
|
||||
? `Published ${formatRelativeTime(createdAt)}`
|
||||
: "Published date unavailable";
|
||||
@@ -455,7 +452,7 @@ export function buildHistoryCard({
|
||||
}
|
||||
const avatarImg = document.createElement("img");
|
||||
const avatarSrc =
|
||||
(profile && typeof profile.picture === "string" && profile.picture.trim())
|
||||
profile && typeof profile.picture === "string" && profile.picture.trim()
|
||||
? profile.picture.trim()
|
||||
: FALLBACK_AVATAR;
|
||||
avatarImg.src = avatarSrc;
|
||||
@@ -538,7 +535,7 @@ export function buildHistoryCard({
|
||||
}
|
||||
removeButton.setAttribute(
|
||||
"aria-label",
|
||||
"Remove this encrypted history entry (sync may take a moment).",
|
||||
"Remove this encrypted history entry (sync may take a moment)."
|
||||
);
|
||||
removeButton.title =
|
||||
"Removes this entry from encrypted history. Relay sync may take a moment.";
|
||||
@@ -551,7 +548,7 @@ export function buildHistoryCard({
|
||||
|
||||
if (metadataPreference === "relay-opt-in") {
|
||||
const hint = document.createElement("p");
|
||||
hint.className = "text-xs text-blue-200";
|
||||
hint.className = "text-xs text-info";
|
||||
hint.textContent = "Metadata is shared with relays for this entry.";
|
||||
meta.appendChild(hint);
|
||||
}
|
||||
@@ -570,12 +567,12 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
if (engine && typeof engine.run === "function") {
|
||||
const runtime = buildWatchHistoryFeedRuntime({
|
||||
actor: actorInput,
|
||||
cursor,
|
||||
cursor
|
||||
});
|
||||
return engine.run("watch-history", { runtime });
|
||||
}
|
||||
const items = await watchHistoryService.loadLatest(actorInput, {
|
||||
allowStale: true,
|
||||
allowStale: true
|
||||
});
|
||||
const normalized = normalizeHistoryItems(items);
|
||||
return { items: normalized, metadata: { engine: "service-fallback" } };
|
||||
@@ -589,8 +586,8 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
emptySelector = "#watchHistoryEmpty",
|
||||
sentinelSelector = "#watchHistorySentinel",
|
||||
loadMoreSelector = "#watchHistoryLoadMore",
|
||||
clearButtonSelector = "[data-history-action=\"clear-cache\"]",
|
||||
republishButtonSelector = "[data-history-action=\"republish\"]",
|
||||
clearButtonSelector = '[data-history-action="clear-cache"]',
|
||||
republishButtonSelector = '[data-history-action="republish"]',
|
||||
privacyBannerSelector = "#watchHistoryPrivacyBanner",
|
||||
privacyMessageSelector = "#watchHistoryPrivacyMessage",
|
||||
privacyToggleSelector = "#watchHistoryPrivacyToggle",
|
||||
@@ -615,7 +612,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
return controller.handleWatchHistoryRemoval(payload);
|
||||
}
|
||||
return defaultRemoveHandler(payload);
|
||||
},
|
||||
}
|
||||
} = config;
|
||||
|
||||
const syncEnabled =
|
||||
@@ -655,7 +652,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
syncEnabled,
|
||||
localSupported,
|
||||
localOnly,
|
||||
featureEnabled: syncEnabled || localSupported,
|
||||
featureEnabled: syncEnabled || localSupported
|
||||
};
|
||||
|
||||
let rendererRef = null;
|
||||
@@ -687,7 +684,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
metadataToggle: null,
|
||||
metadataThumb: null,
|
||||
metadataLabel: null,
|
||||
metadataDescription: null,
|
||||
metadataDescription: null
|
||||
};
|
||||
|
||||
let boundGridClickHandler = null;
|
||||
@@ -725,7 +722,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
metadataToggle: resolveElement(metadataToggleSelector),
|
||||
metadataThumb: resolveElement(metadataThumbSelector),
|
||||
metadataLabel: resolveElement(metadataLabelSelector),
|
||||
metadataDescription: resolveElement(metadataDescriptionSelector),
|
||||
metadataDescription: resolveElement(metadataDescriptionSelector)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -737,7 +734,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
if (isDevEnv) {
|
||||
console.warn(
|
||||
"[historyView] Failed to unsubscribe from watch history event:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -764,11 +761,12 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
const variantClass = {
|
||||
info: "border-blue-500/40 bg-blue-500/10 text-blue-100",
|
||||
warning: "border-amber-500/40 bg-amber-500/10 text-amber-100",
|
||||
error: "border-red-500/40 bg-red-500/10 text-red-100",
|
||||
}[variant] || "border-gray-600 bg-gray-800/70 text-gray-100";
|
||||
const variantClass =
|
||||
{
|
||||
info: "border-info/40 bg-info/10 text-info",
|
||||
warning: "border-warning-strong bg-warning-surface text-warning-strong",
|
||||
error: "border-critical/40 bg-critical/10 text-critical"
|
||||
}[variant] || "border-border bg-panel/70 text-text";
|
||||
|
||||
if (elements.toastRegion.childElementCount >= 3) {
|
||||
const firstChild = elements.toastRegion.firstElementChild;
|
||||
@@ -840,11 +838,14 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
return;
|
||||
}
|
||||
const enabled = state.metadataStorageEnabled;
|
||||
elements.metadataToggle.setAttribute("aria-checked", enabled ? "true" : "false");
|
||||
elements.metadataToggle.classList.toggle("bg-blue-600", enabled);
|
||||
elements.metadataToggle.classList.toggle("border-blue-500", enabled);
|
||||
elements.metadataToggle.classList.toggle("bg-gray-700", !enabled);
|
||||
elements.metadataToggle.classList.toggle("border-gray-600", !enabled);
|
||||
elements.metadataToggle.setAttribute(
|
||||
"aria-checked",
|
||||
enabled ? "true" : "false"
|
||||
);
|
||||
elements.metadataToggle.classList.toggle("bg-info", enabled);
|
||||
elements.metadataToggle.classList.toggle("border-info", enabled);
|
||||
elements.metadataToggle.classList.toggle("bg-panel", !enabled);
|
||||
elements.metadataToggle.classList.toggle("border-border", !enabled);
|
||||
if (elements.metadataThumb instanceof HTMLElement) {
|
||||
elements.metadataThumb.classList.toggle("translate-x-5", enabled);
|
||||
elements.metadataThumb.classList.toggle("translate-x-1", !enabled);
|
||||
@@ -862,7 +863,8 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
? nostrClient.sessionActor.pubkey
|
||||
: "";
|
||||
const activeActor = typeof state.actor === "string" ? state.actor : "";
|
||||
const fallbackActive = !nip07Pubkey && sessionPubkey && sessionPubkey === activeActor;
|
||||
const fallbackActive =
|
||||
!nip07Pubkey && sessionPubkey && sessionPubkey === activeActor;
|
||||
state.sessionFallbackActive = Boolean(fallbackActive);
|
||||
setHidden(elements.sessionWarning, !fallbackActive);
|
||||
}
|
||||
@@ -894,11 +896,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
payload.actor &&
|
||||
state.actor &&
|
||||
payload.actor !== state.actor
|
||||
) {
|
||||
if (payload.actor && state.actor && payload.actor !== state.actor) {
|
||||
return;
|
||||
}
|
||||
let message = "Republish retry scheduled.";
|
||||
@@ -942,11 +940,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
payload.actor &&
|
||||
state.actor &&
|
||||
payload.actor !== state.actor
|
||||
) {
|
||||
if (payload.actor && state.actor && payload.actor !== state.actor) {
|
||||
return;
|
||||
}
|
||||
fingerprintRefreshQueued = true;
|
||||
@@ -1049,7 +1043,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
{
|
||||
root: elements.scrollContainer || null,
|
||||
rootMargin: "0px 0px 320px 0px",
|
||||
threshold: 0.1,
|
||||
threshold: 0.1
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1139,7 +1133,10 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
return result.trim();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[historyView] Failed to resolve actor via getActor:", error);
|
||||
console.warn(
|
||||
"[historyView] Failed to resolve actor via getActor:",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
if (watchHistoryService?.publishView) {
|
||||
@@ -1160,20 +1157,30 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
|
||||
const pointerKeyValue = item.pointerKey || null;
|
||||
const baseMetadata =
|
||||
item && typeof item === "object" && item.metadata && typeof item.metadata === "object"
|
||||
item &&
|
||||
typeof item === "object" &&
|
||||
item.metadata &&
|
||||
typeof item.metadata === "object"
|
||||
? { ...item.metadata }
|
||||
: {};
|
||||
|
||||
let cached = pointerKeyValue ? state.metadataCache.get(pointerKeyValue) : null;
|
||||
if (!cached && pointerKeyValue && typeof watchHistoryService.getLocalMetadata === "function") {
|
||||
let cached = pointerKeyValue
|
||||
? state.metadataCache.get(pointerKeyValue)
|
||||
: null;
|
||||
if (
|
||||
!cached &&
|
||||
pointerKeyValue &&
|
||||
typeof watchHistoryService.getLocalMetadata === "function"
|
||||
) {
|
||||
try {
|
||||
cached = watchHistoryService.getLocalMetadata(pointerKeyValue) || null;
|
||||
cached =
|
||||
watchHistoryService.getLocalMetadata(pointerKeyValue) || null;
|
||||
} catch (error) {
|
||||
if (isDevEnv) {
|
||||
console.warn(
|
||||
"[historyView] Failed to read stored metadata for pointer:",
|
||||
pointerKeyValue,
|
||||
error,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1190,30 +1197,32 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
const metadata = {
|
||||
...baseMetadata,
|
||||
video: video || null,
|
||||
profile: profile || null,
|
||||
profile: profile || null
|
||||
};
|
||||
|
||||
if (pointerKeyValue) {
|
||||
state.metadataCache.set(pointerKeyValue, {
|
||||
video: metadata.video,
|
||||
profile: metadata.profile,
|
||||
profile: metadata.profile
|
||||
});
|
||||
if (state.metadataStorageEnabled) {
|
||||
try {
|
||||
watchHistoryService.setLocalMetadata?.(pointerKeyValue, {
|
||||
video: metadata.video,
|
||||
profile: metadata.profile,
|
||||
profile: metadata.profile
|
||||
});
|
||||
} catch (error) {
|
||||
if (isDevEnv) {
|
||||
console.warn(
|
||||
"[historyView] Failed to persist metadata for pointer:",
|
||||
pointerKeyValue,
|
||||
error,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (typeof watchHistoryService.removeLocalMetadata === "function") {
|
||||
} else if (
|
||||
typeof watchHistoryService.removeLocalMetadata === "function"
|
||||
) {
|
||||
try {
|
||||
watchHistoryService.removeLocalMetadata(pointerKeyValue);
|
||||
} catch (error) {
|
||||
@@ -1221,7 +1230,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
console.warn(
|
||||
"[historyView] Failed to remove cached metadata for pointer:",
|
||||
pointerKeyValue,
|
||||
error,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1279,7 +1288,8 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
section.dataset.historyDay = dayKey;
|
||||
section.className = "space-y-4";
|
||||
const header = document.createElement("h3");
|
||||
header.className = "text-sm font-semibold uppercase tracking-wide text-gray-300";
|
||||
header.className =
|
||||
"text-sm font-semibold uppercase tracking-wide text-text";
|
||||
header.textContent = label;
|
||||
const list = document.createElement("div");
|
||||
list.dataset.historyDayList = "true";
|
||||
@@ -1308,7 +1318,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
item: entry,
|
||||
video: entry.metadata?.video || null,
|
||||
profile: entry.metadata?.profile || null,
|
||||
metadataPreference: state.metadataPreference,
|
||||
metadataPreference: state.metadataPreference
|
||||
});
|
||||
container.list.appendChild(card);
|
||||
});
|
||||
@@ -1331,14 +1341,17 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
elements.grid.removeEventListener("click", boundGridClickHandler);
|
||||
}
|
||||
boundGridClickHandler = async (event) => {
|
||||
const trigger = event.target instanceof HTMLElement
|
||||
? event.target.closest("[data-history-action]")
|
||||
: null;
|
||||
const trigger =
|
||||
event.target instanceof HTMLElement
|
||||
? event.target.closest("[data-history-action]")
|
||||
: null;
|
||||
if (!(trigger instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
const action = trigger.dataset.historyAction || "";
|
||||
const pointerKeyAttr = trigger.dataset.pointerKey || (trigger.closest("[data-pointer-key]")?.dataset.pointerKey ?? "");
|
||||
const pointerKeyAttr =
|
||||
trigger.dataset.pointerKey ||
|
||||
(trigger.closest("[data-pointer-key]")?.dataset.pointerKey ?? "");
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
@@ -1366,7 +1379,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
if (app?.playVideoWithFallback) {
|
||||
app.playVideoWithFallback({
|
||||
url,
|
||||
magnet: magnetAttr,
|
||||
magnet: magnetAttr
|
||||
});
|
||||
}
|
||||
break;
|
||||
@@ -1430,7 +1443,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
if (isDevEnv) {
|
||||
console.warn(
|
||||
"[historyView] Failed to clear stored metadata while resetting progress:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1451,7 +1464,10 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
|
||||
if (elements.republishButton) {
|
||||
if (boundRepublishHandler) {
|
||||
elements.republishButton.removeEventListener("click", boundRepublishHandler);
|
||||
elements.republishButton.removeEventListener(
|
||||
"click",
|
||||
boundRepublishHandler
|
||||
);
|
||||
}
|
||||
boundRepublishHandler = async (event) => {
|
||||
event.preventDefault();
|
||||
@@ -1459,7 +1475,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
try {
|
||||
const payload = state.items.map((entry) => ({
|
||||
...entry.pointer,
|
||||
watchedAt: entry.watchedAt,
|
||||
watchedAt: entry.watchedAt
|
||||
}));
|
||||
await snapshot(payload, { actor, reason: "manual-republish" });
|
||||
const app = getAppInstance();
|
||||
@@ -1499,7 +1515,10 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
updatePrivacyBanner();
|
||||
renderer.render();
|
||||
};
|
||||
elements.privacyToggle.addEventListener("click", boundPrivacyToggleHandler);
|
||||
elements.privacyToggle.addEventListener(
|
||||
"click",
|
||||
boundPrivacyToggleHandler
|
||||
);
|
||||
}
|
||||
|
||||
if (elements.privacyDismiss) {
|
||||
@@ -1515,7 +1534,10 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
writePreference(WATCH_HISTORY_PRIVACY_DISMISSED_KEY, "true");
|
||||
updatePrivacyBanner();
|
||||
};
|
||||
elements.privacyDismiss.addEventListener("click", boundPrivacyDismissHandler);
|
||||
elements.privacyDismiss.addEventListener(
|
||||
"click",
|
||||
boundPrivacyDismissHandler
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1538,7 +1560,10 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
}
|
||||
} catch (error) {
|
||||
if (isDevEnv) {
|
||||
console.warn("[historyView] Failed to set metadata preference:", error);
|
||||
console.warn(
|
||||
"[historyView] Failed to set metadata preference:",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
state.metadataStorageEnabled =
|
||||
@@ -1553,7 +1578,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
if (isDevEnv) {
|
||||
console.warn(
|
||||
"[historyView] Failed to clear stored metadata after disabling preference:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1561,7 +1586,10 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
void renderer.render();
|
||||
}
|
||||
};
|
||||
elements.metadataToggle.addEventListener("click", boundMetadataToggleHandler);
|
||||
elements.metadataToggle.addEventListener(
|
||||
"click",
|
||||
boundMetadataToggleHandler
|
||||
);
|
||||
}
|
||||
|
||||
async function loadHistory({ force = false, actorOverride } = {}) {
|
||||
@@ -1624,11 +1652,14 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
const watchedAtRaw = Number.isFinite(entry?.watchedAt)
|
||||
? entry.watchedAt
|
||||
: Number.isFinite(entry?.metadata?.watchedAt)
|
||||
? entry.metadata.watchedAt
|
||||
: 0;
|
||||
? entry.metadata.watchedAt
|
||||
: 0;
|
||||
const watchedAt = Math.max(0, Math.floor(Number(watchedAtRaw) || 0));
|
||||
const baseMetadata =
|
||||
entry && typeof entry === "object" && entry.metadata && typeof entry.metadata === "object"
|
||||
entry &&
|
||||
typeof entry === "object" &&
|
||||
entry.metadata &&
|
||||
typeof entry.metadata === "object"
|
||||
? { ...entry.metadata }
|
||||
: {};
|
||||
const video = entry?.video || baseMetadata.video || null;
|
||||
@@ -1640,18 +1671,18 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
? baseMetadata.watchedAt
|
||||
: watchedAt || null,
|
||||
video: video || null,
|
||||
profile: profile || null,
|
||||
profile: profile || null
|
||||
};
|
||||
state.metadataCache.set(pointerKeyValue, {
|
||||
video: metadata.video,
|
||||
profile: metadata.profile,
|
||||
profile: metadata.profile
|
||||
});
|
||||
normalized.push({
|
||||
pointer,
|
||||
pointerKey: pointerKeyValue,
|
||||
watchedAt,
|
||||
video: metadata.video,
|
||||
metadata,
|
||||
metadata
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1717,10 +1748,8 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
);
|
||||
state.metadataPreference =
|
||||
storedPreference === "relay-opt-in" ? "relay-opt-in" : "encrypted-only";
|
||||
state.privacyDismissed = readPreference(
|
||||
WATCH_HISTORY_PRIVACY_DISMISSED_KEY,
|
||||
"false"
|
||||
) === "true";
|
||||
state.privacyDismissed =
|
||||
readPreference(WATCH_HISTORY_PRIVACY_DISMISSED_KEY, "false") === "true";
|
||||
updatePrivacyBanner();
|
||||
state.initialized = true;
|
||||
if (!state.featureEnabled) {
|
||||
@@ -1771,7 +1800,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
const { actor, force = true } = options;
|
||||
const changed = await loadHistory({
|
||||
force,
|
||||
actorOverride: actor,
|
||||
actorOverride: actor
|
||||
});
|
||||
if (!changed && state.items.length) {
|
||||
updateLoadMoreVisibility();
|
||||
@@ -1814,7 +1843,10 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
boundClearHandler = null;
|
||||
}
|
||||
if (elements.republishButton && boundRepublishHandler) {
|
||||
elements.republishButton.removeEventListener("click", boundRepublishHandler);
|
||||
elements.republishButton.removeEventListener(
|
||||
"click",
|
||||
boundRepublishHandler
|
||||
);
|
||||
boundRepublishHandler = null;
|
||||
}
|
||||
if (elements.privacyToggle && boundPrivacyToggleHandler) {
|
||||
@@ -1880,7 +1912,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
console.warn(
|
||||
"[historyView] Failed to drop cached metadata for pointer:",
|
||||
pointerKeyValue,
|
||||
error,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1904,7 +1936,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
removed,
|
||||
pointerKey: pointerKeyValue,
|
||||
snapshot,
|
||||
reason: "remove-item",
|
||||
reason: "remove-item"
|
||||
});
|
||||
const app = getAppInstance();
|
||||
if (!result?.handledToasts) {
|
||||
@@ -1929,7 +1961,7 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
if (isDevEnv) {
|
||||
console.warn(
|
||||
"[historyView] Failed to refresh watch history after removal:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1942,9 +1974,9 @@ export function createWatchHistoryRenderer(config = {}) {
|
||||
getState() {
|
||||
return {
|
||||
...state,
|
||||
items: state.items.slice(),
|
||||
items: state.items.slice()
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
rendererRef = renderer;
|
||||
|
||||
+64
-70
@@ -1,16 +1,16 @@
|
||||
// js/subscriptions.js
|
||||
import {
|
||||
nostrClient,
|
||||
convertEventToVideo as sharedConvertEventToVideo,
|
||||
convertEventToVideo as sharedConvertEventToVideo
|
||||
} from "./nostr.js";
|
||||
import {
|
||||
buildSubscriptionListEvent,
|
||||
SUBSCRIPTION_LIST_IDENTIFIER,
|
||||
SUBSCRIPTION_LIST_IDENTIFIER
|
||||
} from "./nostrEventSchemas.js";
|
||||
import { getSidebarLoadingMarkup } from "./sidebarLoading.js";
|
||||
import {
|
||||
publishEventToRelays,
|
||||
assertAnyRelayAccepted,
|
||||
assertAnyRelayAccepted
|
||||
} from "./nostrPublish.js";
|
||||
import { getApplication } from "./applicationContext.js";
|
||||
import { VideoListView } from "./ui/views/VideoListView.js";
|
||||
@@ -48,7 +48,7 @@ class SubscriptionsManager {
|
||||
kinds: [30002],
|
||||
authors: [userPubkey],
|
||||
"#d": [SUBSCRIPTION_LIST_IDENTIFIER],
|
||||
limit: 1,
|
||||
limit: 1
|
||||
};
|
||||
|
||||
const events = [];
|
||||
@@ -180,7 +180,7 @@ class SubscriptionsManager {
|
||||
const evt = buildSubscriptionListEvent({
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
content: cipherText,
|
||||
content: cipherText
|
||||
});
|
||||
|
||||
let signedEvent;
|
||||
@@ -200,7 +200,7 @@ class SubscriptionsManager {
|
||||
let publishSummary;
|
||||
try {
|
||||
publishSummary = assertAnyRelayAccepted(publishResults, {
|
||||
context: "subscription list",
|
||||
context: "subscription list"
|
||||
});
|
||||
} catch (publishError) {
|
||||
if (publishError?.relayFailures?.length) {
|
||||
@@ -222,8 +222,8 @@ class SubscriptionsManager {
|
||||
relayError instanceof Error
|
||||
? relayError.message
|
||||
: relayError
|
||||
? String(relayError)
|
||||
: "publish failed";
|
||||
? String(relayError)
|
||||
: "publish failed";
|
||||
console.warn(
|
||||
`[SubscriptionsManager] Subscription list not accepted by ${url}: ${reason}`,
|
||||
relayError
|
||||
@@ -251,9 +251,10 @@ class SubscriptionsManager {
|
||||
options = {}
|
||||
) {
|
||||
const limitCandidate = Number(options?.limit);
|
||||
const limit = Number.isFinite(limitCandidate) && limitCandidate > 0
|
||||
? Math.floor(limitCandidate)
|
||||
: null;
|
||||
const limit =
|
||||
Number.isFinite(limitCandidate) && limitCandidate > 0
|
||||
? Math.floor(limitCandidate)
|
||||
: null;
|
||||
const reason =
|
||||
typeof options?.reason === "string" && options.reason.trim()
|
||||
? options.reason.trim()
|
||||
@@ -265,7 +266,7 @@ class SubscriptionsManager {
|
||||
if (!userPubkey) {
|
||||
if (container) {
|
||||
container.innerHTML =
|
||||
"<p class='text-gray-500'>Please log in first.</p>";
|
||||
"<p class='text-muted-strong'>Please log in first.</p>";
|
||||
}
|
||||
this.lastRunOptions = null;
|
||||
this.lastResult = null;
|
||||
@@ -281,18 +282,18 @@ class SubscriptionsManager {
|
||||
this.lastRunOptions = {
|
||||
actorPubkey: userPubkey,
|
||||
limit,
|
||||
containerId,
|
||||
containerId
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!channelHexes.length) {
|
||||
container.innerHTML =
|
||||
"<p class='text-gray-500'>No subscriptions found.</p>";
|
||||
"<p class='text-muted-strong'>No subscriptions found.</p>";
|
||||
this.lastRunOptions = {
|
||||
actorPubkey: userPubkey,
|
||||
limit,
|
||||
containerId,
|
||||
containerId
|
||||
};
|
||||
this.lastResult = { items: [], metadata: { reason: "no-subscriptions" } };
|
||||
return this.lastResult;
|
||||
@@ -304,14 +305,14 @@ class SubscriptionsManager {
|
||||
actorPubkey: userPubkey,
|
||||
limit,
|
||||
containerId,
|
||||
reason,
|
||||
reason
|
||||
};
|
||||
|
||||
this.ensureFeedRegistered();
|
||||
const engine = this.getFeedEngine();
|
||||
if (!engine || typeof engine.run !== "function") {
|
||||
container.innerHTML =
|
||||
"<p class='text-gray-500'>Subscriptions are unavailable right now.</p>";
|
||||
"<p class='text-muted-strong'>Subscriptions are unavailable right now.</p>";
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -323,9 +324,9 @@ class SubscriptionsManager {
|
||||
runtime,
|
||||
hooks: {
|
||||
subscriptions: {
|
||||
resolveAuthors: () => this.getSubscribedAuthors(),
|
||||
},
|
||||
},
|
||||
resolveAuthors: () => this.getSubscribedAuthors()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -352,7 +353,7 @@ class SubscriptionsManager {
|
||||
error
|
||||
);
|
||||
container.innerHTML =
|
||||
"<p class='text-gray-500'>Unable to load subscriptions right now.</p>";
|
||||
"<p class='text-muted-strong'>Unable to load subscriptions right now.</p>";
|
||||
this.lastResult = null;
|
||||
return null;
|
||||
}
|
||||
@@ -396,7 +397,7 @@ class SubscriptionsManager {
|
||||
subscriptionAuthors: normalizedAuthors,
|
||||
authors: normalizedAuthors,
|
||||
blacklistedEventIds: blacklist,
|
||||
isAuthorBlocked,
|
||||
isAuthorBlocked
|
||||
};
|
||||
}
|
||||
|
||||
@@ -418,9 +419,10 @@ class SubscriptionsManager {
|
||||
: {};
|
||||
|
||||
const limitCandidate = Number(options?.limit);
|
||||
const limit = Number.isFinite(limitCandidate) && limitCandidate > 0
|
||||
? Math.floor(limitCandidate)
|
||||
: null;
|
||||
const limit =
|
||||
Number.isFinite(limitCandidate) && limitCandidate > 0
|
||||
? Math.floor(limitCandidate)
|
||||
: null;
|
||||
|
||||
const limitedItems = limit ? items.slice(0, limit) : items;
|
||||
const videos = limitedItems
|
||||
@@ -429,7 +431,7 @@ class SubscriptionsManager {
|
||||
|
||||
if (!videos.length) {
|
||||
container.innerHTML = `
|
||||
<p class="flex justify-center items-center h-full w-full text-center text-gray-500">
|
||||
<p class="flex justify-center items-center h-full w-full text-center text-muted-strong">
|
||||
No videos available yet.
|
||||
</p>`;
|
||||
return;
|
||||
@@ -438,7 +440,7 @@ class SubscriptionsManager {
|
||||
const listView = this.getListView(container, app);
|
||||
if (!listView) {
|
||||
container.innerHTML = `
|
||||
<p class="flex justify-center items-center h-full w-full text-center text-gray-500">
|
||||
<p class="flex justify-center items-center h-full w-full text-center text-muted-strong">
|
||||
Unable to render subscriptions feed.
|
||||
</p>`;
|
||||
return;
|
||||
@@ -452,7 +454,7 @@ class SubscriptionsManager {
|
||||
|
||||
const enrichedMetadata = {
|
||||
...metadata,
|
||||
feed: "subscriptions",
|
||||
feed: "subscriptions"
|
||||
};
|
||||
if (limit) {
|
||||
enrichedMetadata.limit = limit;
|
||||
@@ -479,7 +481,7 @@ class SubscriptionsManager {
|
||||
|
||||
const badgeHelpers = baseView?.badgeHelpers || {
|
||||
attachHealthBadges: () => {},
|
||||
attachUrlHealthBadges: () => {},
|
||||
attachUrlHealthBadges: () => {}
|
||||
};
|
||||
|
||||
const formatTimeAgo = (timestamp) => {
|
||||
@@ -502,36 +504,36 @@ class SubscriptionsManager {
|
||||
const assets = baseView?.assets || {
|
||||
fallbackThumbnailSrc: "assets/jpg/video-thumbnail-fallback.jpg",
|
||||
unsupportedBtihMessage:
|
||||
"This magnet link is missing a compatible BitTorrent v1 info hash.",
|
||||
"This magnet link is missing a compatible BitTorrent v1 info hash."
|
||||
};
|
||||
|
||||
const loadedThumbnails =
|
||||
app?.loadedThumbnails instanceof Map
|
||||
? app.loadedThumbnails
|
||||
: baseView?.state?.loadedThumbnails instanceof Map
|
||||
? baseView.state.loadedThumbnails
|
||||
: new Map();
|
||||
? baseView.state.loadedThumbnails
|
||||
: new Map();
|
||||
|
||||
const videosMap =
|
||||
app?.videosMap instanceof Map
|
||||
? app.videosMap
|
||||
: baseView?.state?.videosMap instanceof Map
|
||||
? baseView.state.videosMap
|
||||
: new Map();
|
||||
? baseView.state.videosMap
|
||||
: new Map();
|
||||
|
||||
const urlHealthCache =
|
||||
app?.urlHealthSnapshots instanceof Map
|
||||
? app.urlHealthSnapshots
|
||||
: baseView?.state?.urlHealthByVideoId instanceof Map
|
||||
? baseView.state.urlHealthByVideoId
|
||||
: new Map();
|
||||
? baseView.state.urlHealthByVideoId
|
||||
: new Map();
|
||||
|
||||
const streamHealthCache =
|
||||
app?.streamHealthSnapshots instanceof Map
|
||||
? app.streamHealthSnapshots
|
||||
: baseView?.state?.streamHealthByVideoId instanceof Map
|
||||
? baseView.state.streamHealthByVideoId
|
||||
: new Map();
|
||||
? baseView.state.streamHealthByVideoId
|
||||
: new Map();
|
||||
|
||||
const listViewConfig = {
|
||||
document: doc,
|
||||
@@ -540,21 +542,21 @@ class SubscriptionsManager {
|
||||
badgeHelpers,
|
||||
formatters: {
|
||||
formatTimeAgo,
|
||||
formatViewCountLabel,
|
||||
formatViewCountLabel
|
||||
},
|
||||
helpers: {
|
||||
escapeHtml: (value) => app?.escapeHTML?.(value) ?? value,
|
||||
isMagnetSupported: (magnet) =>
|
||||
app?.isMagnetUriSupported?.(magnet) ?? false,
|
||||
toLocaleString: (value) =>
|
||||
typeof value === "number" ? value.toLocaleString() : value,
|
||||
typeof value === "number" ? value.toLocaleString() : value
|
||||
},
|
||||
assets,
|
||||
state: {
|
||||
loadedThumbnails,
|
||||
videosMap,
|
||||
urlHealthByVideoId: urlHealthCache,
|
||||
streamHealthByVideoId: streamHealthCache,
|
||||
streamHealthByVideoId: streamHealthCache
|
||||
},
|
||||
utils: {
|
||||
dedupeVideos: (videos) => (Array.isArray(videos) ? [...videos] : []),
|
||||
@@ -574,24 +576,22 @@ class SubscriptionsManager {
|
||||
app?.canCurrentUserManageBlacklist?.() ?? false,
|
||||
canEditVideo: (video) => video?.pubkey === app?.pubkey,
|
||||
canDeleteVideo: (video) => video?.pubkey === app?.pubkey,
|
||||
batchFetchProfiles: (authorSet) =>
|
||||
app?.batchFetchProfiles?.(authorSet),
|
||||
batchFetchProfiles: (authorSet) => app?.batchFetchProfiles?.(authorSet),
|
||||
bindThumbnailFallbacks: (target) =>
|
||||
app?.bindThumbnailFallbacks?.(target),
|
||||
handleUrlHealthBadge: (payload) =>
|
||||
app?.handleUrlHealthBadge?.(payload),
|
||||
handleUrlHealthBadge: (payload) => app?.handleUrlHealthBadge?.(payload),
|
||||
refreshDiscussionCounts: (videosList, { container: root } = {}) =>
|
||||
app?.refreshVideoDiscussionCounts?.(videosList, {
|
||||
videoListRoot: root || container || null,
|
||||
videoListRoot: root || container || null
|
||||
}),
|
||||
ensureGlobalMoreMenuHandlers: () =>
|
||||
app?.ensureGlobalMoreMenuHandlers?.(),
|
||||
closeAllMenus: () => app?.closeAllMoreMenus?.(),
|
||||
closeAllMenus: () => app?.closeAllMoreMenus?.()
|
||||
},
|
||||
renderers: {
|
||||
getLoadingMarkup: (message) => getSidebarLoadingMarkup(message),
|
||||
getLoadingMarkup: (message) => getSidebarLoadingMarkup(message)
|
||||
},
|
||||
allowNsfw: ALLOW_NSFW_CONTENT === true,
|
||||
allowNsfw: ALLOW_NSFW_CONTENT === true
|
||||
};
|
||||
|
||||
const listView = new VideoListView(listViewConfig);
|
||||
@@ -604,16 +604,14 @@ class SubscriptionsManager {
|
||||
Promise.resolve(
|
||||
app?.playVideoByEventId?.(detail.videoId, {
|
||||
url: detail.url,
|
||||
magnet: detail.magnet,
|
||||
magnet: detail.magnet
|
||||
})
|
||||
).catch(
|
||||
(error) => {
|
||||
console.error(
|
||||
"[SubscriptionsManager] Failed to play by event id:",
|
||||
error
|
||||
);
|
||||
}
|
||||
);
|
||||
).catch((error) => {
|
||||
console.error(
|
||||
"[SubscriptionsManager] Failed to play by event id:",
|
||||
error
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
Promise.resolve(
|
||||
@@ -632,7 +630,7 @@ class SubscriptionsManager {
|
||||
}
|
||||
app?.handleEditVideo?.({
|
||||
eventId: video.id,
|
||||
index: Number.isFinite(index) ? index : null,
|
||||
index: Number.isFinite(index) ? index : null
|
||||
});
|
||||
});
|
||||
|
||||
@@ -642,7 +640,7 @@ class SubscriptionsManager {
|
||||
}
|
||||
app?.handleRevertVideo?.({
|
||||
eventId: video.id,
|
||||
index: Number.isFinite(index) ? index : null,
|
||||
index: Number.isFinite(index) ? index : null
|
||||
});
|
||||
});
|
||||
|
||||
@@ -652,7 +650,7 @@ class SubscriptionsManager {
|
||||
}
|
||||
app?.handleFullDeleteVideo?.({
|
||||
eventId: video.id,
|
||||
index: Number.isFinite(index) ? index : null,
|
||||
index: Number.isFinite(index) ? index : null
|
||||
});
|
||||
});
|
||||
|
||||
@@ -660,7 +658,7 @@ class SubscriptionsManager {
|
||||
const detail = {
|
||||
...(dataset || {}),
|
||||
author: dataset?.author || video?.pubkey || "",
|
||||
context: dataset?.context || "subscriptions",
|
||||
context: dataset?.context || "subscriptions"
|
||||
};
|
||||
app?.handleMoreMenuAction?.("blacklist-author", detail);
|
||||
});
|
||||
@@ -670,11 +668,8 @@ class SubscriptionsManager {
|
||||
const dataset = {
|
||||
...(detail.dataset || {}),
|
||||
eventId:
|
||||
detail.eventId ||
|
||||
detail.dataset?.eventId ||
|
||||
detail.video?.id ||
|
||||
"",
|
||||
context: detail.dataset?.context || "subscriptions",
|
||||
detail.eventId || detail.dataset?.eventId || detail.video?.id || "",
|
||||
context: detail.dataset?.context || "subscriptions"
|
||||
};
|
||||
app?.handleMoreMenuAction?.(detail.action || "copy-link", dataset);
|
||||
});
|
||||
@@ -684,7 +679,7 @@ class SubscriptionsManager {
|
||||
const dataset = {
|
||||
...(detail.dataset || {}),
|
||||
eventId: detail.dataset?.eventId || detail.video?.id || "",
|
||||
context: detail.dataset?.context || "subscriptions",
|
||||
context: detail.dataset?.context || "subscriptions"
|
||||
};
|
||||
app?.handleMoreMenuAction?.(detail.action, dataset);
|
||||
});
|
||||
@@ -710,14 +705,13 @@ class SubscriptionsManager {
|
||||
|
||||
return this.showSubscriptionVideos(actorPubkey, containerId, {
|
||||
limit,
|
||||
reason,
|
||||
reason
|
||||
});
|
||||
}
|
||||
|
||||
convertEventToVideo(evt) {
|
||||
return sharedConvertEventToVideo(evt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const subscriptions = new SubscriptionsManager();
|
||||
@@ -15,20 +15,29 @@ export class UploadModal {
|
||||
getCurrentPubkey,
|
||||
safeEncodeNpub,
|
||||
eventTarget,
|
||||
container,
|
||||
container
|
||||
} = {}) {
|
||||
this.authService = authService || null;
|
||||
this.r2Service = r2Service || null;
|
||||
this.publishVideoNote = typeof publishVideoNote === "function" ? publishVideoNote : null;
|
||||
this.publishVideoNote =
|
||||
typeof publishVideoNote === "function" ? publishVideoNote : null;
|
||||
this.removeTrackingScripts =
|
||||
typeof removeTrackingScripts === "function" ? removeTrackingScripts : () => {};
|
||||
typeof removeTrackingScripts === "function"
|
||||
? removeTrackingScripts
|
||||
: () => {};
|
||||
this.setGlobalModalState =
|
||||
typeof setGlobalModalState === "function" ? setGlobalModalState : () => {};
|
||||
typeof setGlobalModalState === "function"
|
||||
? setGlobalModalState
|
||||
: () => {};
|
||||
this.showError = typeof showError === "function" ? showError : () => {};
|
||||
this.showSuccess = typeof showSuccess === "function" ? showSuccess : () => {};
|
||||
this.getCurrentPubkey = typeof getCurrentPubkey === "function" ? getCurrentPubkey : null;
|
||||
this.safeEncodeNpub = typeof safeEncodeNpub === "function" ? safeEncodeNpub : () => "";
|
||||
this.eventTarget = eventTarget instanceof EventTarget ? eventTarget : new EventTarget();
|
||||
this.showSuccess =
|
||||
typeof showSuccess === "function" ? showSuccess : () => {};
|
||||
this.getCurrentPubkey =
|
||||
typeof getCurrentPubkey === "function" ? getCurrentPubkey : null;
|
||||
this.safeEncodeNpub =
|
||||
typeof safeEncodeNpub === "function" ? safeEncodeNpub : () => "";
|
||||
this.eventTarget =
|
||||
eventTarget instanceof EventTarget ? eventTarget : new EventTarget();
|
||||
this.container = container || null;
|
||||
|
||||
this.root = null;
|
||||
@@ -161,13 +170,15 @@ export class UploadModal {
|
||||
cacheElements(context) {
|
||||
const scope = this.root || context;
|
||||
this.modalBackdrop = scope?.querySelector?.(".bv-modal-backdrop") || null;
|
||||
this.modalPanel = scope?.querySelector?.(".bv-modal__panel") || scope || null;
|
||||
this.modalPanel =
|
||||
scope?.querySelector?.(".bv-modal__panel") || scope || null;
|
||||
|
||||
this.uploadModeButtons = Array.from(
|
||||
context.querySelectorAll(".upload-mode-toggle[data-upload-mode]")
|
||||
);
|
||||
this.customSection = context.querySelector("#customUploadSection") || null;
|
||||
this.cloudflareSection = context.querySelector("#cloudflareUploadSection") || null;
|
||||
this.cloudflareSection =
|
||||
context.querySelector("#cloudflareUploadSection") || null;
|
||||
|
||||
this.customForm = context.querySelector("#uploadForm") || null;
|
||||
this.customFormInputs = {
|
||||
@@ -181,36 +192,44 @@ export class UploadModal {
|
||||
enableComments: context.querySelector("#uploadEnableComments") || null,
|
||||
isNsfw: context.querySelector("#uploadIsNsfw") || null,
|
||||
isForKids: context.querySelector("#uploadIsForKids") || null,
|
||||
isPrivate: context.querySelector("#uploadIsPrivate") || null,
|
||||
isPrivate: context.querySelector("#uploadIsPrivate") || null
|
||||
};
|
||||
|
||||
this.closeButton = context.querySelector("#closeUploadModal") || null;
|
||||
|
||||
this.cloudflareSettingsForm = context.querySelector("#cloudflareSettingsForm") || null;
|
||||
this.cloudflareSettingsForm =
|
||||
context.querySelector("#cloudflareSettingsForm") || null;
|
||||
this.cloudflareClearSettingsButton =
|
||||
context.querySelector("#cloudflareClearSettings") || null;
|
||||
this.cloudflareSettingsStatus =
|
||||
context.querySelector("#cloudflareSettingsStatus") || null;
|
||||
this.cloudflareBucketPreview =
|
||||
context.querySelector("#cloudflareBucketPreview") || null;
|
||||
this.cloudflareUploadForm = context.querySelector("#cloudflareUploadForm") || null;
|
||||
this.cloudflareUploadForm =
|
||||
context.querySelector("#cloudflareUploadForm") || null;
|
||||
this.cloudflareFileInput = context.querySelector("#cloudflareFile") || null;
|
||||
this.cloudflareUploadButton = context.querySelector("#cloudflareUploadButton") || null;
|
||||
this.cloudflareUploadButton =
|
||||
context.querySelector("#cloudflareUploadButton") || null;
|
||||
this.cloudflareUploadStatus =
|
||||
context.querySelector("#cloudflareUploadStatus") || null;
|
||||
this.cloudflareProgressBar = context.querySelector("#cloudflareProgressBar") || null;
|
||||
this.cloudflareProgressFill = context.querySelector("#cloudflareProgressFill") || null;
|
||||
this.cloudflareTitleInput = context.querySelector("#cloudflareTitle") || null;
|
||||
this.cloudflareProgressBar =
|
||||
context.querySelector("#cloudflareProgressBar") || null;
|
||||
this.cloudflareProgressFill =
|
||||
context.querySelector("#cloudflareProgressFill") || null;
|
||||
this.cloudflareTitleInput =
|
||||
context.querySelector("#cloudflareTitle") || null;
|
||||
this.cloudflareDescriptionInput =
|
||||
context.querySelector("#cloudflareDescription") || null;
|
||||
this.cloudflareThumbnailInput =
|
||||
context.querySelector("#cloudflareThumbnail") || null;
|
||||
this.cloudflareMagnetInput = context.querySelector("#cloudflareMagnet") || null;
|
||||
this.cloudflareMagnetInput =
|
||||
context.querySelector("#cloudflareMagnet") || null;
|
||||
this.cloudflareWsInput = context.querySelector("#cloudflareWs") || null;
|
||||
this.cloudflareXsInput = context.querySelector("#cloudflareXs") || null;
|
||||
this.cloudflareEnableCommentsInput =
|
||||
context.querySelector("#cloudflareEnableComments") || null;
|
||||
this.cloudflareIsNsfwInput = context.querySelector("#cloudflareIsNsfw") || null;
|
||||
this.cloudflareIsNsfwInput =
|
||||
context.querySelector("#cloudflareIsNsfw") || null;
|
||||
this.cloudflareIsForKidsInput =
|
||||
context.querySelector("#cloudflareIsForKids") || null;
|
||||
this.cloudflareAdvancedToggle =
|
||||
@@ -223,7 +242,8 @@ export class UploadModal {
|
||||
context.querySelector("#cloudflareAdvancedFields") || null;
|
||||
this.r2AccountIdInput = context.querySelector("#r2AccountId") || null;
|
||||
this.r2AccessKeyIdInput = context.querySelector("#r2AccessKeyId") || null;
|
||||
this.r2SecretAccessKeyInput = context.querySelector("#r2SecretAccessKey") || null;
|
||||
this.r2SecretAccessKeyInput =
|
||||
context.querySelector("#r2SecretAccessKey") || null;
|
||||
this.r2ApiTokenInput = context.querySelector("#r2ApiToken") || null;
|
||||
this.r2ZoneIdInput = context.querySelector("#r2ZoneId") || null;
|
||||
this.r2BaseDomainInput = context.querySelector("#r2BaseDomain") || null;
|
||||
@@ -301,7 +321,7 @@ export class UploadModal {
|
||||
image: [],
|
||||
fallback: [],
|
||||
service: [],
|
||||
autoGenerated: true,
|
||||
autoGenerated: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -351,12 +371,16 @@ export class UploadModal {
|
||||
|
||||
if (this.cloudflareAdvancedToggle) {
|
||||
this.cloudflareAdvancedToggle.addEventListener("click", () => {
|
||||
if (this.r2Service?.setCloudflareAdvancedVisibility &&
|
||||
this.r2Service?.getCloudflareAdvancedVisibility) {
|
||||
if (
|
||||
this.r2Service?.setCloudflareAdvancedVisibility &&
|
||||
this.r2Service?.getCloudflareAdvancedVisibility
|
||||
) {
|
||||
const nextState = !this.r2Service.getCloudflareAdvancedVisibility();
|
||||
this.r2Service.setCloudflareAdvancedVisibility(nextState);
|
||||
} else {
|
||||
this.renderCloudflareAdvancedVisibility(!this.cloudflareAdvancedVisible);
|
||||
this.renderCloudflareAdvancedVisibility(
|
||||
!this.cloudflareAdvancedVisible
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -392,7 +416,7 @@ export class UploadModal {
|
||||
root: this.root,
|
||||
backdrop: this.modalBackdrop || this.root,
|
||||
panel: this.modalPanel || this.root,
|
||||
onRequestClose: () => this.close(),
|
||||
onRequestClose: () => this.close()
|
||||
});
|
||||
|
||||
this.cleanupHandlers.push(() => {
|
||||
@@ -435,13 +459,21 @@ export class UploadModal {
|
||||
|
||||
register(
|
||||
this.r2Service.on("settingsStatus", ({ message, variant } = {}) => {
|
||||
this.applyCloudflareStatus(this.cloudflareSettingsStatus, message, variant);
|
||||
this.applyCloudflareStatus(
|
||||
this.cloudflareSettingsStatus,
|
||||
message,
|
||||
variant
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
register(
|
||||
this.r2Service.on("uploadStatus", ({ message, variant } = {}) => {
|
||||
this.applyCloudflareStatus(this.cloudflareUploadStatus, message, variant);
|
||||
this.applyCloudflareStatus(
|
||||
this.cloudflareUploadStatus,
|
||||
message,
|
||||
variant
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -548,7 +580,7 @@ export class UploadModal {
|
||||
handleCustomSubmit() {
|
||||
const audienceFlags = this.sanitizeAudienceFlags({
|
||||
isNsfw: this.readCheckboxValue(this.customFormInputs.isNsfw, false),
|
||||
isForKids: this.readCheckboxValue(this.customFormInputs.isForKids, false),
|
||||
isForKids: this.readCheckboxValue(this.customFormInputs.isForKids, false)
|
||||
});
|
||||
const payload = {
|
||||
title: this.customFormInputs.title?.value?.trim() || "",
|
||||
@@ -562,7 +594,7 @@ export class UploadModal {
|
||||
this.customFormInputs.enableComments,
|
||||
true
|
||||
),
|
||||
...audienceFlags,
|
||||
...audienceFlags
|
||||
};
|
||||
|
||||
if (this.customFormInputs.isPrivate) {
|
||||
@@ -587,7 +619,7 @@ export class UploadModal {
|
||||
secretAccessKey: this.r2SecretAccessKeyInput?.value?.trim() || "",
|
||||
apiToken: this.r2ApiTokenInput?.value?.trim() || "",
|
||||
zoneId: this.r2ZoneIdInput?.value?.trim() || "",
|
||||
baseDomain: this.r2BaseDomainInput?.value || "",
|
||||
baseDomain: this.r2BaseDomainInput?.value || ""
|
||||
};
|
||||
}
|
||||
|
||||
@@ -598,24 +630,24 @@ export class UploadModal {
|
||||
|
||||
element.textContent = message || "";
|
||||
element.classList.remove(
|
||||
"text-green-400",
|
||||
"text-red-400",
|
||||
"text-yellow-400",
|
||||
"text-gray-400"
|
||||
"text-info-strong",
|
||||
"text-critical",
|
||||
"text-warning-strong",
|
||||
"text-muted"
|
||||
);
|
||||
|
||||
if (!message) {
|
||||
element.classList.add("text-gray-400");
|
||||
element.classList.add("text-muted");
|
||||
return;
|
||||
}
|
||||
|
||||
let className = "text-gray-400";
|
||||
let className = "text-muted";
|
||||
if (variant === "success") {
|
||||
className = "text-green-400";
|
||||
className = "text-info-strong";
|
||||
} else if (variant === "error") {
|
||||
className = "text-red-400";
|
||||
className = "text-critical";
|
||||
} else if (variant === "warning") {
|
||||
className = "text-yellow-400";
|
||||
className = "text-warning-strong";
|
||||
}
|
||||
element.classList.add(className);
|
||||
}
|
||||
@@ -646,7 +678,10 @@ export class UploadModal {
|
||||
}
|
||||
|
||||
if (this.cloudflareAdvancedToggleIcon) {
|
||||
this.cloudflareAdvancedToggleIcon.classList.toggle("rotate-90", isVisible);
|
||||
this.cloudflareAdvancedToggleIcon.classList.toggle(
|
||||
"rotate-90",
|
||||
isVisible
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,7 +779,8 @@ export class UploadModal {
|
||||
|
||||
resetCloudflareUploadForm() {
|
||||
if (this.cloudflareTitleInput) this.cloudflareTitleInput.value = "";
|
||||
if (this.cloudflareDescriptionInput) this.cloudflareDescriptionInput.value = "";
|
||||
if (this.cloudflareDescriptionInput)
|
||||
this.cloudflareDescriptionInput.value = "";
|
||||
if (this.cloudflareThumbnailInput) this.cloudflareThumbnailInput.value = "";
|
||||
if (this.cloudflareMagnetInput) this.cloudflareMagnetInput.value = "";
|
||||
if (this.cloudflareWsInput) this.cloudflareWsInput.value = "";
|
||||
@@ -766,7 +802,8 @@ export class UploadModal {
|
||||
if (this.customFormInputs.magnet) this.customFormInputs.magnet.value = "";
|
||||
if (this.customFormInputs.ws) this.customFormInputs.ws.value = "";
|
||||
if (this.customFormInputs.xs) this.customFormInputs.xs.value = "";
|
||||
if (this.customFormInputs.thumbnail) this.customFormInputs.thumbnail.value = "";
|
||||
if (this.customFormInputs.thumbnail)
|
||||
this.customFormInputs.thumbnail.value = "";
|
||||
if (this.customFormInputs.description)
|
||||
this.customFormInputs.description.value = "";
|
||||
if (this.customFormInputs.enableComments)
|
||||
@@ -835,7 +872,7 @@ export class UploadModal {
|
||||
const file = this.cloudflareFileInput?.files?.[0] || null;
|
||||
const audienceFlags = this.sanitizeAudienceFlags({
|
||||
isNsfw: this.readCheckboxValue(this.cloudflareIsNsfwInput, false),
|
||||
isForKids: this.readCheckboxValue(this.cloudflareIsForKidsInput, false),
|
||||
isForKids: this.readCheckboxValue(this.cloudflareIsForKidsInput, false)
|
||||
});
|
||||
const metadata = {
|
||||
title: this.cloudflareTitleInput?.value?.trim() || "",
|
||||
@@ -848,7 +885,7 @@ export class UploadModal {
|
||||
this.cloudflareEnableCommentsInput,
|
||||
true
|
||||
),
|
||||
...audienceFlags,
|
||||
...audienceFlags
|
||||
};
|
||||
|
||||
const nip71Metadata = this.nip71FormManager.collectSection("cloudflare");
|
||||
@@ -862,7 +899,7 @@ export class UploadModal {
|
||||
}
|
||||
metadata.nip71 = {
|
||||
...nip71Metadata,
|
||||
imeta: imetaList,
|
||||
imeta: imetaList
|
||||
};
|
||||
}
|
||||
|
||||
@@ -873,8 +910,10 @@ export class UploadModal {
|
||||
metadata,
|
||||
settingsInput: this.collectCloudflareSettingsFormValues(),
|
||||
publishVideoNote: (payload, options) =>
|
||||
this.publishVideoNote ? this.publishVideoNote(payload, options) : null,
|
||||
onReset: () => this.resetCloudflareUploadForm(),
|
||||
this.publishVideoNote
|
||||
? this.publishVideoNote(payload, options)
|
||||
: null,
|
||||
onReset: () => this.resetCloudflareUploadForm()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[UploadModal] Cloudflare upload failed", error);
|
||||
|
||||
+177
-156
@@ -16,7 +16,7 @@ export class VideoCard {
|
||||
state = {},
|
||||
ensureGlobalMoreMenuHandlers,
|
||||
onRequestCloseAllMenus,
|
||||
nsfwContext = null,
|
||||
nsfwContext = null
|
||||
} = {}) {
|
||||
if (!doc) {
|
||||
throw new Error("VideoCard requires a document reference.");
|
||||
@@ -39,30 +39,32 @@ export class VideoCard {
|
||||
canDelete: false,
|
||||
canRevert: false,
|
||||
canManageBlacklist: false,
|
||||
...capabilities,
|
||||
...capabilities
|
||||
};
|
||||
this.formatters = {
|
||||
formatTimeAgo: formatters.formatTimeAgo,
|
||||
formatNumber: formatters.formatNumber,
|
||||
formatNumber: formatters.formatNumber
|
||||
};
|
||||
this.helpers = {
|
||||
escapeHtml: helpers.escapeHtml,
|
||||
isMagnetSupported: helpers.isMagnetSupported,
|
||||
toLocaleString: helpers.toLocaleString,
|
||||
toLocaleString: helpers.toLocaleString
|
||||
};
|
||||
this.assets = {
|
||||
fallbackThumbnailSrc: assets.fallbackThumbnailSrc || "",
|
||||
unsupportedBtihMessage: assets.unsupportedBtihMessage || "",
|
||||
unsupportedBtihMessage: assets.unsupportedBtihMessage || ""
|
||||
};
|
||||
this.state = {
|
||||
loadedThumbnails:
|
||||
state.loadedThumbnails instanceof Map ? state.loadedThumbnails : null,
|
||||
urlHealthByVideoId:
|
||||
state.urlHealthByVideoId instanceof Map ? state.urlHealthByVideoId : null,
|
||||
state.urlHealthByVideoId instanceof Map
|
||||
? state.urlHealthByVideoId
|
||||
: null,
|
||||
streamHealthByVideoId:
|
||||
state.streamHealthByVideoId instanceof Map
|
||||
? state.streamHealthByVideoId
|
||||
: null,
|
||||
: null
|
||||
};
|
||||
this.ensureGlobalMoreMenuHandlers =
|
||||
typeof ensureGlobalMoreMenuHandlers === "function"
|
||||
@@ -76,7 +78,7 @@ export class VideoCard {
|
||||
this.nsfwContext = {
|
||||
isNsfw: Boolean(nsfwContext?.isNsfw),
|
||||
allowNsfw: nsfwContext?.allowNsfw !== false,
|
||||
viewerIsOwner: nsfwContext?.viewerIsOwner === true,
|
||||
viewerIsOwner: nsfwContext?.viewerIsOwner === true
|
||||
};
|
||||
this.shouldMaskNsfwForOwner =
|
||||
this.nsfwContext.isNsfw &&
|
||||
@@ -89,7 +91,7 @@ export class VideoCard {
|
||||
onRevert: null,
|
||||
onDelete: null,
|
||||
onMoreAction: null,
|
||||
onAuthorNavigate: null,
|
||||
onAuthorNavigate: null
|
||||
};
|
||||
|
||||
this.root = null;
|
||||
@@ -111,8 +113,7 @@ export class VideoCard {
|
||||
this.authorNameEl = null;
|
||||
this.timestampEl = null;
|
||||
|
||||
this.playbackUrl =
|
||||
typeof video.url === "string" ? video.url.trim() : "";
|
||||
this.playbackUrl = typeof video.url === "string" ? video.url.trim() : "";
|
||||
const magnet =
|
||||
(typeof video.magnet === "string" ? video.magnet.trim() : "") ||
|
||||
(typeof video.infoHash === "string" ? video.infoHash.trim() : "");
|
||||
@@ -129,7 +130,11 @@ export class VideoCard {
|
||||
: null;
|
||||
this.postedAt = normalizedPostedAt;
|
||||
|
||||
if (this.postedAt !== null && this.video && typeof this.video === "object") {
|
||||
if (
|
||||
this.postedAt !== null &&
|
||||
this.video &&
|
||||
typeof this.video === "object"
|
||||
) {
|
||||
this.video.rootCreatedAt = this.postedAt;
|
||||
}
|
||||
|
||||
@@ -215,13 +220,13 @@ export class VideoCard {
|
||||
const root = this.createElement("div", {
|
||||
classNames: [
|
||||
"video-card",
|
||||
"bg-gray-900",
|
||||
"bg-card",
|
||||
"rounded-lg",
|
||||
"shadow-lg",
|
||||
"hover:shadow-2xl",
|
||||
"transition-all",
|
||||
"duration-300",
|
||||
],
|
||||
"duration-300"
|
||||
]
|
||||
});
|
||||
|
||||
this.applyClassListFromString(root, this.highlightClass);
|
||||
@@ -245,17 +250,17 @@ export class VideoCard {
|
||||
"relative",
|
||||
"group",
|
||||
"rounded-t-lg",
|
||||
"overflow-hidden",
|
||||
"overflow-hidden"
|
||||
],
|
||||
attrs: {
|
||||
href: this.shareUrl,
|
||||
},
|
||||
href: this.shareUrl
|
||||
}
|
||||
});
|
||||
anchor.dataset.videoId = this.video.id;
|
||||
this.anchorEl = anchor;
|
||||
|
||||
const ratio = this.createElement("div", {
|
||||
classNames: ["ratio-16-9"],
|
||||
classNames: ["ratio-16-9"]
|
||||
});
|
||||
const thumbnail = this.buildThumbnail();
|
||||
ratio.appendChild(thumbnail);
|
||||
@@ -267,19 +272,19 @@ export class VideoCard {
|
||||
classNames: [
|
||||
"text-lg",
|
||||
"font-bold",
|
||||
"text-white",
|
||||
"text-text",
|
||||
"line-clamp-2",
|
||||
"hover:text-blue-400",
|
||||
"hover:text-info-strong",
|
||||
"cursor-pointer",
|
||||
"mb-3",
|
||||
"mb-3"
|
||||
],
|
||||
textContent: this.video.title,
|
||||
textContent: this.video.title
|
||||
});
|
||||
title.dataset.videoId = this.video.id;
|
||||
this.titleEl = title;
|
||||
|
||||
const header = this.createElement("div", {
|
||||
classNames: ["flex", "items-center", "justify-between"],
|
||||
classNames: ["flex", "items-center", "justify-between"]
|
||||
});
|
||||
|
||||
const authorSection = this.buildAuthorSection();
|
||||
@@ -308,7 +313,7 @@ export class VideoCard {
|
||||
classNames: ["mt-3", "text-xs", "text-amber-300"],
|
||||
attrs: { title: this.assets.unsupportedBtihMessage || "" },
|
||||
textContent:
|
||||
"WebTorrent fallback unavailable (magnet missing btih info hash)",
|
||||
"WebTorrent fallback unavailable (magnet missing btih info hash)"
|
||||
});
|
||||
warning.dataset.torrentStatus = "unsupported";
|
||||
content.appendChild(warning);
|
||||
@@ -395,7 +400,9 @@ export class VideoCard {
|
||||
const fallbackAttr =
|
||||
(typeof img.dataset.fallbackSrc === "string"
|
||||
? img.dataset.fallbackSrc.trim()
|
||||
: "") || fallbackSrc || "";
|
||||
: "") ||
|
||||
fallbackSrc ||
|
||||
"";
|
||||
|
||||
const currentSrc = img.currentSrc || img.src || "";
|
||||
const isFallback =
|
||||
@@ -442,7 +449,7 @@ export class VideoCard {
|
||||
|
||||
buildAuthorSection() {
|
||||
const wrapper = this.createElement("div", {
|
||||
classNames: ["flex", "items-center", "space-x-3"],
|
||||
classNames: ["flex", "items-center", "space-x-3"]
|
||||
});
|
||||
|
||||
const avatarWrapper = this.createElement("div", {
|
||||
@@ -450,19 +457,19 @@ export class VideoCard {
|
||||
"w-8",
|
||||
"h-8",
|
||||
"rounded-full",
|
||||
"bg-gray-700",
|
||||
"bg-panel",
|
||||
"overflow-hidden",
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-center",
|
||||
],
|
||||
"justify-center"
|
||||
]
|
||||
});
|
||||
|
||||
const avatar = this.createElement("img", {
|
||||
attrs: {
|
||||
src: "assets/svg/default-profile.svg",
|
||||
alt: "Placeholder",
|
||||
},
|
||||
alt: "Placeholder"
|
||||
}
|
||||
});
|
||||
avatar.classList.add("author-pic");
|
||||
if (this.video.pubkey) {
|
||||
@@ -475,8 +482,8 @@ export class VideoCard {
|
||||
const authorMeta = this.createElement("div", { classNames: ["min-w-0"] });
|
||||
|
||||
const authorName = this.createElement("p", {
|
||||
classNames: ["text-sm", "text-gray-400", "author-name"],
|
||||
textContent: "Loading name...",
|
||||
classNames: ["text-sm", "text-muted", "author-name"],
|
||||
textContent: "Loading name..."
|
||||
});
|
||||
if (this.video.pubkey) {
|
||||
authorName.dataset.pubkey = this.video.pubkey;
|
||||
@@ -484,25 +491,31 @@ export class VideoCard {
|
||||
authorName.style.cursor = "pointer";
|
||||
|
||||
const metadata = this.createElement("div", {
|
||||
classNames: ["flex", "items-center", "text-xs", "text-gray-500", "mt-1"],
|
||||
classNames: [
|
||||
"flex",
|
||||
"items-center",
|
||||
"text-xs",
|
||||
"text-muted-strong",
|
||||
"mt-1"
|
||||
]
|
||||
});
|
||||
|
||||
const timeEl = this.createElement("span", {
|
||||
textContent: this.timeAgo,
|
||||
textContent: this.timeAgo
|
||||
});
|
||||
metadata.appendChild(timeEl);
|
||||
this.timestampEl = timeEl;
|
||||
|
||||
if (this.pointerInfo && this.pointerInfo.key) {
|
||||
const dot = this.createElement("span", {
|
||||
classNames: ["mx-1", "text-gray-600"],
|
||||
textContent: "•",
|
||||
classNames: ["mx-1", "text-muted-strong"],
|
||||
textContent: "•"
|
||||
});
|
||||
dot.setAttribute("aria-hidden", "true");
|
||||
|
||||
const view = this.createElement("span", {
|
||||
classNames: ["view-count-text"],
|
||||
textContent: "– views",
|
||||
textContent: "– views"
|
||||
});
|
||||
view.dataset.viewCount = "";
|
||||
view.dataset.viewPointer = this.pointerInfo.key;
|
||||
@@ -528,7 +541,7 @@ export class VideoCard {
|
||||
buildControls() {
|
||||
const doc = this.document;
|
||||
const container = this.createElement("div", {
|
||||
classNames: ["flex", "items-center"],
|
||||
classNames: ["flex", "items-center"]
|
||||
});
|
||||
|
||||
const moreMenu = this.buildMoreMenu();
|
||||
@@ -538,24 +551,22 @@ export class VideoCard {
|
||||
|
||||
if (this.capabilities.canEdit) {
|
||||
const wrapper = this.createElement("div", {
|
||||
classNames: ["relative", "inline-block", "ml-3", "overflow-visible"],
|
||||
classNames: ["relative", "inline-block", "ml-3", "overflow-visible"]
|
||||
});
|
||||
const button = this.createElement("button", {
|
||||
classNames: [
|
||||
"focus-ring",
|
||||
"inline-flex",
|
||||
"items-center",
|
||||
"p-2",
|
||||
"rounded-full",
|
||||
"text-gray-400",
|
||||
"hover:text-gray-200",
|
||||
"hover:bg-gray-800",
|
||||
"focus:outline-none",
|
||||
"focus:ring-2",
|
||||
"focus:ring-blue-500",
|
||||
"text-muted",
|
||||
"hover:text-text",
|
||||
"hover:bg-panel"
|
||||
],
|
||||
attrs: {
|
||||
type: "button",
|
||||
},
|
||||
type: "button"
|
||||
}
|
||||
});
|
||||
button.dataset.settingsDropdown = String(this.index);
|
||||
|
||||
@@ -575,17 +586,17 @@ export class VideoCard {
|
||||
"w-32",
|
||||
"rounded-md",
|
||||
"shadow-lg",
|
||||
"bg-gray-800",
|
||||
"bg-panel",
|
||||
"ring-1",
|
||||
"ring-black",
|
||||
"ring-opacity-5",
|
||||
"z-50",
|
||||
],
|
||||
"z-50"
|
||||
]
|
||||
});
|
||||
dropdown.id = `settingsDropdown-${this.index}`;
|
||||
|
||||
const list = this.createElement("div", {
|
||||
classNames: ["py-1"],
|
||||
classNames: ["py-1"]
|
||||
});
|
||||
|
||||
const editButton = this.createElement("button", {
|
||||
@@ -596,10 +607,10 @@ export class VideoCard {
|
||||
"px-4",
|
||||
"py-2",
|
||||
"text-sm",
|
||||
"text-gray-100",
|
||||
"hover:bg-gray-700",
|
||||
"text-text",
|
||||
"hover:bg-panel-hover"
|
||||
],
|
||||
textContent: "Edit",
|
||||
textContent: "Edit"
|
||||
});
|
||||
editButton.dataset.editIndex = String(this.index);
|
||||
editButton.dataset.editEventId = this.video.id;
|
||||
@@ -615,11 +626,11 @@ export class VideoCard {
|
||||
"px-4",
|
||||
"py-2",
|
||||
"text-sm",
|
||||
"text-red-400",
|
||||
"hover:bg-red-700",
|
||||
"hover:text-white",
|
||||
"text-critical",
|
||||
"hover:bg-critical/20",
|
||||
"hover:text-text"
|
||||
],
|
||||
textContent: "Revert",
|
||||
textContent: "Revert"
|
||||
});
|
||||
revertButton.dataset.revertIndex = String(this.index);
|
||||
revertButton.dataset.revertEventId = this.video.id;
|
||||
@@ -636,11 +647,11 @@ export class VideoCard {
|
||||
"px-4",
|
||||
"py-2",
|
||||
"text-sm",
|
||||
"text-red-400",
|
||||
"hover:bg-red-700",
|
||||
"hover:text-white",
|
||||
"text-critical",
|
||||
"hover:bg-critical/20",
|
||||
"hover:text-text"
|
||||
],
|
||||
textContent: "Delete All",
|
||||
textContent: "Delete All"
|
||||
});
|
||||
deleteButton.dataset.deleteAllIndex = String(this.index);
|
||||
deleteButton.dataset.deleteAllEventId = this.video.id;
|
||||
@@ -663,12 +674,13 @@ export class VideoCard {
|
||||
|
||||
buildMoreMenu() {
|
||||
const wrapper = this.createElement("div", {
|
||||
classNames: ["popover", "ml-1"],
|
||||
classNames: ["popover", "ml-1"]
|
||||
});
|
||||
wrapper.dataset.moreMenuWrapper = "true";
|
||||
|
||||
const button = this.createElement("button", {
|
||||
classNames: [
|
||||
"focus-ring",
|
||||
"inline-flex",
|
||||
"items-center",
|
||||
"justify-center",
|
||||
@@ -676,19 +688,16 @@ export class VideoCard {
|
||||
"h-10",
|
||||
"p-2",
|
||||
"rounded-full",
|
||||
"text-gray-400",
|
||||
"hover:text-gray-200",
|
||||
"hover:bg-gray-800",
|
||||
"focus:outline-none",
|
||||
"focus:ring-2",
|
||||
"focus:ring-blue-500",
|
||||
"text-muted",
|
||||
"hover:text-text",
|
||||
"hover:bg-panel"
|
||||
],
|
||||
attrs: {
|
||||
type: "button",
|
||||
"aria-haspopup": "true",
|
||||
"aria-expanded": "false",
|
||||
"aria-label": "More options",
|
||||
},
|
||||
"aria-label": "More options"
|
||||
}
|
||||
});
|
||||
button.dataset.moreDropdown = String(this.index);
|
||||
button.dataset.moreMenuToggleBound = "true";
|
||||
@@ -704,8 +713,8 @@ export class VideoCard {
|
||||
attrs: {
|
||||
hidden: "",
|
||||
"data-state": "closed",
|
||||
"aria-hidden": "true",
|
||||
},
|
||||
"aria-hidden": "true"
|
||||
}
|
||||
});
|
||||
dropdown.id = `moreDropdown-${this.index}`;
|
||||
dropdown.dataset.moreMenu = "true";
|
||||
@@ -713,21 +722,18 @@ export class VideoCard {
|
||||
|
||||
const list = this.createElement("div", {
|
||||
classNames: ["menu"],
|
||||
attrs: { role: "none" },
|
||||
attrs: { role: "none" }
|
||||
});
|
||||
|
||||
const addActionButton = (text, action, extraDataset = {}, options = {}) => {
|
||||
const { variant = null } = options || {};
|
||||
const btn = this.createElement("button", {
|
||||
classNames: [
|
||||
"menu__item",
|
||||
"justify-start",
|
||||
],
|
||||
classNames: ["menu__item", "justify-start"],
|
||||
textContent: text,
|
||||
attrs: {
|
||||
type: "button",
|
||||
role: "menuitem",
|
||||
},
|
||||
role: "menuitem"
|
||||
}
|
||||
});
|
||||
if (variant) {
|
||||
btn.dataset.variant = variant;
|
||||
@@ -744,11 +750,11 @@ export class VideoCard {
|
||||
};
|
||||
|
||||
addActionButton("Open channel", "open-channel", {
|
||||
author: this.video.pubkey || "",
|
||||
author: this.video.pubkey || ""
|
||||
});
|
||||
|
||||
addActionButton("Copy link", "copy-link", {
|
||||
eventId: this.video.id || "",
|
||||
eventId: this.video.id || ""
|
||||
});
|
||||
|
||||
const pointer = Array.isArray(this.pointerInfo?.pointer)
|
||||
@@ -764,7 +770,7 @@ export class VideoCard {
|
||||
|
||||
const baseBoostDataset = {
|
||||
eventId: this.video.id || "",
|
||||
author: this.video.pubkey || "",
|
||||
author: this.video.pubkey || ""
|
||||
};
|
||||
|
||||
if (pointerType && pointerValue) {
|
||||
@@ -780,50 +786,41 @@ export class VideoCard {
|
||||
|
||||
const boostLabel = this.createElement("div", {
|
||||
classNames: ["menu__heading"],
|
||||
textContent: "Boost on Nostr…",
|
||||
textContent: "Boost on Nostr…"
|
||||
});
|
||||
list.appendChild(boostLabel);
|
||||
|
||||
addActionButton(
|
||||
"Repost (kind 6)",
|
||||
"repost-event",
|
||||
baseBoostDataset,
|
||||
);
|
||||
addActionButton("Repost (kind 6)", "repost-event", baseBoostDataset);
|
||||
|
||||
if (this.playbackUrl && this.video.isPrivate !== true) {
|
||||
const mirrorDataset = {
|
||||
...baseBoostDataset,
|
||||
url: this.playbackUrl,
|
||||
magnet: this.playbackMagnet || "",
|
||||
thumbnail: typeof this.video.thumbnail === "string" ? this.video.thumbnail : "",
|
||||
thumbnail:
|
||||
typeof this.video.thumbnail === "string" ? this.video.thumbnail : "",
|
||||
description:
|
||||
typeof this.video.description === "string" ? this.video.description : "",
|
||||
typeof this.video.description === "string"
|
||||
? this.video.description
|
||||
: "",
|
||||
title: typeof this.video.title === "string" ? this.video.title : "",
|
||||
isPrivate: this.video.isPrivate === true ? "true" : "false",
|
||||
isPrivate: this.video.isPrivate === true ? "true" : "false"
|
||||
};
|
||||
|
||||
addActionButton(
|
||||
"Mirror (kind 1063)",
|
||||
"mirror-video",
|
||||
mirrorDataset,
|
||||
);
|
||||
addActionButton("Mirror (kind 1063)", "mirror-video", mirrorDataset);
|
||||
}
|
||||
|
||||
const ensureDataset = {
|
||||
...baseBoostDataset,
|
||||
pubkey: this.video.pubkey || "",
|
||||
pubkey: this.video.pubkey || ""
|
||||
};
|
||||
|
||||
addActionButton(
|
||||
"Rebroadcast",
|
||||
"ensure-presence",
|
||||
ensureDataset,
|
||||
);
|
||||
addActionButton("Rebroadcast", "ensure-presence", ensureDataset);
|
||||
|
||||
list.appendChild(
|
||||
this.createElement("div", {
|
||||
classNames: ["menu__separator"],
|
||||
attrs: { role: "separator" },
|
||||
attrs: { role: "separator" }
|
||||
})
|
||||
);
|
||||
|
||||
@@ -839,8 +836,8 @@ export class VideoCard {
|
||||
pointerType: historyPointerType,
|
||||
pointerValue: historyPointerValue,
|
||||
pointerRelay: historyPointerRelay || "",
|
||||
reason: "remove-item",
|
||||
},
|
||||
reason: "remove-item"
|
||||
}
|
||||
);
|
||||
removeButton.setAttribute(
|
||||
"title",
|
||||
@@ -869,11 +866,7 @@ export class VideoCard {
|
||||
{ variant: "critical" }
|
||||
);
|
||||
|
||||
addActionButton(
|
||||
"Report",
|
||||
"report",
|
||||
{ eventId: this.video.id || "" },
|
||||
);
|
||||
addActionButton("Report", "report", { eventId: this.video.id || "" });
|
||||
|
||||
dropdown.appendChild(list);
|
||||
wrapper.appendChild(button);
|
||||
@@ -898,8 +891,8 @@ export class VideoCard {
|
||||
"py-1",
|
||||
"rounded",
|
||||
"transition-colors",
|
||||
"duration-200",
|
||||
],
|
||||
"duration-200"
|
||||
]
|
||||
});
|
||||
badge.setAttribute("aria-live", "polite");
|
||||
badge.setAttribute("role", "status");
|
||||
@@ -918,12 +911,15 @@ export class VideoCard {
|
||||
"py-1",
|
||||
"rounded",
|
||||
"transition-colors",
|
||||
"duration-200",
|
||||
],
|
||||
"duration-200"
|
||||
]
|
||||
});
|
||||
badge.setAttribute("aria-live", "polite");
|
||||
badge.setAttribute("role", "status");
|
||||
this.applyStreamBadgeVisualState(badge, this.getCachedStreamHealthEntry());
|
||||
this.applyStreamBadgeVisualState(
|
||||
badge,
|
||||
this.getCachedStreamHealthEntry()
|
||||
);
|
||||
this.torrentHealthBadgeEl = badge;
|
||||
pieces.push(badge);
|
||||
}
|
||||
@@ -933,7 +929,7 @@ export class VideoCard {
|
||||
}
|
||||
|
||||
const container = this.createElement("div", {
|
||||
classNames: ["mt-3", "flex", "flex-wrap", "items-center", "gap-2"],
|
||||
classNames: ["mt-3", "flex", "flex-wrap", "items-center", "gap-2"]
|
||||
});
|
||||
pieces.forEach((el) => container.appendChild(el));
|
||||
return container;
|
||||
@@ -1054,7 +1050,9 @@ export class VideoCard {
|
||||
}
|
||||
|
||||
const status =
|
||||
typeof entry?.status === "string" && entry.status ? entry.status : "checking";
|
||||
typeof entry?.status === "string" && entry.status
|
||||
? entry.status
|
||||
: "checking";
|
||||
const message =
|
||||
typeof entry?.message === "string" && entry.message
|
||||
? entry.message
|
||||
@@ -1069,7 +1067,7 @@ export class VideoCard {
|
||||
"py-1",
|
||||
"rounded",
|
||||
"transition-colors",
|
||||
"duration-200",
|
||||
"duration-200"
|
||||
];
|
||||
if (hadMargin) {
|
||||
baseClasses.unshift("mt-3");
|
||||
@@ -1082,13 +1080,13 @@ export class VideoCard {
|
||||
};
|
||||
|
||||
if (status === "healthy") {
|
||||
addClasses([...common, "bg-green-900", "text-green-200"]);
|
||||
addClasses([...common, "bg-info/20", "text-info-strong"]);
|
||||
} else if (status === "offline") {
|
||||
addClasses([...common, "bg-red-900", "text-red-200"]);
|
||||
addClasses([...common, "bg-critical/20", "text-critical"]);
|
||||
} else if (status === "unknown" || status === "timeout") {
|
||||
addClasses([...common, "bg-amber-900", "text-amber-200"]);
|
||||
addClasses([...common, "bg-warning-surface", "text-warning-strong"]);
|
||||
} else {
|
||||
addClasses([...common, "bg-gray-800", "text-gray-300"]);
|
||||
addClasses([...common, "bg-panel", "text-muted"]);
|
||||
}
|
||||
|
||||
badge.dataset.urlHealthState = status;
|
||||
@@ -1103,21 +1101,28 @@ export class VideoCard {
|
||||
}
|
||||
|
||||
const state =
|
||||
typeof entry?.state === "string" && entry.state ? entry.state : "checking";
|
||||
typeof entry?.state === "string" && entry.state
|
||||
? entry.state
|
||||
: "checking";
|
||||
const peersValue = Number.isFinite(entry?.peers)
|
||||
? Math.max(0, Number(entry.peers))
|
||||
: null;
|
||||
const reason = typeof entry?.reason === "string" && entry.reason ? entry.reason : null;
|
||||
const text = typeof entry?.text === "string" && entry.text ? entry.text : null;
|
||||
const reason =
|
||||
typeof entry?.reason === "string" && entry.reason ? entry.reason : null;
|
||||
const text =
|
||||
typeof entry?.text === "string" && entry.text ? entry.text : null;
|
||||
const tooltip =
|
||||
typeof entry?.tooltip === "string" && entry.tooltip ? entry.tooltip : null;
|
||||
const role = entry?.role === "alert" || entry?.role === "status" ? entry.role : null;
|
||||
typeof entry?.tooltip === "string" && entry.tooltip
|
||||
? entry.tooltip
|
||||
: null;
|
||||
const role =
|
||||
entry?.role === "alert" || entry?.role === "status" ? entry.role : null;
|
||||
const ariaLive =
|
||||
entry?.ariaLive === "assertive" || entry?.ariaLive === "polite"
|
||||
? entry.ariaLive
|
||||
: role === "alert"
|
||||
? "assertive"
|
||||
: "polite";
|
||||
? "assertive"
|
||||
: "polite";
|
||||
|
||||
const hadMargin = badge.classList.contains("mt-3");
|
||||
const baseClasses = [
|
||||
@@ -1128,7 +1133,7 @@ export class VideoCard {
|
||||
"py-1",
|
||||
"rounded",
|
||||
"transition-colors",
|
||||
"duration-200",
|
||||
"duration-200"
|
||||
];
|
||||
if (hadMargin) {
|
||||
baseClasses.unshift("mt-3");
|
||||
@@ -1141,34 +1146,35 @@ export class VideoCard {
|
||||
};
|
||||
|
||||
if (state === "healthy") {
|
||||
addClasses([...common, "bg-green-900", "text-green-200"]);
|
||||
addClasses([...common, "bg-info/20", "text-info-strong"]);
|
||||
} else if (state === "unhealthy") {
|
||||
addClasses([...common, "bg-red-900", "text-red-200"]);
|
||||
addClasses([...common, "bg-critical/20", "text-critical"]);
|
||||
} else {
|
||||
addClasses([...common, "bg-gray-800", "text-gray-300"]);
|
||||
addClasses([...common, "bg-panel", "text-muted"]);
|
||||
}
|
||||
|
||||
const map = {
|
||||
healthy: {
|
||||
icon: "🟢",
|
||||
aria: "WebTorrent peers available",
|
||||
aria: "WebTorrent peers available"
|
||||
},
|
||||
unhealthy: {
|
||||
icon: "🔴",
|
||||
aria: "WebTorrent peers unavailable",
|
||||
aria: "WebTorrent peers unavailable"
|
||||
},
|
||||
checking: {
|
||||
icon: "⏳",
|
||||
aria: "Checking WebTorrent peers",
|
||||
aria: "Checking WebTorrent peers"
|
||||
},
|
||||
unknown: {
|
||||
icon: "⚪",
|
||||
aria: "WebTorrent status unknown",
|
||||
},
|
||||
aria: "WebTorrent status unknown"
|
||||
}
|
||||
};
|
||||
|
||||
const descriptor = map[state] || map.unknown;
|
||||
const peersText = state === "healthy" && peersValue > 0 ? ` (${peersValue})` : "";
|
||||
const peersText =
|
||||
state === "healthy" && peersValue > 0 ? ` (${peersValue})` : "";
|
||||
const iconPrefix = descriptor.icon ? `${descriptor.icon} ` : "";
|
||||
const computedText = `${iconPrefix}WebTorrent${peersText}`;
|
||||
badge.textContent = text || computedText;
|
||||
@@ -1179,14 +1185,19 @@ export class VideoCard {
|
||||
? descriptor.aria
|
||||
: this.buildTorrentTooltip({
|
||||
peers: peersValue,
|
||||
checkedAt: Number.isFinite(entry?.checkedAt) ? entry.checkedAt : null,
|
||||
reason,
|
||||
checkedAt: Number.isFinite(entry?.checkedAt)
|
||||
? entry.checkedAt
|
||||
: null,
|
||||
reason
|
||||
}));
|
||||
|
||||
badge.setAttribute("aria-label", tooltipValue);
|
||||
badge.setAttribute("title", tooltipValue);
|
||||
badge.setAttribute("aria-live", ariaLive);
|
||||
badge.setAttribute("role", role || (state === "unhealthy" ? "alert" : "status"));
|
||||
badge.setAttribute(
|
||||
"role",
|
||||
role || (state === "unhealthy" ? "alert" : "status")
|
||||
);
|
||||
|
||||
badge.dataset.streamHealthState = state;
|
||||
if (reason) {
|
||||
@@ -1234,19 +1245,25 @@ export class VideoCard {
|
||||
: safeCount.toLocaleString();
|
||||
|
||||
const container = this.createElement("div", {
|
||||
classNames: ["flex", "items-center", "text-xs", "text-gray-500", "mt-3"],
|
||||
classNames: [
|
||||
"flex",
|
||||
"items-center",
|
||||
"text-xs",
|
||||
"text-muted-strong",
|
||||
"mt-3"
|
||||
]
|
||||
});
|
||||
container.dataset.discussionCount = this.video.id;
|
||||
container.dataset.countState = "ready";
|
||||
|
||||
const valueEl = this.createElement("span", {
|
||||
textContent: displayValue,
|
||||
textContent: displayValue
|
||||
});
|
||||
valueEl.dataset.discussionCountValue = "";
|
||||
|
||||
const labelEl = this.createElement("span", {
|
||||
classNames: ["ml-1"],
|
||||
textContent: "notes",
|
||||
textContent: "notes"
|
||||
});
|
||||
|
||||
container.appendChild(valueEl);
|
||||
@@ -1308,7 +1325,9 @@ export class VideoCard {
|
||||
if (!this.root) {
|
||||
return;
|
||||
}
|
||||
this.root.dataset.ownerIsViewer = this.capabilities.canEdit ? "true" : "false";
|
||||
this.root.dataset.ownerIsViewer = this.capabilities.canEdit
|
||||
? "true"
|
||||
: "false";
|
||||
if (this.video.pubkey) {
|
||||
this.root.dataset.ownerPubkey = this.video.pubkey;
|
||||
}
|
||||
@@ -1339,7 +1358,8 @@ export class VideoCard {
|
||||
const cachedStreamHealth = this.getCachedStreamHealthEntry();
|
||||
if (this.magnetProvided && this.magnetSupported) {
|
||||
const state =
|
||||
typeof cachedStreamHealth?.state === "string" && cachedStreamHealth.state
|
||||
typeof cachedStreamHealth?.state === "string" &&
|
||||
cachedStreamHealth.state
|
||||
? cachedStreamHealth.state
|
||||
: "checking";
|
||||
this.root.dataset.streamHealthState = state;
|
||||
@@ -1460,7 +1480,8 @@ export class VideoCard {
|
||||
}
|
||||
|
||||
const isMouseEvent =
|
||||
typeof MouseEventCtor !== "undefined" && event instanceof MouseEventCtor;
|
||||
typeof MouseEventCtor !== "undefined" &&
|
||||
event instanceof MouseEventCtor;
|
||||
if (isMouseEvent) {
|
||||
const isPrimaryClick =
|
||||
typeof event.button !== "number" || event.button === 0;
|
||||
@@ -1507,7 +1528,7 @@ export class VideoCard {
|
||||
event,
|
||||
video: this.video,
|
||||
index: this.index,
|
||||
card: this,
|
||||
card: this
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1522,7 +1543,7 @@ export class VideoCard {
|
||||
event,
|
||||
video: this.video,
|
||||
index: this.index,
|
||||
card: this,
|
||||
card: this
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1537,7 +1558,7 @@ export class VideoCard {
|
||||
event,
|
||||
video: this.video,
|
||||
index: this.index,
|
||||
card: this,
|
||||
card: this
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1572,7 +1593,7 @@ export class VideoCard {
|
||||
event,
|
||||
video: this.video,
|
||||
card: this,
|
||||
dataset,
|
||||
dataset
|
||||
});
|
||||
}
|
||||
this.closeMoreMenu();
|
||||
@@ -1590,7 +1611,7 @@ export class VideoCard {
|
||||
event,
|
||||
video: this.video,
|
||||
card: this,
|
||||
pubkey: this.video.pubkey || "",
|
||||
pubkey: this.video.pubkey || ""
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ export class VideoModal {
|
||||
removeTrackingScripts,
|
||||
setGlobalModalState,
|
||||
document: doc,
|
||||
logger,
|
||||
logger
|
||||
} = {}) {
|
||||
if (!doc) {
|
||||
throw new Error("VideoModal requires a document reference.");
|
||||
@@ -225,14 +225,12 @@ export class VideoModal {
|
||||
this.videoTitle = playerModal.querySelector("#videoTitle") || null;
|
||||
this.videoDescription =
|
||||
playerModal.querySelector("#videoDescription") || null;
|
||||
this.videoTimestamp =
|
||||
playerModal.querySelector("#videoTimestamp") || null;
|
||||
this.videoTimestamp = playerModal.querySelector("#videoTimestamp") || null;
|
||||
this.videoEditedTimestamp =
|
||||
playerModal.querySelector("#videoEditedTimestamp") || null;
|
||||
this.videoViewCountEl =
|
||||
playerModal.querySelector("#videoViewCount") || null;
|
||||
this.creatorAvatar =
|
||||
playerModal.querySelector("#creatorAvatar") || null;
|
||||
this.creatorAvatar = playerModal.querySelector("#creatorAvatar") || null;
|
||||
this.creatorName = playerModal.querySelector("#creatorName") || null;
|
||||
this.creatorNpub = playerModal.querySelector("#creatorNpub") || null;
|
||||
this.copyMagnetBtn = playerModal.querySelector("#copyMagnetBtn") || null;
|
||||
@@ -242,8 +240,7 @@ export class VideoModal {
|
||||
this.modalMoreMenu =
|
||||
playerModal.querySelector("#moreDropdown-modal") || null;
|
||||
|
||||
this.modalZapDialog =
|
||||
playerModal.querySelector("#modalZapDialog") || null;
|
||||
this.modalZapDialog = playerModal.querySelector("#modalZapDialog") || null;
|
||||
this.modalZapForm = playerModal.querySelector("#modalZapForm") || null;
|
||||
this.modalZapAmountInput =
|
||||
playerModal.querySelector("#modalZapAmountInput") || null;
|
||||
@@ -321,7 +318,7 @@ export class VideoModal {
|
||||
document: this.document,
|
||||
onRequestClose: () => {
|
||||
this.dispatch("modal:close", { video: this.activeVideo });
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
this.bindVideoEvents();
|
||||
@@ -345,13 +342,13 @@ export class VideoModal {
|
||||
const loadedHandler = () => {
|
||||
this.dispatch("playback:loadeddata", {
|
||||
video: this.modalVideo,
|
||||
active: this.activeVideo,
|
||||
active: this.activeVideo
|
||||
});
|
||||
};
|
||||
const playingHandler = () => {
|
||||
this.dispatch("playback:playing", {
|
||||
video: this.modalVideo,
|
||||
active: this.activeVideo,
|
||||
active: this.activeVideo
|
||||
});
|
||||
};
|
||||
|
||||
@@ -421,7 +418,7 @@ export class VideoModal {
|
||||
this.dispatch("video:zap", {
|
||||
video: this.activeVideo,
|
||||
amount: this.getZapAmountValue(),
|
||||
comment: this.getZapCommentValue(),
|
||||
comment: this.getZapCommentValue()
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -439,7 +436,7 @@ export class VideoModal {
|
||||
const amountHandler = () => {
|
||||
this.dispatch("zap:amount-change", {
|
||||
video: this.activeVideo,
|
||||
amount: this.getZapAmountValue(),
|
||||
amount: this.getZapAmountValue()
|
||||
});
|
||||
};
|
||||
this.modalZapAmountInput.addEventListener("input", amountHandler);
|
||||
@@ -450,7 +447,7 @@ export class VideoModal {
|
||||
const commentHandler = () => {
|
||||
this.dispatch("zap:comment-change", {
|
||||
video: this.activeVideo,
|
||||
comment: this.getZapCommentValue(),
|
||||
comment: this.getZapCommentValue()
|
||||
});
|
||||
};
|
||||
this.modalZapCommentInput.addEventListener("input", commentHandler);
|
||||
@@ -458,7 +455,10 @@ export class VideoModal {
|
||||
|
||||
if (this.creatorAvatar) {
|
||||
this.creatorAvatar.style.cursor = "pointer";
|
||||
this.creatorAvatar.addEventListener("click", this.handleCreatorNavigation);
|
||||
this.creatorAvatar.addEventListener(
|
||||
"click",
|
||||
this.handleCreatorNavigation
|
||||
);
|
||||
}
|
||||
if (this.creatorName) {
|
||||
this.creatorName.style.cursor = "pointer";
|
||||
@@ -628,10 +628,7 @@ export class VideoModal {
|
||||
return;
|
||||
}
|
||||
this.copyMagnetBtn.disabled = !enabled;
|
||||
this.copyMagnetBtn.setAttribute(
|
||||
"aria-disabled",
|
||||
(!enabled).toString()
|
||||
);
|
||||
this.copyMagnetBtn.setAttribute("aria-disabled", (!enabled).toString());
|
||||
this.copyMagnetBtn.classList.toggle("opacity-50", !enabled);
|
||||
this.copyMagnetBtn.classList.toggle("cursor-not-allowed", !enabled);
|
||||
}
|
||||
@@ -641,10 +638,7 @@ export class VideoModal {
|
||||
return;
|
||||
}
|
||||
this.shareBtn.disabled = !enabled;
|
||||
this.shareBtn.setAttribute(
|
||||
"aria-disabled",
|
||||
(!enabled).toString()
|
||||
);
|
||||
this.shareBtn.setAttribute("aria-disabled", (!enabled).toString());
|
||||
this.shareBtn.classList.toggle("opacity-50", !enabled);
|
||||
this.shareBtn.classList.toggle("cursor-not-allowed", !enabled);
|
||||
}
|
||||
@@ -789,7 +783,8 @@ export class VideoModal {
|
||||
return;
|
||||
}
|
||||
const message = typeof text === "string" ? text : "";
|
||||
this.modalZapSplitSummary.textContent = message || "Enter an amount to view the split.";
|
||||
this.modalZapSplitSummary.textContent =
|
||||
message || "Enter an amount to view the split.";
|
||||
}
|
||||
|
||||
setZapStatus(message, tone = "neutral") {
|
||||
@@ -801,26 +796,26 @@ export class VideoModal {
|
||||
const text = typeof message === "string" ? message : "";
|
||||
this.modalZapStatusEl.textContent = text;
|
||||
this.modalZapStatusEl.classList.remove(
|
||||
"text-gray-300",
|
||||
"text-gray-400",
|
||||
"text-green-300",
|
||||
"text-red-300",
|
||||
"text-yellow-300"
|
||||
"text-text",
|
||||
"text-muted",
|
||||
"text-info",
|
||||
"text-critical",
|
||||
"text-warning-strong"
|
||||
);
|
||||
|
||||
if (!text) {
|
||||
this.modalZapStatusEl.classList.add("text-gray-400");
|
||||
this.modalZapStatusEl.classList.add("text-muted");
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizedTone === "success") {
|
||||
this.modalZapStatusEl.classList.add("text-green-300");
|
||||
this.modalZapStatusEl.classList.add("text-info");
|
||||
} else if (normalizedTone === "error") {
|
||||
this.modalZapStatusEl.classList.add("text-red-300");
|
||||
this.modalZapStatusEl.classList.add("text-critical");
|
||||
} else if (normalizedTone === "warning") {
|
||||
this.modalZapStatusEl.classList.add("text-yellow-300");
|
||||
this.modalZapStatusEl.classList.add("text-warning-strong");
|
||||
} else {
|
||||
this.modalZapStatusEl.classList.add("text-gray-300");
|
||||
this.modalZapStatusEl.classList.add("text-text");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -843,7 +838,7 @@ export class VideoModal {
|
||||
if (!Array.isArray(receipts) || receipts.length === 0) {
|
||||
if (partial) {
|
||||
const empty = this.document.createElement("li");
|
||||
empty.className = "text-sm text-gray-300";
|
||||
empty.className = "text-sm text-text";
|
||||
empty.textContent = "No zap receipts available.";
|
||||
this.modalZapReceipts.appendChild(empty);
|
||||
}
|
||||
@@ -852,10 +847,11 @@ export class VideoModal {
|
||||
|
||||
receipts.forEach((receipt) => {
|
||||
const li = this.document.createElement("li");
|
||||
li.className = "rounded border border-gray-700 p-3 bg-gray-800/70";
|
||||
li.className = "rounded border border-border p-3 bg-panel/70";
|
||||
|
||||
const header = this.document.createElement("div");
|
||||
header.className = "flex items-center justify-between gap-2 text-xs text-gray-300";
|
||||
header.className =
|
||||
"flex items-center justify-between gap-2 text-xs text-text";
|
||||
|
||||
const shareType = receipt.recipientType || receipt.type || "creator";
|
||||
const shareLabel = this.document.createElement("span");
|
||||
@@ -863,8 +859,8 @@ export class VideoModal {
|
||||
shareType === "platform"
|
||||
? "Platform"
|
||||
: shareType === "creator"
|
||||
? "Creator"
|
||||
: "Lightning";
|
||||
? "Creator"
|
||||
: "Lightning";
|
||||
shareLabel.textContent = `${label} • ${Math.max(
|
||||
0,
|
||||
Math.round(Number(receipt.amount || 0))
|
||||
@@ -875,21 +871,21 @@ export class VideoModal {
|
||||
? receipt.status === "success"
|
||||
: !receipt.error;
|
||||
status.textContent = isSuccess ? "Success" : "Failed";
|
||||
status.className = isSuccess ? "text-green-300" : "text-red-300";
|
||||
status.className = isSuccess ? "text-info" : "text-critical";
|
||||
|
||||
header.appendChild(shareLabel);
|
||||
header.appendChild(status);
|
||||
li.appendChild(header);
|
||||
|
||||
const address = this.document.createElement("p");
|
||||
address.className = "mt-1 text-xs text-gray-300 break-all";
|
||||
address.className = "mt-1 text-xs text-text break-all";
|
||||
if (receipt.address) {
|
||||
address.textContent = receipt.address;
|
||||
li.appendChild(address);
|
||||
}
|
||||
|
||||
const detail = this.document.createElement("p");
|
||||
detail.className = "mt-2 text-xs text-gray-400";
|
||||
detail.className = "mt-2 text-xs text-muted";
|
||||
if (isSuccess) {
|
||||
let detailMessage = "Invoice settled.";
|
||||
const preimage = receipt.payment?.result?.preimage;
|
||||
@@ -1054,7 +1050,7 @@ export class VideoModal {
|
||||
timestamp,
|
||||
timestamps,
|
||||
viewCount,
|
||||
creator,
|
||||
creator
|
||||
} = {}) {
|
||||
if (this.videoTitle && title !== undefined) {
|
||||
this.videoTitle.textContent = title || "Untitled";
|
||||
@@ -1161,7 +1157,8 @@ export class VideoModal {
|
||||
Array.isArray(currentVideo?.pointer) &&
|
||||
currentVideo.pointer.length >= 2
|
||||
) {
|
||||
const [pointerType, pointerValue, pointerRelay] = currentVideo.pointer;
|
||||
const [pointerType, pointerValue, pointerRelay] =
|
||||
currentVideo.pointer;
|
||||
button.dataset.pointerType = pointerType || "";
|
||||
button.dataset.pointerValue = pointerValue || "";
|
||||
if (pointerRelay) {
|
||||
@@ -1234,7 +1231,8 @@ export class VideoModal {
|
||||
Array.isArray(currentVideo?.pointer) &&
|
||||
currentVideo.pointer.length >= 2
|
||||
) {
|
||||
const [pointerType, pointerValue, pointerRelay] = currentVideo.pointer;
|
||||
const [pointerType, pointerValue, pointerRelay] =
|
||||
currentVideo.pointer;
|
||||
button.dataset.pointerType = pointerType || "";
|
||||
button.dataset.pointerValue = pointerValue || "";
|
||||
if (pointerRelay) {
|
||||
|
||||
+4
-4
@@ -4,15 +4,15 @@
|
||||
This container uses a white background and dark text,
|
||||
overriding your global “body { color: … }” rule
|
||||
-->
|
||||
<div class="bg-white p-6 rounded-lg shadow-md text-gray-900">
|
||||
<h2 class="text-2xl font-bold mb-4 text-gray-700">About bitvid</h2>
|
||||
<div class="rounded-lg bg-page p-6 text-text-strong shadow-md">
|
||||
<h2 class="mb-4 text-2xl font-bold text-text-strong">About bitvid</h2>
|
||||
|
||||
<!--
|
||||
The .markdown-body class ensures the Markdown styling
|
||||
from your markdown.css can be applied here.
|
||||
-->
|
||||
<div id="markdown-container" class="markdown-body">
|
||||
<p class="text-gray-500">Loading content...</p>
|
||||
<p class="text-muted-strong">Loading content...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -44,7 +44,7 @@
|
||||
const container = document.getElementById("markdown-container");
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<p class="text-red-500">Error loading content. Please try again later.</p>
|
||||
<p class="text-critical-strong">Error loading content. Please try again later.</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
<!-- views/blog.html -->
|
||||
<section class="mb-8">
|
||||
<div class="bg-white p-6 rounded-lg shadow-md text-gray-900">
|
||||
<h2 class="text-2xl font-bold mb-4 text-gray-700">Latest Blog Posts</h2>
|
||||
<div class="rounded-lg bg-page p-6 text-text-strong shadow-md">
|
||||
<h2 class="mb-4 text-2xl font-bold text-text-strong">Latest Blog Posts</h2>
|
||||
|
||||
<div class="relative overflow-hidden" style="height: 1600px">
|
||||
<iframe
|
||||
id="blog-iframe"
|
||||
src="https://beta.bitvid.network/blog.html"
|
||||
class="w-full h-full border-none"
|
||||
class="h-full w-full border-none"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+18
-13
@@ -4,25 +4,27 @@
|
||||
<!-- Increase the default height classes -->
|
||||
<img
|
||||
id="channelBanner"
|
||||
class="w-full h-48 md:h-64 object-cover rounded-lg"
|
||||
class="h-48 w-full rounded-lg object-cover md:h-64"
|
||||
src=""
|
||||
alt="Banner"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-black bg-opacity-50 rounded-lg"></div>
|
||||
<div class="absolute inset-0 rounded-lg bg-black bg-opacity-50"></div>
|
||||
|
||||
<div
|
||||
class="absolute bottom-4 left-4 right-4 flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div class="w-16 h-16 rounded-full overflow-hidden border-2 border-white">
|
||||
<div
|
||||
class="h-16 w-16 overflow-hidden rounded-full border-2 border-border-translucent-bright"
|
||||
>
|
||||
<img
|
||||
id="channelAvatar"
|
||||
src="assets/svg/default-profile.svg"
|
||||
alt="Avatar"
|
||||
class="w-full h-full object-cover"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4 text-white">
|
||||
<div class="ml-4 text-text">
|
||||
<h2 id="channelName" class="text-xl font-bold">User Name</h2>
|
||||
<p id="channelNpub" class="text-sm opacity-80">npub...</p>
|
||||
</div>
|
||||
@@ -120,7 +122,10 @@
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
></p>
|
||||
<ul id="zapReceipts" class="space-y-2 text-xs text-muted"></ul>
|
||||
<ul
|
||||
id="zapReceipts"
|
||||
class="space-y-2 text-xs text-muted"
|
||||
></ul>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
type="submit"
|
||||
@@ -157,7 +162,7 @@
|
||||
aria-label="Channel options"
|
||||
data-more-dropdown="channel-profile"
|
||||
>
|
||||
<img src="assets/svg/ellipsis.svg" alt="More" class="w-5 h-5" />
|
||||
<img src="assets/svg/ellipsis.svg" alt="More" class="h-5 w-5" />
|
||||
</button>
|
||||
<div
|
||||
id="moreDropdown-channel-profile"
|
||||
@@ -217,26 +222,26 @@
|
||||
</div>
|
||||
|
||||
<!-- Basic profile details: about text, website, LN address -->
|
||||
<div class="profile-details px-4 mt-4">
|
||||
<p id="channelAbout" class="mb-2 text-gray-700"></p>
|
||||
<div class="profile-details mt-4 px-4">
|
||||
<p id="channelAbout" class="mb-2 text-text-strong"></p>
|
||||
<a
|
||||
id="channelWebsite"
|
||||
href="#"
|
||||
target="_blank"
|
||||
class="block text-blue-500 hover:underline mb-2"
|
||||
class="mb-2 block text-info-strong hover:underline"
|
||||
>Website</a
|
||||
>
|
||||
<p id="channelLightning" class="text-sm text-gray-500"></p>
|
||||
<p id="channelLightning" class="text-sm text-muted-strong"></p>
|
||||
</div>
|
||||
|
||||
<!-- Area for Subscribe/Unsubscribe button -->
|
||||
<div class="px-4 my-4">
|
||||
<div class="my-4 px-4">
|
||||
<div id="subscribeBtnArea" class="hidden"></div>
|
||||
</div>
|
||||
|
||||
<hr class="my-6" />
|
||||
|
||||
<h3 class="text-lg font-bold mb-4 text-gray-700">Videos by This User</h3>
|
||||
<h3 class="mb-4 text-lg font-bold text-text-strong">Videos by This User</h3>
|
||||
<div id="channelVideoList" class="bv-grid-video">
|
||||
<!-- User’s videos will go here -->
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
<!-- views/community-guidelines.html -->
|
||||
<section class="mb-8">
|
||||
<!--
|
||||
This card has a white background and shadow, similar to your other partials.
|
||||
We also add text-gray-900 to override the global body text color.
|
||||
<!--
|
||||
This card has a light background and shadow, similar to your other partials.
|
||||
We use text-text-strong to override the global body text color.
|
||||
-->
|
||||
<div class="bg-white p-6 rounded-lg shadow-md text-gray-900">
|
||||
<h2 class="text-2xl font-bold mb-4 text-gray-700">Community Guidelines</h2>
|
||||
<div class="rounded-lg bg-page p-6 text-text-strong shadow-md">
|
||||
<h2 class="mb-4 text-2xl font-bold text-text-strong">
|
||||
Community Guidelines
|
||||
</h2>
|
||||
|
||||
<!--
|
||||
The .markdown-body class can use your markdown.css
|
||||
so headings, paragraphs, etc. are styled nicely.
|
||||
-->
|
||||
<div id="markdown-container" class="markdown-body">
|
||||
<p class="text-gray-500">Loading content...</p>
|
||||
<p class="text-muted-strong">Loading content...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -45,7 +47,7 @@
|
||||
const container = document.getElementById("markdown-container");
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<p class="text-red-500">Error loading content. Please try again later.</p>
|
||||
<p class="text-critical-strong">Error loading content. Please try again later.</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<!-- views/explore.html -->
|
||||
<section>
|
||||
<h2 class="text-xl mb-4 text-gray-700">Explore</h2>
|
||||
<p class="text-gray-600">Coming Soon...</p>
|
||||
<h2 class="mb-4 text-xl text-text-strong">Explore</h2>
|
||||
<p class="text-muted-strong">Coming Soon...</p>
|
||||
</section>
|
||||
@@ -1,12 +1,12 @@
|
||||
<!-- views/getting-started.html -->
|
||||
<section class="mb-8">
|
||||
<!-- White background, shadow, and dark text to override global site color -->
|
||||
<div class="bg-white p-6 rounded-lg shadow-md text-gray-900">
|
||||
<h2 class="text-2xl font-bold mb-4 text-gray-700">Getting Started</h2>
|
||||
<!-- Light background, shadow, and dark text to override global site color -->
|
||||
<div class="rounded-lg bg-page p-6 text-text-strong shadow-md">
|
||||
<h2 class="mb-4 text-2xl font-bold text-text-strong">Getting Started</h2>
|
||||
|
||||
<!-- .markdown-body references your markdown.css to style headings, paragraphs, etc. -->
|
||||
<div id="markdown-container" class="markdown-body">
|
||||
<p class="text-gray-500">Loading content...</p>
|
||||
<p class="text-muted-strong">Loading content...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -40,7 +40,7 @@
|
||||
const container = document.getElementById("markdown-container");
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<p class="text-red-500">Error loading content. Please try again later.</p>
|
||||
<p class="text-critical-strong">Error loading content. Please try again later.</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
+21
-19
@@ -5,8 +5,8 @@
|
||||
class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"
|
||||
>
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-100">Watch History</h2>
|
||||
<p class="text-sm text-gray-400">
|
||||
<h2 class="text-xl font-semibold text-text">Watch History</h2>
|
||||
<p class="text-sm text-muted">
|
||||
Review the videos you recently played, grouped by day.
|
||||
</p>
|
||||
</div>
|
||||
@@ -14,14 +14,14 @@
|
||||
<button
|
||||
type="button"
|
||||
data-history-action="clear-cache"
|
||||
class="rounded-full bg-gray-800 px-4 py-2 text-sm font-medium text-gray-100 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
class="focus-ring rounded-full bg-panel px-4 py-2 text-sm font-medium text-text hover:bg-panel-hover"
|
||||
>
|
||||
Clear local cache
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-history-action="republish"
|
||||
class="rounded-full bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-300"
|
||||
class="focus-ring rounded-full bg-info px-4 py-2 text-sm font-medium text-text hover:bg-info-strong focus-visible:bg-info-pressed"
|
||||
>
|
||||
Republish now
|
||||
</button>
|
||||
@@ -30,12 +30,14 @@
|
||||
|
||||
<div
|
||||
id="watchHistoryPrivacyBanner"
|
||||
class="hidden rounded-lg border border-blue-500/40 bg-blue-900/30 p-4"
|
||||
class="border-info/40 bg-info/10 hidden rounded-lg border p-4"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<p id="watchHistoryPrivacyMessage" class="text-sm text-blue-100">
|
||||
<div
|
||||
class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<p id="watchHistoryPrivacyMessage" class="text-sm text-info">
|
||||
Your history stays encrypted. Share metadata with relays to sync
|
||||
thumbnails across devices?
|
||||
</p>
|
||||
@@ -43,14 +45,14 @@
|
||||
<button
|
||||
id="watchHistoryPrivacyToggle"
|
||||
type="button"
|
||||
class="rounded-full bg-blue-500 px-4 py-2 text-sm font-medium text-white hover:bg-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-200"
|
||||
class="focus-ring rounded-full bg-info px-4 py-2 text-sm font-medium text-text hover:bg-info-strong focus-visible:bg-info-pressed"
|
||||
>
|
||||
Share metadata
|
||||
</button>
|
||||
<button
|
||||
id="watchHistoryPrivacyDismiss"
|
||||
type="button"
|
||||
class="rounded-full bg-transparent px-3 py-2 text-sm font-medium text-blue-200 hover:bg-blue-800/40 focus:outline-none focus:ring-2 focus:ring-blue-300"
|
||||
class="focus-ring hover:bg-info/10 rounded-full bg-transparent px-3 py-2 text-sm font-medium text-info"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
@@ -60,14 +62,14 @@
|
||||
|
||||
<div
|
||||
id="watchHistoryInfo"
|
||||
class="rounded-lg bg-gray-800/60 p-4 text-sm text-gray-200"
|
||||
class="bg-panel/60 rounded-lg p-4 text-sm text-text"
|
||||
>
|
||||
Thumbnails and titles are hydrated locally from your device caches.
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="watchHistoryError"
|
||||
class="hidden rounded-lg border border-red-500/50 bg-red-900/40 p-3 text-sm text-red-100"
|
||||
class="border-critical/50 bg-critical/40 hidden rounded-lg border p-3 text-sm text-critical"
|
||||
role="alert"
|
||||
></div>
|
||||
</div>
|
||||
@@ -88,20 +90,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="watchHistoryGrid"
|
||||
class="grid-stack hidden"
|
||||
data-variant="cards"
|
||||
data-orientation="vertical"
|
||||
></div>
|
||||
<p id="watchHistoryEmpty" class="hidden text-gray-400 text-center py-12">
|
||||
<div
|
||||
id="watchHistoryGrid"
|
||||
class="grid-stack hidden"
|
||||
data-variant="cards"
|
||||
data-orientation="vertical"
|
||||
></div>
|
||||
<p id="watchHistoryEmpty" class="hidden py-12 text-center text-muted">
|
||||
Your watch history is empty. Watch some videos to populate this list.
|
||||
</p>
|
||||
|
||||
<button
|
||||
id="watchHistoryLoadMore"
|
||||
type="button"
|
||||
class="hidden w-full rounded-full bg-gray-800 px-4 py-2 text-sm font-medium text-gray-100 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
class="focus-ring hidden w-full rounded-full bg-panel px-4 py-2 text-sm font-medium text-text hover:bg-panel-hover"
|
||||
>
|
||||
Load more
|
||||
</button>
|
||||
|
||||
+7
-7
@@ -1,18 +1,18 @@
|
||||
<!-- views/ipns.html -->
|
||||
<section class="mb-8">
|
||||
<!--
|
||||
Added "text-gray-900" here to ensure we override
|
||||
any global white text color that might be inherited.
|
||||
<!--
|
||||
Added text-text-strong here to ensure we override
|
||||
any global white text color that might be inherited.
|
||||
-->
|
||||
<div class="bg-white p-6 rounded-lg shadow-md text-gray-900">
|
||||
<h2 class="text-2xl font-bold mb-4 text-gray-700">IPNS Gateways</h2>
|
||||
<div class="rounded-lg bg-page p-6 text-text-strong shadow-md">
|
||||
<h2 class="mb-4 text-2xl font-bold text-text-strong">IPNS Gateways</h2>
|
||||
|
||||
<!--
|
||||
"markdown-body" can be any class name; it’s just so markdown.css
|
||||
can style headings, lists, etc.
|
||||
-->
|
||||
<div id="markdown-container" class="markdown-body">
|
||||
<p class="text-gray-500">Loading content...</p>
|
||||
<p class="text-muted-strong">Loading content...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -44,7 +44,7 @@
|
||||
const container = document.getElementById("markdown-container");
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<p class="text-red-500">Error loading content. Please try again later.</p>
|
||||
<p class="text-critical-strong">Error loading content. Please try again later.</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -4,15 +4,15 @@
|
||||
This container uses a white background and dark text,
|
||||
overriding your global “body { color: … }” rule
|
||||
-->
|
||||
<div class="bg-white p-6 rounded-lg shadow-md text-gray-900">
|
||||
<h2 class="text-2xl font-bold mb-4 text-gray-700">bitvid Links</h2>
|
||||
<div class="rounded-lg bg-page p-6 text-text-strong shadow-md">
|
||||
<h2 class="mb-4 text-2xl font-bold text-text-strong">bitvid Links</h2>
|
||||
|
||||
<!--
|
||||
The .markdown-body class ensures the Markdown styling
|
||||
from your markdown.css can be applied here.
|
||||
-->
|
||||
<div id="markdown-container" class="markdown-body">
|
||||
<p class="text-gray-500">Loading content...</p>
|
||||
<p class="text-muted-strong">Loading content...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -44,7 +44,7 @@
|
||||
const container = document.getElementById("markdown-container");
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<p class="text-red-500">Error loading content. Please try again later.</p>
|
||||
<p class="text-critical-strong">Error loading content. Please try again later.</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<!-- views/most-recent-videos.html -->
|
||||
<section>
|
||||
<!-- You can have a heading if you want -->
|
||||
<h2 class="text-xl mb-4 text-gray-700">Most Recent Videos</h2>
|
||||
<h2 class="mb-4 text-xl text-text-strong">Most Recent Videos</h2>
|
||||
|
||||
<!-- Video List -->
|
||||
<div id="videoList" class="bv-grid-video">
|
||||
<div class="sidebar-loading-wrapper" role="status" aria-live="polite">
|
||||
<div class="sidebar-loading-indicator">
|
||||
<span class="status-spinner status-spinner--inline" aria-hidden="true"></span>
|
||||
<span
|
||||
class="status-spinner status-spinner--inline"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span class="sidebar-loading-text">Fetching recent videos…</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
<!-- views/roadmap.html -->
|
||||
<section class="mb-8">
|
||||
<!-- White card, dark text to override global site color -->
|
||||
<div class="bg-white p-6 rounded-lg shadow-md text-gray-900">
|
||||
<h2 class="text-2xl font-bold mb-4 text-gray-700">Roadmap</h2>
|
||||
<div class="rounded-lg bg-page p-6 text-text-strong shadow-md">
|
||||
<h2 class="mb-4 text-2xl font-bold text-text-strong">Roadmap</h2>
|
||||
|
||||
<!-- .markdown-body uses your markdown.css rules for headings, etc. -->
|
||||
<div id="markdown-container" class="markdown-body">
|
||||
<p class="text-gray-500">Loading content...</p>
|
||||
<p class="text-muted-strong">Loading content...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -39,7 +39,7 @@
|
||||
const container = document.getElementById("markdown-container");
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<p class="text-red-500">Error loading content. Please try again later.</p>
|
||||
<p class="text-critical-strong">Error loading content. Please try again later.</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<!-- views/subscriptions.html -->
|
||||
<section>
|
||||
<h2 class="text-xl mb-4">Subscriptions</h2>
|
||||
<h2 class="mb-4 text-xl">Subscriptions</h2>
|
||||
<div id="subscriptionsVideoList" class="bv-grid-video">
|
||||
<div class="sidebar-loading-wrapper" role="status" aria-live="polite">
|
||||
<div class="sidebar-loading-indicator">
|
||||
<span class="status-spinner status-spinner--inline" aria-hidden="true"></span>
|
||||
<span
|
||||
class="status-spinner status-spinner--inline"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span class="sidebar-loading-text">Fetching subscriptions…</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user