X.com Enhanced Gallery

Media viewer and download functionality for X.com

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name X.com Enhanced Gallery
// @namespace https://github.com/PiesP/xcom-enhanced-gallery
// @version 1.3.0
// @description Media viewer and download functionality for X.com
// @author PiesP
// @license MIT
// @homepageURL https://github.com/PiesP/xcom-enhanced-gallery
// @match https://x.com/*
// @match https://*.x.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_download
// @grant GM_notification
// @grant GM_xmlhttpRequest
// @grant GM_cookie
// @grant GM_addStyle
// @connect pbs.twimg.com
// @connect video.twimg.com
// @connect api.twitter.com
// @run-at document-idle
// @supportURL https://github.com/PiesP/xcom-enhanced-gallery/issues
// @icon https://abs.twimg.com/favicons/twitter.3.ico
// @compatible chrome 117+
// @compatible firefox 119+
// @compatible edge 117+
// @compatible safari 17+
// @noframes
// ==/UserScript==
// Third-party licenses: https://github.com/PiesP/xcom-enhanced-gallery/tree/master/LICENSES
(function(){if(typeof document==='undefined')return;var css=".xeg_EeShbY{display:flex;flex-direction:column;gap:var(--xse-g);padding:var(--xse-p)} .xeg_nm9B3P{gap:var(--sps)} .xeg_PI5CjL{display:flex;flex-direction:column;gap:var(--xse-cg)} .xeg_VUTt8w{gap:var(--spx)} .xeg_vhT3QS{font-size:var(--xse-lf);font-weight:var(--xse-lw);color:var(--xct-p)} .xeg_Y62M5l{font-size:var(--fsx);color:var(--xct-s);letter-spacing:.04em;text-transform:uppercase} .xeg_jpiS5y{width:100%;padding:var(--xse-sp);font-size:var(--xse-sf);color:var(--xct-p);background-color:var(--xtp-s, var(--xt-s));border:var(--bwt) solid var(--xt-b);border-radius:var(--xr-m);cursor:pointer;line-height:1.375;min-height:2.5em;transform:none;overflow:visible;transition:border-color var(--xdf) var(--xe-s), background-color var(--xdf) var(--xe-s)} .xeg_jpiS5y:hover{border-color:var(--xcb-h);background-color:var(--xtp-s, var(--xt-s))} .xeg_jpiS5y:focus, .xeg_jpiS5y:focus-visible{border-color:var(--xfic, var(--xcb-h))} .xeg_jpiS5y option{padding:.5em .75em;line-height:1.5} .xeg_4eojab{color:var(--xtt-c, var(--xct-p));cursor:pointer;font-size:.875em;font-weight:500;width:var(--xsb-m);height:var(--xsb-m);min-width:var(--xsb-m);min-height:var(--xsb-m);padding:.5em;aspect-ratio:1;position:relative;overflow:clip;border-radius:var(--xr-m);background:transparent;--toolbar-button-accent:var(--toolbar-surface-border, var(--xt-b));--toolbar-button-accent-hover:var(--xcb-h);--toolbar-button-focus-border:var( --xfic, var(--toolbar-button-accent-hover) );border:none;transition:var(--xts), transform var(--xdf) var(--xe-s)} .xeg_4eojab:focus, .xeg_4eojab:focus-visible{background:var(--xte-b, var(--xcn1))} .xeg_fLg7uD{--toolbar-surface-base:var(--xtp-s, var(--xt-s));--toolbar-surface-border:var(--xt-b);--xb-do:1;background:var(--toolbar-surface-base);border:none;border-radius:var(--xr-l);position:fixed;top:1.25em;left:50%;transform:translateX(-50%);z-index:var(--xz-t);display:var(--toolbar-display, inline-flex);align-items:center;justify-content:space-between;height:3em;padding:.5em 1em;gap:0;color:var(--xtt-c, var(--xct-p));visibility:var(--toolbar-visibility, visible);opacity:var(--toolbar-opacity, 1);pointer-events:var(--toolbar-pointer-events, auto);transition:var(--xten);user-select:none;overscroll-behavior:contain} .xeg_fLg7uD.xeg_ZpP8ej, .xeg_fLg7uD.xeg_t4eqv-{border-radius:var(--xr-l) var(--xr-l) 0 0} .xeg_fLg7uD.xeg_ojCWl4{--toolbar-opacity:1;--toolbar-pointer-events:auto;--toolbar-visibility:visible;--toolbar-display:inline-flex} .xeg_fLg7uD.xeg_Y6KFai, .xeg_fLg7uD.xeg_n-abf0, .xeg_fLg7uD.xeg_bEzlgK{--toolbar-opacity:1;--toolbar-pointer-events:auto;--toolbar-visibility:visible;--toolbar-display:inline-flex} .xeg_f8g4ur{display:flex;align-items:center;justify-content:center;width:100%;max-width:100%;overflow:hidden} .xeg_Ix3ja2{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:var(--spx);width:100%} .xeg_Ix3ja2 > *{flex:0 0 auto} .xeg_0EHq9g{display:flex;align-items:center;justify-content:center;padding-inline:var(--sps);min-width:5em} .xeg_FKnOOH{color:var(--xtt-m, var(--xct-p));margin:0 .125em}:where(.xeg_4eojab[aria-pressed=\"true\"]){background:var(--xte-bs, var(--xcn2))} .xeg_4eojab[aria-busy=\"true\"]{--button-opacity:.7;--button-transform:scale(.95)} .xeg_4eojab:disabled{--button-opacity:.5;color:var(--xtt-m, var(--xcn4));cursor:not-allowed} @media (hover:hover){.xeg_4eojab:hover:not(:disabled){background:var(--xte-b, var(--xcn1));transform:translateY(var(--xb-l))}} .xeg_4eojab:active:not(:disabled){background:var(--xte-bs, var(--xcn2));transform:translateY(0)} .xeg_njlfQM{--toolbar-button-accent:var(--xc-p);--toolbar-button-accent-hover:var(--xc-ph);--toolbar-button-focus-border:var(--xc-ph)} .xeg_AU-dPz{--toolbar-button-accent:var(--xc-s);--toolbar-button-accent-hover:var(--xc-sh);--toolbar-button-focus-border:var(--xc-sh)} .xeg_Vn14NE{--toolbar-button-accent:var(--xc-e);--toolbar-button-accent-hover:var(--xc-eh);--toolbar-button-focus-border:var(--xc-eh)} .xeg_atmJJM{position:relative} .xeg_GG869J{position:relative;gap:0;min-width:5em;min-height:2.5em;padding-bottom:.5em;box-sizing:border-box} .xeg_2cjmvu{color:var(--xtt-c, var(--xct-p));font-size:var(--xfs-m);font-weight:600;text-align:center;white-space:nowrap;line-height:1;background:transparent;padding:.25em .5em;border-radius:var(--xr-m);border:none} .xeg_JEXmPu{color:var(--xtt-c, var(--xct-p));font-weight:700} .xeg_d1et2f{color:var(--xtt-c, var(--xct-p))} .xeg_vB6NL3{position:absolute;left:50%;bottom:.125em;transform:translateX(-50%);width:3.75em;height:.125em;background:var(--xtp-pt, var(--xcn2));border-radius:var(--xr-s);overflow:clip} .xeg_LWQwIA{width:100%;height:100%;background:var(--xtt-c, var(--xct-p));border-radius:var(--xr-s);transition:var(--xtwn);transform-origin:left} .xeg_Q7dUY4, button.xeg_Q7dUY4{transition:var(--xti);position:relative;z-index:10;pointer-events:auto} .xeg_Q7dUY4[data-selected=\"true\"]{--toolbar-button-accent-hover:var(--xc-p);--toolbar-button-focus-border:var(--xc-ph)} .xeg_Q7dUY4:focus, .xeg_Q7dUY4:focus-visible{border:none} .xeg_atmJJM{position:relative} @media (prefers-reduced-transparency:reduce){.xeg_fLg7uD{background:var(--xtp-s, var(--xt-s))} [data-theme=\"dark\"] .xeg_fLg7uD{background:var(--xtp-s, var(--xt-s))}} @media (prefers-reduced-motion:reduce){.xeg_4eojab:hover:not(:disabled), .xeg_atmJJM:hover:not(:disabled), .xeg_Vn14NE:hover:not(:disabled), .xeg_Q7dUY4:hover{transform:none}}:where(.xeg_JcF-YS, .xeg_yRtvAY){position:absolute;top:100%;left:0;right:0;width:100%;display:flex;flex-direction:column;gap:var(--spm);padding:var(--spm);max-height:var(--xtp-mh);overflow:hidden;opacity:0;transform:translateY(-.5em);visibility:hidden;pointer-events:none;transition:var(--xtp-t), transform var(--xdn) var(--xe-s), visibility 0s var(--xdn);background:var( --toolbar-surface-base, var(--xtp-s, var(--xt-s)) );border-top:var(--bwt) solid var(--toolbar-surface-border, var(--xt-b));border-radius:0 0 var(--xr-l) var(--xr-l);z-index:var(--xz-tp);will-change:transform, opacity;overscroll-behavior:contain} .xeg_JcF-YS{height:var(--xtp-h)} .xeg_yRtvAY{min-height:var(--xtp-h)}:where(.xeg_JcF-YS, .xeg_yRtvAY).xeg_4a2L8u{height:auto;opacity:1;transform:translateY(0);visibility:visible;pointer-events:auto;border-top-color:var(--toolbar-surface-border, var(--xt-b));transition:var(--xtp-t), transform var(--xdn) var(--xe-s), visibility 0s 0s;z-index:var(--xz-ta)} .xeg_w56Ci4{display:flex;flex-direction:column;gap:var(--sps)} .xeg_jmjGCs{padding:var(--sps);font-size:var(--xfs-m);line-height:1.5;color:var(--xtt-c, var(--xct-p));background:var( --toolbar-surface-base, var(--xtp-s, var(--xt-s)) );border:var(--bwt) solid var(--toolbar-surface-border, var(--xt-b));border-radius:var(--xr-m);white-space:pre-wrap;word-wrap:break-word;overflow-y:auto;overscroll-behavior:contain;max-height:15em;transition:var(--xts);user-select:text;-webkit-user-select:text;cursor:text} .xeg_jmjGCs::-webkit-scrollbar{width:.5em} .xeg_jmjGCs::-webkit-scrollbar-track{background:var(--xts-t, var(--xcn2));border-radius:var(--xr-s)} .xeg_jmjGCs::-webkit-scrollbar-thumb{background:var(--xts-th, var(--xcn4));border-radius:var(--xr-s)} .xeg_jmjGCs::-webkit-scrollbar-thumb:hover{background:var(--xte-bs, var(--xcn5))}:where(.xeg_ZzP6Op, .xeg_jmjGCs a){color:var(--xc-p);text-decoration:none;overflow-wrap:break-word;transition:color var(--xdf) var(--xe-s), background-color var(--xdf) var(--xe-s);cursor:pointer}:where(.xeg_ZzP6Op, .xeg_jmjGCs a):hover{color:var(--xc-ph);text-decoration:underline}:where(.xeg_ZzP6Op, .xeg_jmjGCs a):focus,:where(.xeg_ZzP6Op, .xeg_jmjGCs a):focus-visible{background:var(--xte-bs, var(--xcn2));color:var(--xc-ph);border-radius:var(--xr-xs)}:where(.xeg_ZzP6Op, .xeg_jmjGCs a):active{color:var(--xc-p-active)} .xeg_LSA44p{container-type:size;container-name:vertical-gallery;contain:layout style paint;content-visibility:auto;contain-intrinsic-size:100vw 100vh} @layer xeg.components{:root{--xtt:opacity var(--xdt) var(--xeo), transform var(--xdt) var(--xeo), visibility 0ms;--xeg-spacing-gallery:clamp(var(--xs-s), 2.5vw, var(--xs-l));--xeg-spacing-mobile:clamp(var(--xs-xs), 2vw, var(--xs-m));--xeg-spacing-compact:clamp(.25rem, 1.5vw, var(--xs-s));--xth-o:0;--xth-v:hidden;--xth-pe:none}} @media (prefers-reduced-motion:reduce){@layer xeg.components{:root{--xtt:none}}} .xeg_X9gZRg{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:var(--xz-g, 10000);background:var(--xg-b);display:flex;flex-direction:column;transform:var(--xgh);will-change:opacity, transform;contain:layout style paint;opacity:1;visibility:visible;transition:var(--xten);cursor:default;pointer-events:auto;container-type:size;container-name:gallery-container;scroll-behavior:smooth;overscroll-behavior:none} .xeg_meO3Up{position:fixed;top:0;left:0;right:0;height:auto;z-index:var(--xz-t);opacity:var(--toolbar-opacity, 0);visibility:var(--toolbar-visibility, hidden);display:block;transition:var(--xtt);will-change:transform, opacity, visibility;contain:layout style;transform:var(--xgh);backface-visibility:var(--xbv);pointer-events:var(--toolbar-pointer-events, none);background:transparent;border:none;border-radius:0;margin:0;padding-block-end:var(--xeg-spacing-gallery)} .xeg_meO3Up:hover{--toolbar-opacity:1;--toolbar-visibility:visible;--toolbar-pointer-events:auto;--toolbar-transform-y:0} .xeg_meO3Up:focus-within{--toolbar-opacity:1;--toolbar-visibility:visible;--toolbar-pointer-events:auto;--toolbar-transform-y:0;transition:var(--xtef)} .xeg_meO3Up *{pointer-events:inherit} .xeg_meO3Up [data-gallery-element=\"settings-panel\"][data-expanded=\"true\"]{pointer-events:auto} .xeg_meO3Up:has([data-gallery-element=\"settings-panel\"][data-expanded=\"true\"]){--toolbar-opacity:1;--toolbar-visibility:visible;--toolbar-pointer-events:auto} .xeg_X9gZRg.xeg_9abgzR{cursor:none} .xeg_X9gZRg.xeg_sOsSyv[data-xeg-gallery=\"true\"][data-xeg-role=\"gallery\"] .xeg_meO3Up{--toolbar-opacity:var(--xth-o, 0);--toolbar-visibility:var(--xth-v, hidden);--toolbar-pointer-events:var(--xth-pe, none)} .xeg_X9gZRg *{pointer-events:auto} .xeg_gmRWyH{flex:1;display:flex;flex-direction:column;overflow:auto;position:relative;contain:layout style;transform:var(--xgh);overscroll-behavior:contain;scrollbar-gutter:stable;pointer-events:auto;container-type:size;container-name:items-list} .xeg_gmRWyH::-webkit-scrollbar{width:var(--xsw)} .xeg_gmRWyH::-webkit-scrollbar-track{background:transparent} .xeg_gmRWyH::-webkit-scrollbar-thumb{background:var(--xcn3);border-radius:var( --xsbr );transition:background-color var(--xdn) var(--xe-s)} .xeg_gmRWyH::-webkit-scrollbar-thumb:hover{background:var(--xcn4)} .xeg_X9gZRg.xeg_9abgzR .xeg_meO3Up{pointer-events:none;opacity:0;transition:opacity var(--xdf) var(--xeo)} .xeg_X9gZRg.xeg_9abgzR [data-xeg-role=\"items-list\"], .xeg_X9gZRg.xeg_9abgzR .xeg_gmRWyH{pointer-events:auto} .xeg_X9gZRg.xeg_yhK-Ds{justify-content:center;align-items:center} .xeg_EfVayF{position:relative;margin-bottom:var(--xs-m, 1rem);border-radius:var(--xr-l, .5rem);transition:var(--xten);contain:layout style;transform:var(--xgh)} .xeg_LxHLC8{position:relative;z-index:1} .xeg_sfF005{height:calc(100vh - var(--xeg-toolbar-height, 3.75rem));min-height:50vh;pointer-events:none;user-select:none;flex-shrink:0;background:transparent;opacity:0;contain:strict;content-visibility:auto} .xeg_X9gZRg:has(.xeg_LxHLC8){--has-active-item:1} .xeg_X9gZRg:has(.xeg_meO3Up:hover){--toolbar-interaction:1} .xeg_gC-mQz{position:fixed;top:0;left:0;right:0;height:var(--xhzh);z-index:var(--xz-th);background:transparent;pointer-events:auto} .xeg_gC-mQz:hover{z-index:var(--xz-th);background:var(--xth-bg, transparent)} .xeg_X9gZRg.xeg_Canm64:not([data-settings-expanded=\"true\"]) .xeg_gC-mQz, .xeg_X9gZRg:has(.xeg_meO3Up:hover):not([data-settings-expanded=\"true\"]) .xeg_gC-mQz{pointer-events:none} .xeg_X9gZRg.xeg_Canm64 .xeg_meO3Up, .xeg_X9gZRg:has(.xeg_gC-mQz:hover) .xeg_meO3Up{--toolbar-opacity:1;--toolbar-visibility:visible;--toolbar-pointer-events:auto;--toolbar-transform-y:0} @supports not (selector(:has(*))){.xeg_meO3Up:hover ~ .xeg_gC-mQz{pointer-events:none} .xeg_gC-mQz:hover + .xeg_meO3Up, .xeg_gC-mQz:hover ~ .xeg_meO3Up{--toolbar-opacity:1;--toolbar-visibility:visible;--toolbar-pointer-events:auto;--toolbar-transform-y:0} .xeg_X9gZRg:hover .xeg_meO3Up{--toolbar-opacity:1;--toolbar-visibility:visible;--toolbar-pointer-events:auto;--toolbar-transform-y:0}} .xeg_meO3Up [class*=\"galleryToolbar\"], .xeg_meO3Up [data-testid*=\"toolbar\"]{opacity:var(--toolbar-opacity, 0);visibility:var(--toolbar-visibility, hidden);display:flex;pointer-events:var(--toolbar-pointer-events, none)} .xeg_meO3Up button, .xeg_meO3Up [role=\"button\"], .xeg_meO3Up .xeg_e06XPV{pointer-events:auto;position:relative;z-index:10} .xeg_fwsrVX{text-align:center;color:var(--xct-s);max-inline-size:min(25rem, 90vw);padding:clamp(1.875rem, 5vw, 2.5rem)} .xeg_fwsrVX h3{margin:0 0 clamp(.75rem, 2vw, 1rem);font-size:clamp(1.25rem, 4vw, 1.5rem);font-weight:600;color:var(--xct-p);line-height:1.2} .xeg_fwsrVX p{margin:0;font-size:clamp(.875rem, 2.5vw, 1rem);line-height:1.5;color:var(--xct-t)} @container gallery-container (max-width:48rem){.xeg_gmRWyH{padding:var(--xeg-spacing-mobile);gap:var(--xeg-spacing-mobile)} .xeg_meO3Up{padding-block-end:var(--xeg-spacing-mobile)}} @container gallery-container (max-width:30rem){.xeg_gmRWyH{padding:var(--xeg-spacing-compact);gap:var(--xeg-spacing-compact)}} @media (prefers-reduced-motion:reduce){.xeg_gmRWyH{scroll-behavior:auto;will-change:auto;transform:none}} @media (prefers-reduced-motion:reduce){.xeg_meO3Up:hover, .xeg_meO3Up:focus-within{transform:none}} .xeg_X9gZRg [class*=\"galleryToolbar\"]:hover{--toolbar-opacity:1;--toolbar-pointer-events:auto} .xeg_huYoSL{position:relative;margin-bottom:var(--xs-m);margin-inline:auto;border-radius:var(--xr-l);overflow:visible;transition:var(--xti);cursor:pointer;border:.0625rem solid var(--xcb-p);background:var(--xcbg-s);padding:var(--xs-s);width:fit-content;max-width:100%;text-align:center;display:flex;flex-direction:column;align-items:center;pointer-events:auto;transform:var(--xgh);will-change:transform;contain:layout style} .xeg_huYoSL[data-fit-mode=\"original\"]{max-width:none;flex-shrink:0;width:max-content;align-self:center} .xeg_huYoSL:hover{transform:var(--xhl);background:var(--xc-se);border-color:var(--xbe)} .xeg_huYoSL:focus-visible{border-color:var(--xfic, var(--xcb-p))} .xeg_huYoSL.xeg_xm-1cY{border-color:var(--xbe, var(--xcb-s));transition:var(--xti)} .xeg_huYoSL.xeg_xm-1cY:focus-visible{border-color:var(--xfic, var(--xcb-s))} .xeg_huYoSL.xeg_luqi-C{border-color:var(--xfic, var(--xcb-p));transition:var(--xti)} .xeg_8-c8dL{position:relative;background:var(--xcbg-s);width:fit-content;max-width:100%;margin:0 auto;display:flex;justify-content:center;align-items:center} .xeg_huYoSL[data-fit-mode=\"original\"] .xeg_8-c8dL{width:auto;max-width:none} .xeg_huYoSL[data-media-loaded=\"false\"] .xeg_8-c8dL{min-height:var(--xs-3);aspect-ratio:var(--xad)} .xeg_lhkEW2{position:absolute;top:0;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;background:var(--xsk-b);min-height:var(--xs-3)} .xeg_6YYDYR{--xsp-s:var(--xs-l);--xsp-bw:.125rem;--xsp-tc:var(--xcb-p);--xsp-ic:var(--xc-p)} .xeg_FWlk5q, .xeg_GUevPQ{display:block;border-radius:var(--xr-m);object-fit:contain;pointer-events:auto;user-select:none;-webkit-user-drag:none;transform:var(--xgh);will-change:opacity;transition:opacity var(--xdn) var(--xeo)}:is(.xeg_FWlk5q, .xeg_GUevPQ).xeg_8Z3Su4{opacity:0}:is(.xeg_FWlk5q, .xeg_GUevPQ).xeg_y9iPua{opacity:1} .xeg_GUevPQ{inline-size:100%;overflow:clip}:is(.xeg_FWlk5q, .xeg_GUevPQ).xeg_yYtGJp{inline-size:auto;block-size:auto;max-inline-size:none;max-block-size:none;object-fit:none}:is(.xeg_FWlk5q, .xeg_GUevPQ).xeg_Uc0oUi{inline-size:auto;block-size:auto;max-inline-size:100%;max-block-size:none;object-fit:scale-down}:is(.xeg_FWlk5q, .xeg_GUevPQ).xeg_M9Z6MG{inline-size:auto;block-size:auto;max-inline-size:calc(100vw - var(--xs-l) * 2);max-block-size:var(--xvhc);object-fit:scale-down}:is(.xeg_FWlk5q, .xeg_GUevPQ).xeg_-Mlrhi{inline-size:auto;block-size:auto;max-inline-size:100%;max-block-size:var(--xvhc);object-fit:contain} .xeg_Wno7Ud{font-size:var(--xfs-2);margin-bottom:var(--xs-s)} .xeg_8-wisg{font-size:var(--xfs-s);text-align:center} .xeg_GswePL{position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:var(--ceb);color:var(--xc-e, var(--ce));min-height:var(--xs-3)} .xeg_huYoSL[data-media-loaded=\"false\"][data-fit-mode=\"original\"]{inline-size:min(var(--xgi-w, 100%), 100%);max-inline-size:min(var(--xgi-w, 100%), 100%);max-block-size:min( var(--xgi-h, var(--xs-5)), var(--xvhc) )} .xeg_huYoSL[data-media-loaded=\"false\"][data-fit-mode=\"original\"] .xeg_FWlk5q, .xeg_huYoSL[data-media-loaded=\"false\"][data-fit-mode=\"original\"] .xeg_GUevPQ{inline-size:min(var(--xgi-w, 100%), 100%);max-inline-size:min(var(--xgi-w, 100%), 100%);max-block-size:min( var(--xgi-h, var(--xs-5)), var(--xvhc) )} .xeg_huYoSL[data-media-loaded=\"false\"][data-has-intrinsic-size=\"true\"][data-fit-mode=\"fitHeight\"], .xeg_huYoSL[data-media-loaded=\"false\"][data-has-intrinsic-size=\"true\"][data-fit-mode=\"fitContainer\"]{--xgf-ht:min( var(--xgi-h, var(--xs-5)), var(--xvhc) );max-block-size:var(--xgf-ht);inline-size:min( 100%, calc(var(--xgf-ht) * var(--xgi-r, 1)) );max-inline-size:min( 100%, calc(var(--xgf-ht) * var(--xgi-r, 1)) )} .xeg_huYoSL[data-media-loaded=\"false\"][data-has-intrinsic-size=\"true\"][data-fit-mode=\"fitHeight\"] .xeg_FWlk5q, .xeg_huYoSL[data-media-loaded=\"false\"][data-has-intrinsic-size=\"true\"][data-fit-mode=\"fitHeight\"] .xeg_GUevPQ, .xeg_huYoSL[data-media-loaded=\"false\"][data-has-intrinsic-size=\"true\"][data-fit-mode=\"fitContainer\"] .xeg_FWlk5q, .xeg_huYoSL[data-media-loaded=\"false\"][data-has-intrinsic-size=\"true\"][data-fit-mode=\"fitContainer\"] .xeg_GUevPQ{max-block-size:var(--xgf-ht);max-inline-size:min( 100%, calc(var(--xgf-ht) * var(--xgi-r, 1)) )} @media (prefers-reduced-motion:reduce){.xeg_huYoSL{will-change:auto} .xeg_huYoSL:hover{transform:none}} @layer xeg.features{:where(.xeg-surface, .xeg-glass-surface){background:var(--xsu-b);border:var(--bwt) solid var(--xsu-br);border-radius:var(--xr-2);isolation:isolate;transition:opacity var(--xdn) var(--xe-s)}:where(.xeg-surface, .xeg-glass-surface):hover{background:var(--xsu-bh, var(--xsu-b))} .xeg-gallery-renderer[data-renderer=\"gallery\"]{display:block;width:0;height:0;overflow:visible} .xeg-gallery-overlay{display:flex;align-items:center;justify-content:center;position:fixed;inset:0;z-index:var(--xz-g, 10000);background:var(--xg-b);opacity:1;transition:opacity var(--xdn) var(--xe-s);pointer-events:auto} .xeg-gallery-container{position:relative;width:100%;height:100%;max-width:100vw;max-height:100vh;display:flex;flex-direction:column;overflow-y:auto;overflow-x:hidden}} @layer xeg.tokens, xeg.base, xeg.utilities, xeg.components, xeg.features, xeg.overrides;@layer xeg.tokens{:where(:root, .xeg-theme-scope){--cbw:oklch(1 0 0);--cbb:oklch(0 0 0);--cg0:oklch(.97 .002 206.2);--cg1:oklch(.943 .006 206.2);--cg2:oklch(.896 .006 206.2);--cg3:oklch(.796 .006 206.2);--cg4:oklch(.696 .006 286.3);--cg5:oklch(.598 .006 286.3);--cg6:oklch(.488 .006 286.3);--cg7:oklch(.378 .005 286.3);--cg8:oklch( .306 .005 282 );--cg9:oklch(.234 .006 277.8);--spx:.25rem;--sps:.5rem;--spm:1rem;--spl:1.5rem;--spxl:2rem;--sp2:3rem;--rx:.125em;--rs:.25em;--rm:.375em;--rl:.5em;--rxl:.75em;--r2:1em;--rp:1.75em;--rf:50%;--ffp:\"TwitterChirp\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;--fs2x:.6875rem;--fsx:.75rem;--fss:.875rem;--fsb:.9375rem;--fsm:1rem;--fsl:1.0625rem;--fsxl:1.125rem;--fs2:1.25rem;--fs3:1.5rem;--fwn:400;--fwm:500;--fws:600;--fwb:700;--df:150ms;--dn:250ms;--ds:300ms;--bwt:.0625rem;--bws:.125rem;--oob:.85;--lhn:1.5}} @layer xeg.tokens{:where(:root, .xeg-theme-scope){--cbp:var(--cbw);--cbs:var(--cg0);--cbu:var(--cbw);--cbe:var(--cbw);--xbgt:var(--cbu);--xt-b:var(--xcb-p);--xt-s:var(--xbgt);--xtp-s:var(--xt-s);--xcbg-s:var(--cbs);--xg-bl:var(--cbp);--xg-bd:var(--cg9);--xg-b:var(--xg-bl);--xc-bg:oklch(95% 0 0deg);--ctp:var(--cbb);--cts:var( --cg6 );--ctm:var(--cg5);--cti:var(--cbw);--cte:var(--cbw);--cto:var(--cbw);--cbd:var(--cg2);--cbm:var(--cg1);--cbsu:var(--cg1);--cbe2:var(--cg5);--cbh:var(--cg3);--xcb-p:var(--cbd);--xcb-h:var(--cbh);--xcb-s:var(--cbe2);--xgbs:var(--cbe2);--xb-bg:var(--cbu);--xb-b:var(--cbd);--xb-t:var(--ctp);--xb-bgh:var(--cbs);--xb-bh:var(--cbh);--xbb:var(--xcb-p);--xm-bl:var(--cbe);--xm-brl:var(--cbd);--xm-bd:var(--cg8);--xm-brd:var(--cbe2);--xm-b:var(--xm-bl);--xm-br:var(--xm-brl);--xtt-c:var(--xct-p);--xtt-m:var(--xct-s);--xte-b:color-mix( in oklch, var(--xbgt) 80%, var(--cbw) 20% );--xte-bs:color-mix( in oklch, var(--xbgt) 65%, var(--cbw) 35% );--xte-br:color-mix( in oklch, var(--xt-b) 85%, var(--cbw) 15% );--xtp-pt:color-mix( in oklch, var(--xte-b) 60%, var(--xte-br) 40% );--xts-t:color-mix( in oklch, var(--xte-b) 50%, var(--cbw) 50% );--xts-th:color-mix( in oklch, var(--xte-br) 80%, var(--cbw) 20% );--cs:var(--cg8);--csh:var(--cg9);--csb:var(--cg1);--ce:var(--cg8);--ceh:var(--cg9);--ceb:var(--cg1);--cw:var(--cg7);--cwb:var(--cg0);--ci:var(--cg7);--cib:var(--cg0);--xc-e:var(--ce);--xc-eh:var(--ceh);--xc-sh:var(--csh);--cp:var(--cg9);--cph:var(--cg7);--cpa:var(--cg8);--xc-p:var(--cp);--xc-s:var(--cs);--xcn1:var(--cg1);--xcn2:var(--cg2);--xcn3:var(--cg3);--xcn4:var(--cg4);--xcn5:var(--cg5);--xct-p:var(--ctp);--xct-s:var(--cts);--xct-t:var(--ctm);--xct-i:var(--cti);--xcbg-p:var(--cbp);--com:var(--cg3);--cob:var(--cg9);--xc-om:var(--com);--sbm:2.5em;--sim:1.25em;--xsb-m:var(--sbm);--tf:var(--df) cubic-bezier(.4, 0, .2, 1);--tn:var(--dn) cubic-bezier(.4, 0, .2, 1);--ts:var(--ds) cubic-bezier(.4, 0, .2, 1);--xeg-shadow-xs:none;--xeg-shadow-sm:none;--xeg-shadow-md:none;--xeg-shadow-lg:none;--xfic:var(--xcb-p);--xfs-s:.875rem;--xfs-b:1rem;--xfs-l:1.125rem;--xfw-m:500;--xfs-2:var(--fs2);--xfw-s:var(--fws);--xdf:var(--df);--xds:var(--ds);--xdn:var(--dn);--xdt:var(--dn);--xto:opacity var(--xdn) var(--xeo);--xsu-b:var(--cbu);--xsu-br:var(--cbd);--xsu-bh:var(--cbs);--xc-se:var(--cbe);--xsk-b:var(--cbs);--xbe:var(--cbe2);--xz-sb:2147483600;--xz-g:2147483600;--xz-go:2147483608;--xz-gt:2147483612;--xz-th:2147483618;--xz-t:2147483620;--xz-tp:2147483622;--xz-ta:2147483624;--xz-o:2147483630;--xz-mb:2147483640;--xz-m:2147483645;--xz-mf:2147483646;--xz-tt:2147483647;--xlr:var(--xz-g);--xeo:cubic-bezier(.4, 0, .2, 1);--xei:cubic-bezier(.4, 0, 1, 1);--xel:linear;--xlh:var(--lhn, 1.5);--xd:var(--dn);--xb-l:-.0625rem;--xo-d:.5;--xhl:translateY(-.125rem);--xr-s:var(--rs);--xr-m:var(--rm);--xr-l:var(--rl);--xr-xl:var(--rxl);--xr-2:var(--r2);--xr-f:var(--rf)}:where(:root, .xeg-theme-scope)[data-theme=\"light\"]{--cbp:var(--cbw);--ctp:var(--cbb);--cts:var(--cg6);--color-glass-bg:var(--cbu);--color-glass-border:var(--cbd);--xg-b:var(--xg-bl);--xm-b:var(--xm-bl);--xm-br:var(--xm-brl);--xcb-p:var(--cbd);--xse-g:var(--spm);--xse-p:var(--spm);--xse-cg:var(--sps);--xse-lf:var(--fss);--xse-lw:var(--fwm);--xse-sp:var(--sps);--xse-sf:var(--fss);--xad:4 / 3;--xc-bg:oklch(95% 0 0deg)}:where(:root, .xeg-theme-scope)[data-theme=\"dark\"]{--cbp:var(--cg9);--cbu:var(--cg9);--cbe:var(--cg7);--ctp:var(--cbw);--cts:var(--cg4);--color-glass-bg:var(--cg9);--color-glass-border:var(--cg6);--xbgt:var(--cg8);--xcb-p:var(--cg6);--xt-b:var(--cg6);--xcbg-s:var(--cg8);--xg-b:var(--xg-bd);--xb-bg:var(--cg8);--xb-b:var(--cg6);--xb-t:var(--ctp);--xb-bgh:var(--cg7);--xb-bh:var(--cg6);--xm-b:var(--xm-bd);--xm-br:var(--xm-brd);--xtt-c:var(--ctp);--xtt-m:var(--cg3);--xte-b:color-mix( in oklch, var(--xbgt) 85%, var(--cbb) 15% );--xte-bs:color-mix( in oklch, var(--xbgt) 70%, var(--cbb) 30% );--xte-br:color-mix( in oklch, var(--xt-b) 75%, var(--cbb) 25% );--xtp-pt:color-mix( in oklch, var(--xt-b) 65%, var(--xbgt) 35% );--xts-t:color-mix( in oklch, var(--xte-b) 80%, var(--cbb) 20% );--xts-th:color-mix( in oklch, var(--xte-br) 85%, var(--cbb) 15% );--xc-bg:oklch(20% 0 0deg);--cp:var(--cg1);--cph:var(--cg2);--cpa:var(--cg3);--xsu-b:var(--cg9);--xsu-br:var(--cg6);--xsu-bh:var(--cg8)} @media (prefers-color-scheme:dark){:where(:root, .xeg-theme-scope):not([data-theme]){--cbp:var(--cg9);--cbu:var(--cg9);--cbe:var(--cg7);--ctp:var(--cbw);--cts:var(--cg4);--color-glass-bg:var(--cg9);--color-glass-border:var(--cg6);--xbgt:var(--cg8);--xcb-p:var(--cg6);--xt-b:var(--cg6);--xcbg-s:var(--cg8);--xg-b:var(--xg-bd);--xb-bg:var(--cg8);--xb-b:var(--cg6);--xb-t:var(--ctp);--xb-bgh:var(--cg7);--xb-bh:var(--cg6);--xm-b:var(--xm-bd);--xm-br:var(--xm-brd);--xtt-c:var(--ctp);--xtt-m:var(--cg3);--xte-b:color-mix( in oklch, var(--xbgt) 85%, var(--cbb) 15% );--xte-bs:color-mix( in oklch, var(--xbgt) 70%, var(--cbb) 30% );--xte-br:color-mix( in oklch, var(--xt-b) 75%, var(--cbb) 25% );--xtp-pt:color-mix( in oklch, var(--xt-b) 65%, var(--xbgt) 35% );--xts-t:color-mix( in oklch, var(--xte-b) 80%, var(--cbb) 20% );--xts-th:color-mix( in oklch, var(--xte-br) 85%, var(--cbb) 15% );--xc-bg:oklch(20% 0 0deg);--cp:var(--cg1);--cph:var(--cg2);--cpa:var(--cg3);--xsu-b:var(--cg9);--xsu-br:var(--cg6);--xsu-bh:var(--cg8)}} @media (prefers-reduced-motion:reduce){:where(:root, .xeg-theme-scope){--xd:0ms;--xdf:0ms;--xds:0ms;--tf:0ms;--tn:0ms;--ts:0ms;--xtsn:none;--xts:none;--xten:none;--xtef:none;--xti:none;--xtwn:none;--animation-fade-in:none;--animation-fade-out:none;--animation-slide-in:none;--animation-slide-out:none;--animation-toolbar-show:none;--animation-toolbar-hide:none}}:where(:root, .xeg-theme-scope){--xeg-space-8:var(--sps);--xeg-space-12:.75rem;--xeg-space-16:var(--spm);--xse-g:var(--spm);--xse-p:var(--spm);--xse-cg:var(--sps);--xse-lf:var(--fss);--xse-lw:var( --fwb );--xse-sf:var(--fss);--xse-sp:var(--sps) var(--spm);--xtp-sh:var(--xeg-shadow-xs);--xeg-text-counter:var(--xct-p);--xeg-counter-text:var(--xeg-text-counter);--xeg-text-button:var(--xct-p);--xeg-text-button-navigation:var(--xct-p);--xeg-shadow-toolbar:var(--xeg-shadow-md);--xt-sh:var(--xeg-shadow-toolbar);--xeg-toolbar-wrapper-gradient-start:var(--xbgt);--xeg-toolbar-wrapper-gradient-mid:color-mix( in oklab, var(--xbgt) 85%, var(--cg9) 15% );--xeg-toolbar-wrapper-gradient-end:var(--xbgt);--xeg-toolbar-wrapper-border:none;--xeg-toolbar-wrapper-radius:0;--xeg-toolbar-wrapper-margin:0;--xth-bg:transparent;--xeg-neutral-100:var(--xcn1);--xeg-neutral-200:var(--xcn2);--xeg-neutral-300:var(--xcn3);--xeg-neutral-400:var(--xcn4);--xeg-comp-modal-backdrop:var(--cob);--xm-bl:var(--cbe);--xm-brl:var(--cbd);--xm-b:var(--xm-bl);--xm-br:var(--xm-brl)}} @layer xeg.tokens{:where(:root, .xeg-theme-scope){--xtp-t:height var(--xdn) var(--xe-s), opacity var(--xdf) var(--xe-s);--xtp-h:0;--xtp-mh:17.5rem;--xsw:.5rem;--xhzh:7.5rem;--xsp-sd:1rem;--xsp-bw:.125rem;--xsp-tc:color-mix(in oklch, var(--xcn4) 60%, transparent);--xsp-ic:var(--xc-p, currentColor);--xsp-d:var(--xdn);--xsp-e:var(--xel);--xtsn:background-color var(--xdn) var(--xe-s), border-color var(--xdn) var(--xe-s), color var(--xdn) var(--xe-s);--xts:background-color var(--xdf) var(--xe-s), border-color var(--xdf) var(--xe-s), color var(--xdf) var(--xe-s);--xten:transform var(--xdn) var(--xe-s), opacity var(--xdn) var(--xe-s);--xtef:transform var(--xdf) var(--xe-s), opacity var(--xdf) var(--xe-s);--xti:background-color var(--xdf) var(--xeo), border-color var(--xdf) var(--xeo), color var(--xdf) var(--xeo), transform var(--xdf) var(--xeo);--xtwn:width var(--xdn) var(--xe-s);--xisw:.125rem;--xis:var(--sim);--xs-xs:var(--spx);--xs-s:var(--sps);--xs-m:var(--spm);--xs-l:var(--spl);--xs-xl:var(--spxl);--xs-2:var(--sp2);--xs-3:3rem;--xs-5:5rem;--xvhc:90vh} @media (prefers-reduced-transparency:reduce){:where(:root, .xeg-theme-scope){--xsu-b:var(--cbp)}}} @layer xeg.components{.xeg-surface{background:var(--xsu-b);border:.0625rem solid var(--xsu-br);border-radius:var(--xr-l)} .xeg-spinner{display:inline-block;width:var(--xsp-s, var(--xsp-sd));height:var(--xsp-s, var(--xsp-sd));border-radius:var(--xr-f);border:var(--xsp-bw) solid var(--xsp-tc);border-top-color:var(--xsp-ic);animation:xeg-spin var(--xsp-d) var(--xsp-e) infinite;box-sizing:border-box} @media (prefers-reduced-motion:reduce){.xeg-spinner{animation:none}}} @layer xeg.components{@keyframes xeg-fade-in{from{opacity:0} to{opacity:1}} @keyframes xeg-fade-out{from{opacity:1} to{opacity:0}} @keyframes xeg-spin{from{transform:rotate(0deg)} to{transform:rotate(360deg)}} @keyframes xeg-slide-out-top{from{opacity:1;transform:translateY(0) scale(1)} to{opacity:0;transform:translateY(-1.25rem) scale(.95)}}} @layer xeg.tokens{:root{--xe-d:cubic-bezier(0, 0, .2, 1);--xe-a:cubic-bezier(.4, 0, 1, 1);--xe-s:cubic-bezier(.4, 0, .2, 1);--xe-e:var(--xe-d);--xgh:translate3d(0, 0, 0);--xbv:hidden}} @layer xeg.base{:where(.xeg-gallery-root, .xeg-gallery-root *),:where(.xeg-gallery-root *::before, .xeg-gallery-root *::after){box-sizing:border-box;margin:0;padding:0} .xeg-gallery-root{scroll-behavior:smooth;font-family:var( --ffp, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif );line-height:var(--xlh, 1.5);color:var(--xct-p, var(--ctp, currentColor));background:var(--xc-bg, transparent);-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} .xeg-gallery-root button{border:none;background:none;cursor:pointer;font:inherit;color:inherit} .xeg-gallery-root a{color:inherit;text-decoration:none} .xeg-gallery-root img{max-width:100%;height:auto;display:block} .xeg-gallery-root ul, .xeg-gallery-root ol{list-style:none} .xeg-gallery-root input, .xeg-gallery-root textarea, .xeg-gallery-root select{font:inherit;color:inherit;background:transparent} .xeg-gallery-root::-webkit-scrollbar{width:var(--xsw, .5rem);height:var(--xsw, .5rem)} .xeg-gallery-root::-webkit-scrollbar-track{background:transparent} .xeg-gallery-root::-webkit-scrollbar-thumb{background:var(--xcn4, oklch(60% 0 0deg));border-radius:var(--xr-s, .25em)} .xeg-gallery-root::-webkit-scrollbar-thumb:hover{background:var(--xcn5, oklch(50% 0 0deg))}} @layer xeg.utilities{.xeg-row-center{display:flex;align-items:center} .xeg-inline-center{display:inline-flex;align-items:center;justify-content:center} .xeg-gap-sm{gap:var(--xs-s)}} @layer xeg.utilities{.xeg-fade-in{animation:xeg-fade-in var(--xdn) var(--xe-e);animation-fill-mode:both} .xeg-fade-out{animation:xeg-fade-out var(--xdf) var(--xe-a);animation-fill-mode:both} @media (prefers-reduced-motion:reduce){.xeg-fade-in, .xeg-fade-out{animation:none}}} @layer xeg.features{.xeg-gallery-root{all:unset;box-sizing:border-box;font-family:var( --ffp, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif );font-size:var(--fsb, .9375rem);line-height:var(--xlh, 1.5);color:var(--xct-p, currentColor);position:fixed;inset:0;width:100vw;height:100vh;display:block;z-index:var(--xlr, 10000);isolation:isolate;contain:style paint;background:var(--xg-b, var(--cbp));pointer-events:auto;user-select:none;overscroll-behavior:contain;transform:translateZ(0);will-change:opacity, transform} .xeg-gallery-root *, .xeg-gallery-root *::before, .xeg-gallery-root *::after{box-sizing:border-box}}";var s=document.getElementById('xeg-injected-styles');if(!s){s=document.createElement('style');s.id='xeg-injected-styles';(document.head||document.documentElement).appendChild(s);}s.textContent=css;})();
(function() {
var __defProp = Object.defineProperty;
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
var __export = (all, symbols) => {
let target = {};
for (var name in all) __defProp(target, name, {
get: all[name],
enumerable: true
});
if (symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
return target;
};
var CLASSES, DATA_ATTRIBUTES, SELECTORS$1, INTERNAL_SELECTORS, SCOPES, CSS;
var init_css = __esmMin((() => {
CLASSES = {
OVERLAY: "xeg-gallery-overlay",
CONTAINER: "xeg-gallery-container",
ROOT: "xeg-gallery-root",
RENDERER: "xeg-gallery-renderer",
VERTICAL_VIEW: "xeg-vertical-gallery",
ITEM: "xeg-gallery-item"
};
DATA_ATTRIBUTES = {
GALLERY: "data-xeg-gallery",
CONTAINER: "data-xeg-gallery-container",
ELEMENT: "data-gallery-element",
ROLE: "data-xeg-role",
ROLE_COMPAT: "data-xeg-role-compat",
GALLERY_TYPE: "data-xeg-gallery-type",
GALLERY_VERSION: "data-xeg-gallery-version"
};
SELECTORS$1 = {
OVERLAY: `.${CLASSES.OVERLAY}`,
CONTAINER: `.${CLASSES.CONTAINER}`,
ROOT: `.${CLASSES.ROOT}`,
RENDERER: `.${CLASSES.RENDERER}`,
VERTICAL_VIEW: `.${CLASSES.VERTICAL_VIEW}`,
ITEM: `.${CLASSES.ITEM}`,
DATA_GALLERY: `[${DATA_ATTRIBUTES.GALLERY}]`,
DATA_CONTAINER: `[${DATA_ATTRIBUTES.CONTAINER}]`,
DATA_ELEMENT: `[${DATA_ATTRIBUTES.ELEMENT}]`,
DATA_ROLE: `[${DATA_ATTRIBUTES.ROLE}]`,
DATA_ROLE_COMPAT: `[${DATA_ATTRIBUTES.ROLE_COMPAT}]`,
DATA_GALLERY_TYPE: `[${DATA_ATTRIBUTES.GALLERY_TYPE}]`,
DATA_GALLERY_VERSION: `[${DATA_ATTRIBUTES.GALLERY_VERSION}]`,
ROLE_GALLERY: `[${DATA_ATTRIBUTES.ROLE}="gallery"]`,
ROLE_ITEMS_CONTAINER: `[${DATA_ATTRIBUTES.ROLE}="items-container"]`
};
INTERNAL_SELECTORS = [
SELECTORS$1.OVERLAY,
SELECTORS$1.CONTAINER,
SELECTORS$1.ROOT,
SELECTORS$1.RENDERER,
SELECTORS$1.VERTICAL_VIEW,
SELECTORS$1.ITEM,
SELECTORS$1.DATA_GALLERY,
SELECTORS$1.DATA_CONTAINER,
SELECTORS$1.DATA_ELEMENT,
SELECTORS$1.DATA_ROLE,
SELECTORS$1.DATA_ROLE_COMPAT,
SELECTORS$1.DATA_GALLERY_TYPE,
SELECTORS$1.DATA_GALLERY_VERSION,
SELECTORS$1.ROLE_GALLERY,
SELECTORS$1.ROLE_ITEMS_CONTAINER
];
SCOPES = { HOSTS: [
SELECTORS$1.ROOT,
SELECTORS$1.DATA_GALLERY,
SELECTORS$1.CONTAINER,
SELECTORS$1.DATA_CONTAINER
] };
CSS = {
CLASSES,
DATA_ATTRIBUTES,
SELECTORS: SELECTORS$1,
INTERNAL_SELECTORS,
SCOPES
};
}));
function safeParseInt(value, radix = 10) {
if (value == null) return 0;
const result = Number.parseInt(value, radix);
return Number.isNaN(result) ? 0 : result;
}
function clamp(value, min = 0, max = 1) {
return Math.min(Math.max(value, min), max);
}
function clampIndex(index, length) {
if (!Number.isFinite(index) || length <= 0) return 0;
return clamp(Math.floor(index), 0, length - 1);
}
function isGMUserScriptInfo(obj) {
if (obj === null || typeof obj !== "object") return false;
const objRecord = obj;
return "scriptHandler" in objRecord || Object.keys(objRecord).length > 0;
}
function cloneDeep(value) {
return globalThis.structuredClone(value);
}
function createDefaultSettings(timestamp = Date.now()) {
const settings = cloneDeep(DEFAULT_SETTINGS);
settings.lastModified = timestamp;
return settings;
}
var STATIC_DEFAULT_SETTINGS, DEFAULT_SETTINGS;
var init_default_settings = __esmMin((() => {
STATIC_DEFAULT_SETTINGS = {
gallery: {
autoScrollSpeed: 5,
infiniteScroll: true,
preloadCount: 3,
imageFitMode: "fitWidth",
theme: "auto",
animations: true,
enableKeyboardNav: true,
videoVolume: 1,
videoMuted: false
},
toolbar: { autoHideDelay: 3e3 },
download: {
filenamePattern: "original",
imageQuality: "original",
maxConcurrentDownloads: 3,
autoZip: false,
folderStructure: "flat"
},
tokens: {
autoRefresh: true,
expirationMinutes: 60
},
accessibility: {
reduceMotion: false,
screenReaderSupport: true,
focusIndicators: true
},
features: {
gallery: true,
settings: true,
download: true,
mediaExtraction: true,
accessibility: true
},
version: "1.3.0",
lastModified: 0
};
DEFAULT_SETTINGS = STATIC_DEFAULT_SETTINGS;
}));
var MEDIA;
var init_media = __esmMin((() => {
MEDIA = {
DOMAINS: [
"pbs.twimg.com",
"video.twimg.com",
"abs.twimg.com"
],
HOSTS: { MEDIA_CDN: ["pbs.twimg.com", "video.twimg.com"] },
TYPES: {
IMAGE: "image",
VIDEO: "video",
GIF: "gif"
},
EXTENSIONS: {
JPEG: "jpg",
PNG: "png",
WEBP: "webp",
GIF: "gif",
MP4: "mp4",
ZIP: "zip"
},
QUALITY: {
ORIGINAL: "orig",
LARGE: "large",
MEDIUM: "medium",
SMALL: "small"
}
};
}));
var DEFAULT_LOG_LEVEL;
var init_log_level = __esmMin((() => {
DEFAULT_LOG_LEVEL = "error";
}));
function safeJsonStringify(value) {
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
function normalizeErrorMessage(error$1) {
if (error$1 instanceof Error) {
if (error$1.message) return error$1.message;
return error$1.name || "Error";
}
if (typeof error$1 === "string") return error$1;
if (error$1 == null) return String(error$1);
if (typeof error$1 === "object") {
const record = error$1;
const message = record.message;
if (typeof message === "string") return message;
return safeJsonStringify(record);
}
return String(error$1);
}
function getErrorMessage(error$1) {
if (error$1 instanceof Error) return error$1.message;
if (typeof error$1 === "string") return error$1;
if (error$1 == null) return "";
if (typeof error$1 === "object") {
const record = error$1;
if ("message" in record) return String(record.message ?? "");
return String(record);
}
return String(error$1);
}
function createLogger(config = {}) {
return createLoggerImpl(config);
}
function createScopedLogger(scope, config = {}) {
return createScopedLoggerImpl(scope, config);
}
var BASE_PREFIX, createProdLogger, createLoggerImpl, createScopedLoggerImpl, logger;
var init_logger = __esmMin((() => {
init_log_level();
BASE_PREFIX = "[XEG]";
createProdLogger = (config) => {
const noop = () => {};
const hasConsole = typeof console !== "undefined";
return {
info: noop,
debug: noop,
trace: noop,
warn: noop,
error: (...args) => {
if (hasConsole) console.error(config.prefix, ...args);
}
};
};
{
const DEFAULT_CONFIG$1 = {
level: DEFAULT_LOG_LEVEL,
prefix: BASE_PREFIX
};
createLoggerImpl = (config = {}) => {
return createProdLogger({
...DEFAULT_CONFIG$1,
...config
});
};
createScopedLoggerImpl = (scope, config = {}) => createLoggerImpl({
...config,
prefix: `${config.prefix ?? BASE_PREFIX} [${scope}]`
});
}
logger = createLogger({ level: DEFAULT_LOG_LEVEL });
}));
var init_logging = __esmMin((() => {
init_logger();
}));
function warnInvalidSelectorOnce(selector, error$1) {}
function queryWithFallback(container$2, primarySelector, fallbacks = []) {
try {
const primary = container$2.querySelector(primarySelector);
if (primary) return primary;
} catch (error$1) {
warnInvalidSelectorOnce(primarySelector, error$1);
}
for (const fallback of fallbacks) try {
const element = container$2.querySelector(fallback);
if (element) return element;
} catch (error$1) {
warnInvalidSelectorOnce(fallback, error$1);
}
return null;
}
function queryAllWithFallback(container$2, selectors) {
const seen = /* @__PURE__ */ new WeakSet();
const results = [];
for (const selector of selectors) try {
const elements = container$2.querySelectorAll(selector);
for (const element of elements) if (!seen.has(element)) {
seen.add(element);
results.push(element);
}
} catch (error$1) {
warnInvalidSelectorOnce(selector, error$1);
}
return results;
}
var GALLERY_SELECTORS$1, TWEET_SELECTOR, TWEET_ARTICLE_SELECTOR, TWEET_PHOTO_SELECTOR, TWEET_TEXT_SELECTOR, VIDEO_PLAYER_SELECTOR, STATUS_LINK_SELECTOR, TWITTER_IMAGE_SELECTOR, TWITTER_VIDEO_SELECTOR, TWITTER_MEDIA_SELECTOR, GALLERY_OVERLAY_SELECTOR, GALLERY_CONTAINER_SELECTOR, FALLBACK_SELECTORS, STABLE_TWEET_CONTAINERS_SELECTORS, STABLE_MEDIA_CONTAINERS_SELECTORS, STABLE_VIDEO_CONTAINERS_SELECTORS, STABLE_IMAGE_CONTAINERS_SELECTORS, STABLE_MEDIA_LINKS_SELECTORS, STABLE_MEDIA_VIEWERS_SELECTORS, STABLE_MEDIA_PLAYERS_SELECTORS;
var init_selectors$1 = __esmMin((() => {
init_css();
GALLERY_SELECTORS$1 = CSS.SELECTORS;
TWEET_SELECTOR = "article[data-testid=\"tweet\"]";
TWEET_ARTICLE_SELECTOR = "[data-testid=\"tweet\"], article";
TWEET_PHOTO_SELECTOR = "[data-testid=\"tweetPhoto\"]";
TWEET_TEXT_SELECTOR = "[data-testid=\"tweetText\"]";
VIDEO_PLAYER_SELECTOR = "[data-testid=\"videoPlayer\"]";
STATUS_LINK_SELECTOR = "a[href*=\"/status/\"]";
TWITTER_IMAGE_SELECTOR = "img[src*=\"pbs.twimg.com\"]";
TWITTER_VIDEO_SELECTOR = "video[src*=\"video.twimg.com\"]";
TWITTER_MEDIA_SELECTOR = "img[src*=\"pbs.twimg.com\"], video[src*=\"video.twimg.com\"]";
GALLERY_OVERLAY_SELECTOR = GALLERY_SELECTORS$1.OVERLAY;
GALLERY_CONTAINER_SELECTOR = GALLERY_SELECTORS$1.CONTAINER;
FALLBACK_SELECTORS = {
TWEET: [
"article[role=\"article\"]",
"article[aria-labelledby]",
"[data-testid=\"cellInnerDiv\"] > div > article"
],
TWEET_PHOTO: [
"[aria-label*=\"Image\"]",
"[role=\"img\"][aria-label]",
"a[href*=\"/photo/\"] img",
"div[aria-label] img[src*=\"pbs.twimg.com\"]"
],
TWEET_TEXT: [
"[lang][dir=\"auto\"]",
"div[data-testid=\"tweetText\"]",
"article [lang]"
],
VIDEO_PLAYER: [
"[aria-label*=\"Video\"]",
"[role=\"application\"] video",
"div[data-testid=\"videoComponent\"]",
"video[src*=\"video.twimg.com\"]"
],
MODAL: [
"[aria-modal=\"true\"]",
"[role=\"dialog\"]",
"[aria-label*=\"Close\"]"
],
MEDIA_VIEWER: [
"[aria-roledescription=\"carousel\"]",
"[aria-label*=\"Gallery\"]",
"[role=\"dialog\"][aria-modal=\"true\"]"
]
};
STABLE_TWEET_CONTAINERS_SELECTORS = [
"article[data-testid=\"tweet\"]",
"article[role=\"article\"]",
"[data-testid=\"cellInnerDiv\"] article"
];
STABLE_MEDIA_CONTAINERS_SELECTORS = [
"[data-testid=\"tweetPhoto\"]",
"[data-testid=\"videoPlayer\"]",
"[aria-label*=\"Image\"]",
"[aria-label*=\"Video\"]",
"a[href*=\"/photo/\"] > div"
];
STABLE_VIDEO_CONTAINERS_SELECTORS = [
"[data-testid=\"videoPlayer\"]",
"video",
"[aria-label*=\"Video\"]",
"[data-testid=\"videoComponent\"]"
];
STABLE_IMAGE_CONTAINERS_SELECTORS = [
"[data-testid=\"tweetPhoto\"]",
"img[src*=\"pbs.twimg.com\"]",
"[aria-label*=\"Image\"] img",
"a[href*=\"/photo/\"] img"
];
STABLE_MEDIA_LINKS_SELECTORS = [
"a[href*=\"/status/\"][href*=\"/photo/\"]",
"a[href*=\"/status/\"][href*=\"/video/\"]",
"a[href*=\"/photo/\"][aria-label]",
"a[href*=\"/video/\"][aria-label]"
];
STABLE_MEDIA_VIEWERS_SELECTORS = [
"[data-testid=\"photoViewer\"]",
"[aria-modal=\"true\"][data-testid=\"Drawer\"]",
"[aria-roledescription=\"carousel\"]",
"[role=\"dialog\"][aria-modal=\"true\"]",
"[aria-label*=\"Gallery\"]"
];
STABLE_MEDIA_PLAYERS_SELECTORS = [
"[data-testid=\"videoPlayer\"]",
"video",
"[role=\"application\"] video"
];
}));
var SERVICE_KEYS;
var init_service_keys = __esmMin((() => {
SERVICE_KEYS = {
LANGUAGE: "core.language",
GALLERY_DOWNLOAD: "gallery.download",
GALLERY_RENDERER: "gallery.renderer",
MEDIA_FILENAME: "media.filename",
MEDIA_SERVICE: "media.service",
SETTINGS: "settings.manager",
THEME: "ui.theme"
};
}));
var APP_SETTINGS_STORAGE_KEY;
var init_storage = __esmMin((() => {
APP_SETTINGS_STORAGE_KEY = "xeg-app-settings";
}));
var TWITTER_API_CONFIG;
var init_twitter_api$1 = __esmMin((() => {
TWITTER_API_CONFIG = {
GUEST_AUTHORIZATION: "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA",
TWEET_RESULT_BY_REST_ID_QUERY_ID: "zAz9764BcLZOJ0JU2wrd1A",
USER_BY_SCREEN_NAME_QUERY_ID: "1VOOyvKkiI3FMmkeDNxM9A",
SUPPORTED_HOSTS: ["x.com", "twitter.com"],
DEFAULT_HOST: "x.com",
CACHE_TTL_MS: 12e4
};
}));
var VIDEO_CONTROL_SELECTORS, VIDEO_CONTROL_DATASET_PREFIXES, VIDEO_CONTROL_ROLES, VIDEO_CONTROL_ARIA_TOKENS, SYSTEM_PAGES, VIEW_MODES;
var init_video_controls = __esmMin((() => {
VIDEO_CONTROL_SELECTORS = [
"[data-testid=\"playButton\"]",
"[data-testid=\"pauseButton\"]",
"[data-testid=\"muteButton\"]",
"[data-testid=\"unmuteButton\"]",
"[data-testid=\"volumeButton\"]",
"[data-testid=\"volumeSlider\"]",
"[data-testid=\"volumeControl\"]",
"[data-testid=\"videoProgressSlider\"]",
"[data-testid=\"seekBar\"]",
"[data-testid=\"scrubber\"]",
"[data-testid=\"videoPlayer\"] [role=\"slider\"]",
"[data-testid=\"videoPlayer\"] [role=\"progressbar\"]",
"[data-testid=\"videoPlayer\"] [data-testid=\"SliderRail\"]",
"[data-testid=\"videoPlayer\"] input[type=\"range\"]",
"[data-testid=\"videoPlayer\"] [aria-label*=\"Volume\"]",
".video-controls button",
".video-progress button"
];
VIDEO_CONTROL_DATASET_PREFIXES = [
"play",
"pause",
"mute",
"unmute",
"volume",
"slider",
"seek",
"scrub",
"progress"
];
VIDEO_CONTROL_ROLES = ["slider", "progressbar"];
VIDEO_CONTROL_ARIA_TOKENS = [
"volume",
"mute",
"unmute",
"seek",
"scrub",
"timeline",
"progress"
];
SYSTEM_PAGES = [
"home",
"explore",
"notifications",
"messages",
"bookmarks",
"lists",
"profile",
"settings",
"help",
"search",
"login",
"signup"
];
VIEW_MODES = ["verticalList"];
}));
var constants_exports = /* @__PURE__ */ __export({
APP_SETTINGS_STORAGE_KEY: () => APP_SETTINGS_STORAGE_KEY,
CSS: () => CSS,
DEFAULT_SETTINGS: () => DEFAULT_SETTINGS,
FALLBACK_SELECTORS: () => FALLBACK_SELECTORS,
GALLERY_CONTAINER_SELECTOR: () => GALLERY_CONTAINER_SELECTOR,
GALLERY_OVERLAY_SELECTOR: () => GALLERY_OVERLAY_SELECTOR,
MEDIA: () => MEDIA,
SERVICE_KEYS: () => SERVICE_KEYS,
STABLE_IMAGE_CONTAINERS_SELECTORS: () => STABLE_IMAGE_CONTAINERS_SELECTORS,
STABLE_MEDIA_CONTAINERS_SELECTORS: () => STABLE_MEDIA_CONTAINERS_SELECTORS,
STABLE_MEDIA_LINKS_SELECTORS: () => STABLE_MEDIA_LINKS_SELECTORS,
STABLE_MEDIA_PLAYERS_SELECTORS: () => STABLE_MEDIA_PLAYERS_SELECTORS,
STABLE_MEDIA_VIEWERS_SELECTORS: () => STABLE_MEDIA_VIEWERS_SELECTORS,
STABLE_TWEET_CONTAINERS_SELECTORS: () => STABLE_TWEET_CONTAINERS_SELECTORS,
STABLE_VIDEO_CONTAINERS_SELECTORS: () => STABLE_VIDEO_CONTAINERS_SELECTORS,
STATUS_LINK_SELECTOR: () => STATUS_LINK_SELECTOR,
SYSTEM_PAGES: () => SYSTEM_PAGES,
TWEET_ARTICLE_SELECTOR: () => TWEET_ARTICLE_SELECTOR,
TWEET_PHOTO_SELECTOR: () => TWEET_PHOTO_SELECTOR,
TWEET_SELECTOR: () => TWEET_SELECTOR,
TWEET_TEXT_SELECTOR: () => TWEET_TEXT_SELECTOR,
TWITTER_API_CONFIG: () => TWITTER_API_CONFIG,
TWITTER_IMAGE_SELECTOR: () => TWITTER_IMAGE_SELECTOR,
TWITTER_MEDIA_SELECTOR: () => TWITTER_MEDIA_SELECTOR,
TWITTER_VIDEO_SELECTOR: () => TWITTER_VIDEO_SELECTOR,
VIDEO_CONTROL_ARIA_TOKENS: () => VIDEO_CONTROL_ARIA_TOKENS,
VIDEO_CONTROL_DATASET_PREFIXES: () => VIDEO_CONTROL_DATASET_PREFIXES,
VIDEO_CONTROL_ROLES: () => VIDEO_CONTROL_ROLES,
VIDEO_CONTROL_SELECTORS: () => VIDEO_CONTROL_SELECTORS,
VIDEO_PLAYER_SELECTOR: () => VIDEO_PLAYER_SELECTOR,
VIEW_MODES: () => VIEW_MODES,
createDefaultSettings: () => createDefaultSettings,
queryAllWithFallback: () => queryAllWithFallback,
queryWithFallback: () => queryWithFallback
}, 1);
var init_constants$1 = __esmMin((() => {
init_css();
init_default_settings();
init_media();
init_selectors$1();
init_service_keys();
init_storage();
init_twitter_api$1();
init_video_controls();
}));
function createSingleton(factory) {
let hasInstance = false;
let instance;
return {
get() {
if (!hasInstance) {
instance = factory();
hasInstance = true;
}
return instance;
},
peek() {
return hasInstance ? instance : null;
},
reset() {
hasInstance = false;
}
};
}
var service_manager_exports = /* @__PURE__ */ __export({
CoreService: () => CoreService,
serviceManager: () => serviceManager
}, 1);
function isDisposable(value) {
return value !== null && typeof value === "object" && "destroy" in value && typeof value.destroy === "function";
}
var CoreService, serviceManager;
var init_service_manager = __esmMin((() => {
init_logging();
CoreService = class CoreService {
static singleton = createSingleton(() => new CoreService());
services = /* @__PURE__ */ new Map();
constructor() {}
static getInstance() {
return CoreService.singleton.get();
}
register(key, instance, options) {
const allowOverride = options?.allowOverride ?? false;
const onDuplicate = options?.onDuplicate ?? "warn";
if (this.services.has(key) && !allowOverride) {
const message = `[CoreService] Service key "${key}" already registered, skipping. Use { allowOverride: true } to replace existing service.`;
if (onDuplicate === "throw") throw new Error(message);
if (onDuplicate === "warn") ;
return;
}
this.services.set(key, instance);
}
get(key) {
if (this.services.has(key)) return this.services.get(key);
throw new Error(`Service not found: ${key}`);
}
tryGet(key) {
if (this.services.has(key)) return this.services.get(key);
return null;
}
has(key) {
return this.services.has(key);
}
getRegisteredServices() {
return Array.from(this.services.keys());
}
cleanup() {
this.services.forEach((service) => {
try {
if (isDisposable(service)) service.destroy();
} catch (e) {
logger.error("Service cleanup failed", e);
}
});
this.services.clear();
}
reset() {
this.cleanup();
}
};
serviceManager = CoreService.getInstance();
}));
function isBaseLanguageCode(value) {
return value != null && LANGUAGE_CODE_LOOKUP.has(value);
}
var LANGUAGE_CODES, LANGUAGE_CODE_LOOKUP;
var init_language_types = __esmMin((() => {
LANGUAGE_CODES = [
"en",
"ko",
"ja"
];
LANGUAGE_CODE_LOOKUP = new Set(LANGUAGE_CODES);
}));
var en;
var init_en = __esmMin((() => {
en = {
toolbar: {
previous: "Previous",
next: "Next",
download: "Download",
downloadAll: "Download ZIP",
settings: "Settings",
close: "Close",
tweetText: "Tweet text",
tweetTextPanel: "Tweet text panel"
},
settings: {
title: "Settings",
theme: "Theme",
language: "Language",
themeAuto: "Auto",
themeLight: "Light",
themeDark: "Dark",
languageAuto: "Auto / 자동 / 自動",
languageKo: "한국어",
languageEn: "English",
languageJa: "日本語",
close: "Close",
gallery: { sectionTitle: "Gallery" }
},
messages: {
errorBoundary: {
title: "An error occurred",
body: "An unexpected error occurred: {error}"
},
keyboardHelp: {
title: "Keyboard shortcuts",
navPrevious: "ArrowLeft: Previous media",
navNext: "ArrowRight: Next media",
close: "Escape: Close gallery",
toggleHelp: "?: Show this help"
},
download: {
single: { error: {
title: "Download Failed",
body: "Could not download the file: {error}"
} },
allFailed: {
title: "Download Failed",
body: "Failed to download all items."
},
partial: {
title: "Partial Failure",
body: "Failed to download {count} items."
},
retry: {
action: "Retry",
success: {
title: "Retry Successful",
body: "Successfully downloaded all previously failed items."
}
},
cancelled: {
title: "Download Cancelled",
body: "The requested download was cancelled."
}
},
gallery: {
emptyTitle: "No media available",
emptyDescription: "There are no images or videos to display.",
mediaItemLabel: "Media {index}: {filename}",
failedToLoadImage: "Failed to load {type}"
}
}
};
}));
var ko_exports = /* @__PURE__ */ __export({ ko: () => ko }, 1);
var ko;
var init_ko = __esmMin((() => {
ko = {
toolbar: {
previous: "이전",
next: "다음",
download: "다운로드",
downloadAll: "ZIP 다운로드",
settings: "설정",
close: "닫기",
tweetText: "트윗 텍스트",
tweetTextPanel: "트윗 텍스트 패널"
},
settings: {
title: "설정",
theme: "테마",
language: "언어",
themeAuto: "자동",
themeLight: "라이트",
themeDark: "다크",
languageAuto: "자동 / Auto / 자동",
languageKo: "한국어",
languageEn: "English",
languageJa: "日本語",
close: "닫기",
gallery: { sectionTitle: "갤러리" }
},
messages: {
errorBoundary: {
title: "오류가 발생했습니다",
body: "예상치 못한 오류가 발생했습니다: {error}"
},
keyboardHelp: {
title: "키보드 단축키",
navPrevious: "ArrowLeft: 이전 미디어",
navNext: "ArrowRight: 다음 미디어",
close: "Escape: 갤러리 닫기",
toggleHelp: "?: 이 도움말 표시"
},
download: {
single: { error: {
title: "다운로드 실패",
body: "파일을 가져올 수 없습니다: {error}"
} },
allFailed: {
title: "다운로드 실패",
body: "모든 항목을 다운로드할 수 없었습니다."
},
partial: {
title: "일부 실패",
body: "{count}개 항목을 가져올 수 없었습니다."
},
retry: {
action: "다시 시도",
success: {
title: "다시 시도 성공",
body: "실패했던 모든 항목을 가져왔습니다."
}
},
cancelled: {
title: "다운로드가 취소되었습니다",
body: "요청한 다운로드가 취소되었습니다."
}
},
gallery: {
emptyTitle: "미디어 없음",
emptyDescription: "표시할 이미지 또는 동영상이 없습니다.",
mediaItemLabel: "미디어 {index}: {filename}",
failedToLoadImage: "{type} 로드 실패"
}
}
};
}));
var ja_exports = /* @__PURE__ */ __export({ ja: () => ja }, 1);
var ja;
var init_ja = __esmMin((() => {
ja = {
toolbar: {
previous: "前へ",
next: "次へ",
download: "ダウンロード",
downloadAll: "ZIPダウンロード",
settings: "設定",
close: "閉じる",
tweetText: "ツイートテキスト",
tweetTextPanel: "ツイートテキストパネル"
},
settings: {
title: "設定",
theme: "テーマ",
language: "言語",
themeAuto: "自動",
themeLight: "ライト",
themeDark: "ダーク",
languageAuto: "自動 / Auto / 자동",
languageKo: "한국어",
languageEn: "English",
languageJa: "日本語",
close: "閉じる",
gallery: { sectionTitle: "ギャラリー" }
},
messages: {
errorBoundary: {
title: "エラーが発生しました",
body: "予期しないエラーが発生しました: {error}"
},
keyboardHelp: {
title: "キーボードショートカット",
navPrevious: "ArrowLeft: 前のメディア",
navNext: "ArrowRight: 次のメディア",
close: "Escape: ギャラリーを閉じる",
toggleHelp: "?: このヘルプを表示"
},
download: {
single: { error: {
title: "ダウンロード失敗",
body: "ファイルを取得できません: {error}"
} },
allFailed: {
title: "ダウンロード失敗",
body: "すべての項目をダウンロードできませんでした。"
},
partial: {
title: "一部失敗",
body: "{count}個の項目を取得できませんでした。"
},
retry: {
action: "再試行",
success: {
title: "再試行成功",
body: "失敗していた項目をすべて取得しました。"
}
},
cancelled: {
title: "ダウンロードがキャンセルされました",
body: "要求したダウンロードはキャンセルされました。"
}
},
gallery: {
emptyTitle: "メディアがありません",
emptyDescription: "表示する画像や動画がありません。",
mediaItemLabel: "メディア {index}: {filename}",
failedToLoadImage: "{type} の読み込みに失敗しました"
}
}
};
})), TRANSLATION_REGISTRY, LAZY_LANGUAGE_LOADERS;
var init_translation_registry = __esmMin((() => {
init_en();
TRANSLATION_REGISTRY = Object.freeze({ en });
LAZY_LANGUAGE_LOADERS = {
ko: async () => {
const { ko: ko$1 } = await Promise.resolve().then(() => (init_ko(), ko_exports));
return ko$1;
},
ja: async () => {
const { ja: ja$1 } = await Promise.resolve().then(() => (init_ja(), ja_exports));
return ja$1;
}
};
}));
var TranslationCatalog;
var init_translation_catalog = __esmMin((() => {
init_language_types();
init_translation_registry();
TranslationCatalog = class {
bundles = {};
fallbackLanguage;
loadingPromises = {};
constructor(options = {}) {
const { bundles = TRANSLATION_REGISTRY, fallbackLanguage = "en" } = options;
this.fallbackLanguage = fallbackLanguage;
this.registerBundles(bundles);
if (!this.bundles[this.fallbackLanguage]) throw new Error(`Missing fallback language bundle: ${this.fallbackLanguage}`);
}
register(language, strings) {
this.bundles[language] = strings;
}
has(language) {
return Boolean(this.bundles[language]);
}
get(language) {
if (language) {
const strings = this.bundles[language];
if (strings) return strings;
}
return this.bundles[this.fallbackLanguage];
}
async ensureLanguage(language) {
if (this.bundles[language]) return false;
const loader = LAZY_LANGUAGE_LOADERS[language];
if (!loader) return false;
const existingPromise = this.loadingPromises[language];
if (existingPromise) {
await existingPromise;
return true;
}
const loadPromise = (async () => {
const strings = await loader();
this.register(language, strings);
})();
this.loadingPromises[language] = loadPromise;
try {
await loadPromise;
return true;
} finally {
delete this.loadingPromises[language];
}
}
canLazyLoad(language) {
return Boolean(LAZY_LANGUAGE_LOADERS[language]);
}
keys() {
return Object.keys(this.bundles);
}
availableLanguages() {
return [...LANGUAGE_CODES];
}
toRecord() {
return { ...this.bundles };
}
registerBundles(bundles) {
for (const [language, strings] of Object.entries(bundles)) {
if (!strings) continue;
this.register(language, strings);
}
}
};
}));
function resolveTranslationValue(dictionary, key) {
const segments = key.split(".");
let current = dictionary;
for (const segment of segments) {
if (!current || typeof current !== "object") return;
current = current[segment];
}
return typeof current === "string" ? current : void 0;
}
var Translator;
var init_translator = __esmMin((() => {
init_language_types();
init_translation_catalog();
Translator = class {
catalog;
constructor(options = {}) {
this.catalog = options instanceof TranslationCatalog ? options : new TranslationCatalog(options);
}
get languages() {
return [...LANGUAGE_CODES];
}
async ensureLanguage(language) {
await this.catalog.ensureLanguage(language);
}
translate(language, key, params) {
const template$1 = resolveTranslationValue(this.catalog.get(language), key);
if (!template$1) return key;
if (!params) return template$1;
return template$1.replace(/\{(\w+)\}/g, (_, placeholder$1) => {
if (Object.hasOwn(params, placeholder$1)) return String(params[placeholder$1]);
return `{${placeholder$1}}`;
});
}
};
}));
var init_i18n = __esmMin((() => {
init_translation_catalog();
init_translator();
}));
function createLifecycle(serviceName, options = {}) {
const { onInitialize, onDestroy, silent = false } = options;
let initialized = false;
const initialize = async () => {
if (initialized) return;
try {
if (onInitialize) await onInitialize();
initialized = true;
} catch (error$1) {
throw error$1;
}
};
const destroy = () => {
if (!initialized) return;
try {
if (onDestroy) onDestroy();
} catch (error$1) {} finally {
initialized = false;
}
};
const isInitialized = () => initialized;
return {
initialize,
destroy,
isInitialized,
serviceName
};
}
function createNetworkErrorResponse(details) {
return {
finalUrl: details.url,
readyState: 4,
status: 0,
statusText: "Network Error",
responseHeaders: "",
response: void 0,
responseXML: null,
responseText: "",
context: details.context
};
}
function scheduleUserscriptRequestFailureCallbacks(details, response) {
Promise.resolve().then(() => {
try {
details.onerror?.(response);
details.onloadend?.(response);
} catch {}
});
}
function fallbackAddStyle(css) {
if (typeof document !== "undefined" && typeof document.createElement === "function") {
const style$1 = document.createElement("style");
style$1.textContent = css;
document.head?.appendChild(style$1);
return style$1;
}
return {};
}
function resolveGMAPIs() {
const global = globalThis;
return Object.freeze({
info: typeof GM_info !== "undefined" ? GM_info : global.GM_info,
download: typeof GM_download !== "undefined" ? GM_download : typeof global.GM_download === "function" ? global.GM_download : void 0,
setValue: typeof GM_setValue !== "undefined" ? GM_setValue : typeof global.GM_setValue === "function" ? global.GM_setValue : void 0,
getValue: typeof GM_getValue !== "undefined" ? GM_getValue : typeof global.GM_getValue === "function" ? global.GM_getValue : void 0,
deleteValue: typeof GM_deleteValue !== "undefined" ? GM_deleteValue : typeof global.GM_deleteValue === "function" ? global.GM_deleteValue : void 0,
listValues: typeof GM_listValues !== "undefined" ? GM_listValues : typeof global.GM_listValues === "function" ? global.GM_listValues : void 0,
addStyle: typeof GM_addStyle !== "undefined" ? GM_addStyle : typeof global.GM_addStyle === "function" ? global.GM_addStyle : void 0,
xmlHttpRequest: typeof GM_xmlhttpRequest !== "undefined" ? GM_xmlhttpRequest : typeof global.GM_xmlhttpRequest === "function" ? global.GM_xmlhttpRequest : void 0,
cookie: typeof GM_cookie !== "undefined" ? GM_cookie : global.GM_cookie && typeof global.GM_cookie.list === "function" ? global.GM_cookie : void 0,
notification: typeof GM_notification !== "undefined" ? GM_notification : typeof global.GM_notification === "function" ? global.GM_notification : void 0
});
}
function resolveGMDownload() {
return resolveGMAPIs().download;
}
function detectManager(global) {
try {
const info = typeof GM_info !== "undefined" ? GM_info : global.GM_info;
const handler = isGMUserScriptInfo(info) ? info?.scriptHandler?.toLowerCase?.() : void 0;
if (!handler) return "unknown";
if (handler.includes("tamper")) return "tampermonkey";
if (handler.includes("grease")) return "greasemonkey";
if (handler.includes("violent")) return "violentmonkey";
return "unknown";
} catch {
return "unknown";
}
}
function safeInfo(global) {
try {
const info = typeof GM_info !== "undefined" ? GM_info : global.GM_info;
return isGMUserScriptInfo(info) ? info : null;
} catch {
return null;
}
}
function assertFunction(fn, errorMessage) {
if (typeof fn !== "function") throw new Error(errorMessage);
return fn;
}
function getUserscript() {
const global = globalThis;
const resolved = resolveGMAPIs();
const gmDownload = resolved.download;
const gmSetValue = resolved.setValue;
const gmGetValue = resolved.getValue;
const gmDeleteValue = resolved.deleteValue;
const gmListValues = resolved.listValues;
const gmAddStyle = resolved.addStyle;
const gmXmlHttpRequest = resolved.xmlHttpRequest;
const gmCookie = resolved.cookie;
const gmNotification = resolved.notification;
return Object.freeze({
hasGM: Boolean(gmDownload || gmSetValue && gmGetValue || gmXmlHttpRequest),
manager: detectManager(global),
info: () => safeInfo(global),
async download(url, filename) {
assertFunction(gmDownload, ERROR_MESSAGES.download)(url, filename);
},
async setValue(key, value) {
const fn = assertFunction(gmSetValue, ERROR_MESSAGES.setValue);
await Promise.resolve(fn(key, value));
},
async getValue(key, defaultValue) {
const fn = assertFunction(gmGetValue, ERROR_MESSAGES.getValue);
return await Promise.resolve(fn(key, defaultValue));
},
getValueSync(key, defaultValue) {
if (!gmGetValue) return defaultValue;
try {
const value = gmGetValue(key, defaultValue);
if (value instanceof Promise) return defaultValue;
return value;
} catch {
return defaultValue;
}
},
async deleteValue(key) {
const fn = assertFunction(gmDeleteValue, ERROR_MESSAGES.deleteValue);
await Promise.resolve(fn(key));
},
async listValues() {
const fn = assertFunction(gmListValues, ERROR_MESSAGES.listValues);
const values = await Promise.resolve(fn());
return Array.isArray(values) ? values : [];
},
addStyle(css) {
return assertFunction(gmAddStyle, ERROR_MESSAGES.addStyle)(css);
},
xmlHttpRequest(details) {
return assertFunction(gmXmlHttpRequest, ERROR_MESSAGES.xmlHttpRequest)(details);
},
notification(details) {
if (!gmNotification) return;
try {
gmNotification(details, void 0);
} catch {}
},
cookie: gmCookie
});
}
function getUserscriptSafe() {
const global = globalThis;
const resolved = resolveGMAPIs();
const gmDownload = typeof resolved.download === "function" ? resolved.download : void 0;
const gmSetValue = typeof resolved.setValue === "function" ? resolved.setValue : void 0;
const gmGetValue = typeof resolved.getValue === "function" ? resolved.getValue : void 0;
const gmDeleteValue = typeof resolved.deleteValue === "function" ? resolved.deleteValue : void 0;
const gmListValues = typeof resolved.listValues === "function" ? resolved.listValues : void 0;
const gmAddStyle = typeof resolved.addStyle === "function" ? resolved.addStyle : void 0;
const gmXmlHttpRequest = typeof resolved.xmlHttpRequest === "function" ? resolved.xmlHttpRequest : void 0;
const gmNotification = typeof resolved.notification === "function" ? resolved.notification : void 0;
return Object.freeze({
hasGM: Boolean(gmDownload || gmSetValue && gmGetValue || gmXmlHttpRequest),
manager: detectManager(global),
info: () => safeInfo(global),
async download(url, filename) {
if (!gmDownload) return;
try {
gmDownload(url, filename);
} catch {}
},
async setValue(key, value) {
if (!gmSetValue) return;
try {
await Promise.resolve(gmSetValue(key, value));
} catch {}
},
async getValue(key, defaultValue) {
if (!gmGetValue) return defaultValue;
try {
return await Promise.resolve(gmGetValue(key, defaultValue));
} catch {
return defaultValue;
}
},
getValueSync(key, defaultValue) {
if (!gmGetValue) return defaultValue;
try {
const value = gmGetValue(key, defaultValue);
if (value instanceof Promise) return defaultValue;
return value;
} catch {
return defaultValue;
}
},
async deleteValue(key) {
if (!gmDeleteValue) return;
try {
await Promise.resolve(gmDeleteValue(key));
} catch {}
},
async listValues() {
if (!gmListValues) return [];
try {
const values = await Promise.resolve(gmListValues());
return Array.isArray(values) ? values : [];
} catch {
return [];
}
},
addStyle(css) {
if (!gmAddStyle) return fallbackAddStyle(css);
try {
return gmAddStyle(css);
} catch {
return fallbackAddStyle(css);
}
},
xmlHttpRequest(details) {
if (gmXmlHttpRequest) try {
return gmXmlHttpRequest(details);
} catch {}
scheduleUserscriptRequestFailureCallbacks(details, createNetworkErrorResponse(details));
return { abort() {} };
},
notification(details) {
if (!gmNotification) return;
try {
gmNotification(details, void 0);
} catch {}
},
cookie: resolved.cookie
});
}
var ERROR_MESSAGES;
var init_adapter = __esmMin((() => {
ERROR_MESSAGES = {
download: "GM_download unavailable",
setValue: "GM_setValue unavailable",
getValue: "GM_getValue unavailable",
deleteValue: "GM_deleteValue unavailable",
listValues: "GM_listValues unavailable",
addStyle: "GM_addStyle unavailable",
xmlHttpRequest: "GM_xmlhttpRequest unavailable"
};
}));
function isGMAPIAvailable(apiName) {
const checker = GM_API_CHECKS[apiName];
try {
return checker(resolveGMAPIs());
} catch {
return false;
}
}
var GM_API_CHECKS;
var init_environment_detector = __esmMin((() => {
init_adapter();
GM_API_CHECKS = {
getValue: (gm) => typeof gm.getValue === "function",
setValue: (gm) => typeof gm.setValue === "function",
download: (gm) => typeof gm.download === "function",
notification: (gm) => typeof gm.notification === "function",
deleteValue: (gm) => typeof gm.deleteValue === "function",
listValues: (gm) => typeof gm.listValues === "function",
cookie: (gm) => typeof gm.cookie?.list === "function"
};
}));
var init_userscript = __esmMin((() => {
init_adapter();
init_environment_detector();
}));
function getPersistentStorage() {
return PersistentStorage.getInstance();
}
var PersistentStorage;
var init_persistent_storage = __esmMin((() => {
init_userscript();
init_logging();
PersistentStorage = class PersistentStorage {
get userscript() {
return getUserscriptSafe();
}
static singleton = createSingleton(() => new PersistentStorage());
static parseWarnedKeys = /* @__PURE__ */ new Set();
static maxParseWarnedKeys = 1e3;
constructor() {}
static getInstance() {
return PersistentStorage.singleton.get();
}
static maybeResetWarnedKeysOnOverflow() {
if (PersistentStorage.parseWarnedKeys.size < PersistentStorage.maxParseWarnedKeys) return;
PersistentStorage.parseWarnedKeys.clear();
}
parseMaybeJsonString(rawValue) {
try {
const parsed = JSON.parse(rawValue);
return typeof parsed === "string" ? parsed : void 0;
} catch {
return;
}
}
serializeValueForStorage(value) {
if (typeof value === "string") return value;
return JSON.stringify(value);
}
async setString(key, value) {
return this.set(key, value);
}
async setJson(key, value) {
try {
if (value === void 0) {
;
await this.userscript.deleteValue(key);
return;
}
const serialized = JSON.stringify(value);
if (serialized === void 0) {
;
await this.userscript.deleteValue(key);
return;
}
await this.userscript.setValue(key, serialized);
} catch (error$1) {
logger.error(`PersistentStorage.setJson failed for "${key}":`, error$1);
throw error$1;
}
}
async set(key, value) {
try {
if (value === void 0) {
;
await this.userscript.deleteValue(key);
return;
}
const serialized = this.serializeValueForStorage(value);
if (serialized === void 0) {
;
await this.userscript.deleteValue(key);
return;
}
await this.userscript.setValue(key, serialized);
} catch (error$1) {
logger.error(`PersistentStorage.set failed for "${key}":`, error$1);
throw error$1;
}
}
async getString(key, defaultValue) {
const value = await this.userscript.getValue(key);
if (value === void 0 || value === null) return defaultValue;
const parsedString = this.parseMaybeJsonString(value);
if (parsedString !== void 0) return parsedString;
return value;
}
warnParseErrorOnce(key, rawValue, error$1) {
PersistentStorage.maybeResetWarnedKeysOnOverflow();
if (PersistentStorage.parseWarnedKeys.has(key)) return;
PersistentStorage.parseWarnedKeys.add(key);
const preview = rawValue.length > 160 ? `${rawValue.slice(0, 160)}…` : rawValue;
;
}
async trySelfHealOnParseError(key) {
try {
await this.userscript.deleteValue(key);
} catch (error$1) {
;
}
}
async get(key, defaultValue, options = {}) {
const value = await this.userscript.getValue(key);
if (value === void 0 || value === null) return defaultValue;
try {
return JSON.parse(value);
} catch (error$1) {
if (options.warnOnParseErrorOnce !== false) this.warnParseErrorOnce(key, value, error$1);
if (options.selfHealOnParseError === true) await this.trySelfHealOnParseError(key);
return defaultValue;
}
}
async getJson(key, defaultValue, options = {}) {
return this.get(key, defaultValue, options);
}
async has(key) {
const value = await this.userscript.getValue(key);
return value !== void 0 && value !== null;
}
getSync(key, defaultValue) {
try {
const value = this.userscript.getValueSync(key);
if (value === void 0 || value === null) return defaultValue;
try {
return JSON.parse(value);
} catch {
return defaultValue;
}
} catch {
return defaultValue;
}
}
getJsonSync(key, defaultValue) {
return this.getSync(key, defaultValue);
}
getStringSync(key, defaultValue) {
try {
const value = this.userscript.getValueSync(key);
if (value === void 0 || value === null) return defaultValue;
try {
const parsedString = this.parseMaybeJsonString(value);
if (parsedString !== void 0) return parsedString;
} catch {}
return value;
} catch {
return defaultValue;
}
}
async remove(key) {
try {
await this.userscript.deleteValue(key);
} catch (error$1) {
logger.error(`PersistentStorage.remove failed for "${key}":`, error$1);
throw error$1;
}
}
};
}));
var translator, LanguageService;
var init_language_service = __esmMin((() => {
init_language_types();
init_translation_registry();
init_i18n();
init_logging();
init_persistent_storage();
translator = new Translator(new TranslationCatalog({
bundles: TRANSLATION_REGISTRY,
fallbackLanguage: "en"
}));
LanguageService = class LanguageService {
lifecycle;
static STORAGE_KEY = "xeg-language";
static SUPPORTED_LANGUAGES = new Set(["auto", ...LANGUAGE_CODES]);
currentLanguage = "auto";
listeners = /* @__PURE__ */ new Set();
storage = getPersistentStorage();
static singleton = createSingleton(() => new LanguageService());
static getInstance() {
return LanguageService.singleton.get();
}
constructor() {
this.lifecycle = createLifecycle("LanguageService", {
onInitialize: () => this.onInitialize(),
onDestroy: () => this.onDestroy()
});
}
async initialize() {
return this.lifecycle.initialize();
}
destroy() {
this.lifecycle.destroy();
}
isInitialized() {
return this.lifecycle.isInitialized();
}
async onInitialize() {
try {
const saved = await this.storage.getString(LanguageService.STORAGE_KEY);
const normalized = this.normalizeLanguage(saved);
if (normalized !== this.currentLanguage) {
this.currentLanguage = normalized;
this.notifyListeners(normalized);
}
const effectiveLang = this.getEffectiveLanguage();
await this.ensureLanguageLoaded(effectiveLang);
} catch (error$1) {
;
}
}
onDestroy() {
this.listeners.clear();
}
detectLanguage() {
const browserLang = typeof navigator !== "undefined" && navigator.language ? navigator.language.slice(0, 2) : "en";
if (isBaseLanguageCode(browserLang)) return browserLang;
return "en";
}
getCurrentLanguage() {
return this.currentLanguage;
}
getAvailableLanguages() {
return [...LANGUAGE_CODES];
}
setLanguage(language) {
const normalized = this.normalizeLanguage(language);
if (language !== normalized && language !== "auto") ;
if (this.currentLanguage === normalized) return;
this.currentLanguage = normalized;
this.notifyListeners(normalized);
this.persistLanguage(normalized).catch((error$1) => {
;
});
const effectiveLang = this.getEffectiveLanguage();
this.ensureLanguageLoaded(effectiveLang).catch((error$1) => {
;
});
}
async ensureLanguageLoaded(language) {
try {
await translator.ensureLanguage(language);
} catch (error$1) {
;
}
}
translate(key, params) {
return translator.translate(this.getEffectiveLanguage(), key, params);
}
onLanguageChange(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
normalizeLanguage(language) {
if (!language) return "auto";
if (LanguageService.SUPPORTED_LANGUAGES.has(language)) return language;
return "en";
}
notifyListeners(language) {
this.listeners.forEach((listener) => {
try {
listener(language);
} catch (error$1) {
;
}
});
}
async persistLanguage(language) {
try {
await this.storage.setString(LanguageService.STORAGE_KEY, language);
} catch (error$1) {
;
}
}
getEffectiveLanguage() {
return this.currentLanguage === "auto" ? this.detectLanguage() : this.currentLanguage;
}
};
}));
function optimizePbsImageUrlToWebP(originalUrl) {
try {
const url = new URL(originalUrl);
if (url.hostname !== "pbs.twimg.com") return originalUrl;
if (url.searchParams.get("format") === "webp") return originalUrl;
url.searchParams.set("format", "webp");
return url.toString();
} catch {
return originalUrl;
}
}
function isTimeoutError(value) {
if (value instanceof DOMException) return value.name === "TimeoutError";
if (value instanceof Error) return value.name === "TimeoutError";
return false;
}
function attachCause(target, cause) {
if (cause === void 0) return;
try {
target.cause = cause;
} catch {}
}
function createAbortError(message, cause) {
try {
const error$1 = new DOMException(message, "AbortError");
attachCause(error$1, cause);
return error$1;
} catch {
const error$1 = new Error(message);
error$1.name = "AbortError";
attachCause(error$1, cause);
return error$1;
}
}
function createUserCancelledAbortError(cause) {
const error$1 = new Error(USER_CANCELLED_MESSAGE);
error$1.name = "AbortError";
attachCause(error$1, cause);
return error$1;
}
function isUserCancelledAbortError(error$1) {
if (error$1 instanceof DOMException) return error$1.name === "AbortError" && error$1.message === "Download cancelled by user";
if (error$1 instanceof Error) return error$1.name === "AbortError" && error$1.message === "Download cancelled by user";
return false;
}
function getUserCancelledAbortErrorFromSignal(signal) {
const reason = signal?.reason;
if (isTimeoutError(reason)) return reason;
if (isUserCancelledAbortError(reason)) return reason;
return createUserCancelledAbortError(reason);
}
function getAbortReasonOrAbortErrorFromSignal(signal) {
const reason = signal?.reason;
if (reason instanceof DOMException) return reason;
if (reason instanceof Error) return reason;
return createAbortError(DEFAULT_ABORT_MESSAGE, reason);
}
var USER_CANCELLED_MESSAGE, DEFAULT_ABORT_MESSAGE;
var init_cancellation = __esmMin((() => {
USER_CANCELLED_MESSAGE = "Download cancelled by user";
DEFAULT_ABORT_MESSAGE = "This operation was aborted";
}));
function promisifyCallback(executor, options) {
return new Promise((resolve$1, reject) => {
try {
executor((result, error$1) => {
if (error$1) {
if (options?.fallback) resolve$1(Promise.resolve(options.fallback()));
else reject(new Error(String(error$1)));
return;
}
resolve$1(result);
});
} catch (error$1) {
if (options?.fallback) resolve$1(Promise.resolve(options.fallback()));
else reject(error$1 instanceof Error ? error$1 : new Error(String(error$1)));
}
});
}
function createDeferred() {
let resolve$1;
let reject;
return {
promise: new Promise((res, rej) => {
resolve$1 = res;
reject = rej;
}),
resolve: resolve$1,
reject
};
}
function createSingleSettler(deferred, cleanup$1) {
let settled = false;
const runCleanup = () => {
if (!cleanup$1) return;
try {
cleanup$1();
} catch {}
};
return {
resolve: (value) => {
if (settled) return;
settled = true;
runCleanup();
deferred.resolve(value);
},
reject: (reason) => {
if (settled) return;
settled = true;
runCleanup();
deferred.reject(reason);
},
isSettled: () => settled
};
}
function parseResponseHeaders(raw) {
const out = {};
if (!raw) return out;
for (const line of raw.split(/\r?\n/)) {
if (!line) continue;
const idx = line.indexOf(":");
if (idx <= 0) continue;
const name = line.slice(0, idx).trim().toLowerCase();
if (!name) continue;
out[name] = line.slice(idx + 1).trim();
}
return out;
}
function normalizeRequestHeaders(headers) {
const out = {};
if (!headers) return out;
for (const [key, value] of Object.entries(headers)) out[key.toLowerCase()] = value;
return out;
}
var HttpError, HttpRequestService;
var init_http_request_service = __esmMin((() => {
init_cancellation();
init_userscript();
HttpError = class extends Error {
constructor(message, status, statusText) {
super(message);
this.status = status;
this.statusText = statusText;
this.name = "HttpError";
}
};
HttpRequestService = class HttpRequestService {
static singleton = createSingleton(() => new HttpRequestService());
defaultTimeout = 1e4;
constructor() {}
async request(method, url, options) {
const deferred = createDeferred();
let abortListener = null;
const cleanupAbortListener = () => {
if (abortListener && options?.signal) {
options.signal.removeEventListener("abort", abortListener);
abortListener = null;
}
};
const { resolve: safeResolve, reject: safeReject } = createSingleSettler(deferred, () => {
cleanupAbortListener();
});
try {
const userscript = getUserscript();
if (options?.signal?.aborted) {
safeReject(getAbortReasonOrAbortErrorFromSignal(options.signal));
return deferred.promise;
}
const headers = normalizeRequestHeaders(options?.headers);
const details = {
method,
url,
headers,
timeout: options?.timeout ?? this.defaultTimeout,
onload: (response) => {
const responseHeaders = parseResponseHeaders(response.responseHeaders);
safeResolve({
ok: response.status >= 200 && response.status < 300,
status: response.status,
statusText: response.statusText,
data: response.response,
headers: responseHeaders
});
},
onerror: (response) => {
const status = response.status ?? 0;
const statusText = response.statusText || "Network Error";
safeReject(new HttpError(status === 0 ? `Network error: Unable to connect to ${url} (CORS, network failure, or blocked request)` : `HTTP ${status}: ${statusText}`, status, statusText));
},
ontimeout: () => {
safeReject(new HttpError(`Request timed out after ${options?.timeout ?? this.defaultTimeout}ms for ${url}`, 0, "Timeout"));
},
onabort: () => {
safeReject(getAbortReasonOrAbortErrorFromSignal(options?.signal));
}
};
if (options?.responseType) details.responseType = options.responseType;
if (options && "data" in options && options.data !== void 0) {
const data = options.data;
const isBinaryLike = data instanceof Blob || data instanceof ArrayBuffer || typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(data) || data instanceof FormData || data instanceof URLSearchParams;
if (typeof data === "object" && data !== null && !isBinaryLike) {
details.data = JSON.stringify(data);
if (!headers["content-type"]) headers["content-type"] = "application/json";
} else details.data = data;
}
if (options?.contentType && !headers["content-type"]) headers["content-type"] = options.contentType;
const control = userscript.xmlHttpRequest(details);
if (options?.signal) {
abortListener = () => {
control.abort();
};
options.signal.addEventListener("abort", abortListener, { once: true });
if (options.signal.aborted) abortListener();
}
} catch (error$1) {
safeReject(error$1);
}
return deferred.promise;
}
static getInstance() {
return HttpRequestService.singleton.get();
}
async get(url, options) {
return this.request("GET", url, options);
}
async post(url, data, options) {
return this.request("POST", url, {
...options,
data
});
}
async put(url, data, options) {
return this.request("PUT", url, {
...options,
data
});
}
async delete(url, options) {
return this.request("DELETE", url, options);
}
async patch(url, data, options) {
return this.request("PATCH", url, {
...options,
data
});
}
async postBinary(url, data, options) {
const contentType = options?.contentType ?? "application/octet-stream";
return await this.request("POST", url, {
...options,
data,
contentType
});
}
};
}));
var TimerManager, globalTimerManager;
var init_timer_management = __esmMin((() => {
TimerManager = class {
timers = /* @__PURE__ */ new Set();
setTimeout(callback, delay$1) {
let id;
id = window.setTimeout(() => {
try {
callback();
} finally {
this.timers.delete(id);
}
}, delay$1);
this.timers.add(id);
return id;
}
clearTimeout(id) {
if (this.timers.has(id)) {
window.clearTimeout(id);
this.timers.delete(id);
}
}
cleanup() {
this.timers.forEach((id) => window.clearTimeout(id));
this.timers.clear();
}
getActiveTimersCount() {
return this.timers.size;
}
};
globalTimerManager = new TimerManager();
}));
function scheduleIdle(task) {
const { ric, cic } = getIdleAPIs();
if (ric) {
const id = ric(() => {
try {
task();
} catch (error$1) {}
});
return { cancel: () => {
cic?.(id);
} };
}
const timerId = globalTimerManager.setTimeout(() => {
try {
task();
} catch (error$1) {}
}, 0);
return { cancel: () => {
globalTimerManager.clearTimeout(timerId);
} };
}
var getIdleAPIs;
var init_idle_scheduler = __esmMin((() => {
init_timer_management();
getIdleAPIs = () => {
const source = typeof globalThis !== "undefined" ? globalThis : void 0;
return {
ric: source && typeof source === "object" && "requestIdleCallback" in source ? source.requestIdleCallback || null : null,
cic: source && typeof source === "object" && "cancelIdleCallback" in source ? source.cancelIdleCallback || null : null
};
};
}));
function getRootKey(root) {
if (!root) return "root:null";
const rootObject = root;
const existing = rootIdMap.get(rootObject);
if (existing) return `root:${existing}`;
const id = ++rootIdCounter;
rootIdMap.set(rootObject, id);
return `root:${id}`;
}
function releaseObserverIfIdle(key) {
const entry = observerPool.get(key);
if (!entry) return;
if (entry.activeElements > 0) return;
try {
entry.observer.disconnect();
} catch {}
observerPool.delete(key);
}
var observerPool, elementCallbackMap, callbackIdCounter, rootIdCounter, rootIdMap, createObserverKey, getObserverEntry, SharedObserver;
var init_observer_pool = __esmMin((() => {
observerPool = /* @__PURE__ */ new Map();
elementCallbackMap = /* @__PURE__ */ new WeakMap();
callbackIdCounter = 0;
rootIdCounter = 0;
rootIdMap = /* @__PURE__ */ new WeakMap();
createObserverKey = (options = {}) => {
return `${getRootKey(options.root ?? null)}|${options.rootMargin ?? "0px"}|${Array.isArray(options.threshold) ? options.threshold.join(",") : `${options.threshold ?? 0}`}`;
};
getObserverEntry = (key, options) => {
const existing = observerPool.get(key);
if (existing) return existing;
const entry = {
observer: new IntersectionObserver((entries) => {
entries.forEach((entry$1) => {
const callbacks = elementCallbackMap.get(entry$1.target)?.get(key);
if (!callbacks || callbacks.size === 0) return;
callbacks.forEach((cb) => {
try {
cb(entry$1);
} catch (error$1) {}
});
});
}, options),
activeElements: 0
};
observerPool.set(key, entry);
return entry;
};
SharedObserver = {
observe(element, callback, options = {}) {
const key = createObserverKey(options);
const entry = getObserverEntry(key, options);
const observer = entry.observer;
let callbacksByKey = elementCallbackMap.get(element);
if (!callbacksByKey) {
callbacksByKey = /* @__PURE__ */ new Map();
elementCallbackMap.set(element, callbacksByKey);
}
let callbacks = callbacksByKey.get(key);
if (!callbacks) {
callbacks = /* @__PURE__ */ new Map();
callbacksByKey.set(key, callbacks);
}
const callbackId = ++callbackIdCounter;
const isFirstForKey = callbacks.size === 0;
callbacks.set(callbackId, callback);
if (isFirstForKey) {
observer.observe(element);
entry.activeElements += 1;
}
let isActive = true;
const unsubscribe = () => {
if (!isActive) return;
isActive = false;
const callbacksByKeyCurrent = elementCallbackMap.get(element);
const callbacksForKey = callbacksByKeyCurrent?.get(key);
callbacksForKey?.delete(callbackId);
if (callbacksForKey && callbacksForKey.size === 0) {
callbacksByKeyCurrent?.delete(key);
observer.unobserve(element);
const pooled = observerPool.get(key);
if (pooled) {
pooled.activeElements = Math.max(0, pooled.activeElements - 1);
releaseObserverIfIdle(key);
}
}
if (!callbacksByKeyCurrent || callbacksByKeyCurrent.size === 0) elementCallbackMap.delete(element);
};
return unsubscribe;
},
unobserve(element) {
const callbacksByKey = elementCallbackMap.get(element);
if (!callbacksByKey) return;
callbacksByKey.forEach((_callbacks, key) => {
const entry = observerPool.get(key);
entry?.observer.unobserve(element);
if (entry) {
entry.activeElements = Math.max(0, entry.activeElements - 1);
releaseObserverIfIdle(key);
}
});
elementCallbackMap.delete(element);
}
};
}));
function computePreloadIndices(currentIndex$1, total, count) {
const safeTotal = Number.isFinite(total) && total > 0 ? Math.floor(total) : 0;
const safeIndex = clampIndex(Math.floor(currentIndex$1), safeTotal);
const safeCount = clamp(Math.floor(count), 0, 20);
if (safeTotal === 0 || safeCount === 0) return [];
const indices = [];
for (let i = 1; i <= safeCount; i++) {
const idx = safeIndex - i;
if (idx >= 0) indices.push(idx);
else break;
}
for (let i = 1; i <= safeCount; i++) {
const idx = safeIndex + i;
if (idx < safeTotal) indices.push(idx);
else break;
}
return indices;
}
var init_preload = __esmMin((() => {
}));
var init_performance = __esmMin((() => {
init_idle_scheduler();
init_observer_pool();
init_preload();
}));
var PrefetchManager;
var init_prefetch_manager = __esmMin((() => {
init_http_request_service();
init_performance();
PrefetchManager = class {
cache = /* @__PURE__ */ new Map();
activeRequests = /* @__PURE__ */ new Map();
maxEntries;
constructor(maxEntries = 20) {
this.maxEntries = maxEntries;
}
async prefetch(media, schedule = "idle") {
if (schedule === "immediate") {
await this.prefetchSingle(media.url);
return;
}
scheduleIdle(() => {
this.prefetchSingle(media.url).catch(() => {});
});
}
get(url) {
return this.cache.get(url) ?? null;
}
has(url) {
return this.cache.has(url);
}
cancelAll() {
for (const controller of this.activeRequests.values()) controller.abort();
this.activeRequests.clear();
}
clear() {
this.cache.clear();
}
getCache() {
return new Map(this.cache);
}
destroy() {
this.cancelAll();
this.clear();
}
async prefetchSingle(url) {
if (this.cache.has(url)) return;
const controller = new AbortController();
this.activeRequests.set(url, controller);
const fetchPromise = HttpRequestService.getInstance().get(url, {
signal: controller.signal,
responseType: "blob"
}).then((response) => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.data;
}).finally(() => {
this.activeRequests.delete(url);
});
if (this.cache.size >= this.maxEntries) this.evictOldest();
this.cache.set(url, fetchPromise);
try {
await fetchPromise;
} catch (error$1) {
if (this.cache.get(url) === fetchPromise) this.cache.delete(url);
}
}
evictOldest() {
const first = this.cache.keys().next();
if (!first.done) {
const url = first.value;
const controller = this.activeRequests.get(url);
if (controller) {
controller.abort();
this.activeRequests.delete(url);
}
this.cache.delete(url);
}
}
};
}));
var init_selectors = __esmMin((() => {
init_selectors$1();
}));
function tryParseUrl(value, base = FALLBACK_BASE_URL) {
if (value instanceof URL) return value;
if (typeof value !== "string") return null;
const trimmed = value.trim();
if (!trimmed) return null;
try {
if (trimmed.startsWith("//")) return new URL(`https:${trimmed}`);
return new URL(trimmed, base);
} catch {
return null;
}
}
function isHostMatching(value, allowedHosts, options = {}) {
if (!Array.isArray(allowedHosts)) return false;
const parsed = tryParseUrl(value);
if (!parsed) return false;
const hostname = parsed.hostname.toLowerCase();
const allowSubdomains = options.allowSubdomains === true;
return allowedHosts.some((host) => {
const normalized = host.toLowerCase();
if (hostname === normalized) return true;
return allowSubdomains && hostname.endsWith(`.${normalized}`);
});
}
function extractUsernameFromUrl(url, options = {}) {
if (!url || typeof url !== "string") return null;
try {
let path;
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
const parsed = tryParseUrl(url);
if (!parsed) return null;
if (options.strictHost) {
if (!isHostMatching(parsed, TWITTER_HOSTS, { allowSubdomains: true })) return null;
}
path = parsed.pathname;
} else path = url;
const segments = path.split("/").filter(Boolean);
if (segments.length >= 3 && segments[1] === "status") {
const username = segments[0];
if (!username) return null;
if (RESERVED_TWITTER_PATHS.has(username.toLowerCase())) return null;
if (TWITTER_USERNAME_PATTERN.test(username)) return username;
}
return null;
} catch {
return null;
}
}
var FALLBACK_BASE_URL, RESERVED_TWITTER_PATHS, TWITTER_USERNAME_PATTERN, TWITTER_HOSTS;
var init_host$1 = __esmMin((() => {
FALLBACK_BASE_URL = "https://x.com";
RESERVED_TWITTER_PATHS = new Set([
"home",
"explore",
"notifications",
"messages",
"search",
"settings",
"i",
"intent",
"compose",
"hashtag"
]);
TWITTER_USERNAME_PATTERN = /^[a-zA-Z0-9_]{1,15}$/;
TWITTER_HOSTS = ["twitter.com", "x.com"];
}));
function isUrlAllowed(rawUrl, policy) {
if (!rawUrl || typeof rawUrl !== "string") return false;
const normalized = rawUrl.replace(CONTROL_CHARS_REGEX, "").trim();
if (!normalized) return false;
if (startsWithBlockedProtocolHint(normalized, policy.blockedProtocolHints ?? DEFAULT_BLOCKED_PROTOCOL_HINTS)) return false;
const lower = normalized.toLowerCase();
if (lower.startsWith("data:")) return policy.allowDataUrls === true && isAllowedDataUrl(lower, policy.allowedDataMimePrefixes);
if (lower.startsWith("//")) return handleProtocolRelative(normalized, policy);
if (policy.allowFragments && lower.startsWith("#")) return true;
if (!EXPLICIT_SCHEME_REGEX.test(normalized)) return policy.allowRelative === true;
try {
const parsed = new URL(normalized);
return policy.allowedProtocols.has(parsed.protocol);
} catch {
return false;
}
}
function startsWithBlockedProtocolHint(value, hints) {
const probe = value.slice(0, MAX_SCHEME_PROBE_LENGTH);
if (/%(?![0-9A-Fa-f]{2})/.test(probe)) return true;
return buildProbeVariants(probe).some((candidate) => hints.some((hint) => candidate.startsWith(hint)));
}
function buildProbeVariants(value) {
const variants = /* @__PURE__ */ new Set();
const base = value.toLowerCase();
variants.add(base);
variants.add(base.replace(SCHEME_WHITESPACE_REGEX, ""));
let decoded = base;
for (let i = 0; i < MAX_DECODE_ITERATIONS; i += 1) try {
decoded = decodeURIComponent(decoded);
variants.add(decoded);
variants.add(decoded.replace(SCHEME_WHITESPACE_REGEX, ""));
} catch {
break;
}
return Array.from(variants.values());
}
function isAllowedDataUrl(lowerCaseValue, allowedPrefixes) {
if (!allowedPrefixes || allowedPrefixes.length === 0) return false;
const [mime] = lowerCaseValue.slice(5).split(";", 1);
if (!mime) return false;
return allowedPrefixes.some((prefix) => mime.startsWith(prefix));
}
function handleProtocolRelative(url, policy) {
if (!policy.allowProtocolRelative) return false;
const fallbackProtocol = policy.allowedProtocols.has("https:") ? "https:" : policy.allowedProtocols.has("http:") ? "http:" : "https:";
try {
const resolved = new URL(`${fallbackProtocol}${url}`);
return policy.allowedProtocols.has(resolved.protocol);
} catch {
return false;
}
}
var CONTROL_CHARS_REGEX, SCHEME_WHITESPACE_REGEX, EXPLICIT_SCHEME_REGEX, MAX_DECODE_ITERATIONS, MAX_SCHEME_PROBE_LENGTH, DEFAULT_BLOCKED_PROTOCOL_HINTS, MEDIA_SAFE_PROTOCOLS, HTML_ATTR_SAFE_PROTOCOLS, DATA_IMAGE_MIME_PREFIXES, MEDIA_URL_POLICY, HTML_ATTRIBUTE_URL_POLICY;
var init_safety = __esmMin((() => {
CONTROL_CHARS_REGEX = /[\u0000-\u001F\u007F]/g;
SCHEME_WHITESPACE_REGEX = /[\u0000-\u001F\u007F\s]+/g;
EXPLICIT_SCHEME_REGEX = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
MAX_DECODE_ITERATIONS = 3;
MAX_SCHEME_PROBE_LENGTH = 64;
DEFAULT_BLOCKED_PROTOCOL_HINTS = Object.freeze([
"javascript:",
"vbscript:",
"file:",
"filesystem:",
"ms-appx:",
"ms-appx-web:",
"about:",
"intent:",
"mailto:",
"tel:",
"sms:",
"wtai:",
"chrome:",
"chrome-extension:",
"opera:",
"resource:",
"data:text",
"data:application",
"data:video",
"data:audio"
]);
MEDIA_SAFE_PROTOCOLS = new Set([
"http:",
"https:",
"blob:"
]);
HTML_ATTR_SAFE_PROTOCOLS = new Set(["http:", "https:"]);
DATA_IMAGE_MIME_PREFIXES = Object.freeze([
"image/png",
"image/jpeg",
"image/jpg",
"image/gif",
"image/webp",
"image/avif"
]);
MEDIA_URL_POLICY = {
allowedProtocols: MEDIA_SAFE_PROTOCOLS,
allowRelative: true,
allowProtocolRelative: true,
allowFragments: false,
allowDataUrls: true,
allowedDataMimePrefixes: DATA_IMAGE_MIME_PREFIXES
};
HTML_ATTRIBUTE_URL_POLICY = {
allowedProtocols: HTML_ATTR_SAFE_PROTOCOLS,
allowRelative: true,
allowProtocolRelative: true,
allowFragments: true,
allowDataUrls: false
};
}));
function isValidMediaUrl(url) {
if (typeof url !== "string" || url.length > MAX_URL_LENGTH) return false;
const parsed = tryParseUrl(url);
if (!parsed) return false;
if (!isHttpProtocol(parsed.protocol)) return false;
if (!isHostMatching(parsed, ALLOWED_MEDIA_HOSTS)) return false;
return isAllowedMediaPath(parsed.hostname, parsed.pathname);
}
function isHttpProtocol(protocol) {
return protocol === "https:" || protocol === "http:";
}
function isAllowedMediaPath(hostname, pathname) {
if (hostname === "pbs.twimg.com") return checkPbsMediaPath(pathname);
if (hostname === "video.twimg.com") return checkVideoMediaPath(pathname);
return false;
}
function checkPbsMediaPath(pathname) {
return pathname.startsWith("/media/") || pathname.startsWith("/ext_tw_video_thumb/") || pathname.startsWith("/tweet_video_thumb/") || pathname.startsWith("/video_thumb/") || pathname.startsWith("/amplify_video_thumb/");
}
function checkVideoMediaPath(pathname) {
return pathname.startsWith("/ext_tw_video/") || pathname.startsWith("/tweet_video/") || pathname.startsWith("/amplify_video/") || pathname.startsWith("/dm_video/");
}
var MAX_URL_LENGTH, ALLOWED_MEDIA_HOSTS;
var init_validator = __esmMin((() => {
init_constants$1();
init_host$1();
MAX_URL_LENGTH = 2048;
ALLOWED_MEDIA_HOSTS = MEDIA.HOSTS.MEDIA_CDN;
}));
function isSafeAndValidMediaUrl(url) {
if (!url || typeof url !== "string") return false;
const trimmed = url.trim();
if (!trimmed) return false;
if (!isUrlAllowed(trimmed, MEDIA_URL_POLICY)) return false;
if (trimmed.startsWith("//")) return isValidMediaUrl(`https:${trimmed}`);
return isValidMediaUrl(trimmed);
}
var init_media_url = __esmMin((() => {
init_safety();
init_validator();
}));
var init_url = __esmMin((() => {
init_host$1();
init_media_url();
init_safety();
}));
function normalizeTweetUrl(inputUrl) {
try {
const url = new URL(inputUrl, DEFAULT_TWEET_ORIGIN);
const hostname = url.hostname.toLowerCase();
if (hostname === "twitter.com" || hostname === "www.twitter.com" || hostname === "mobile.twitter.com") {
url.hostname = "x.com";
url.protocol = "https:";
}
if (hostname === "www.x.com") {
url.hostname = "x.com";
url.protocol = "https:";
}
return url.toString();
} catch {
if (inputUrl.startsWith("/")) return `${DEFAULT_TWEET_ORIGIN}${inputUrl}`;
return inputUrl;
}
}
var DEFAULT_TWEET_ORIGIN, extractFromElement, extractFromDOM, extractFromMediaGridItem, TweetInfoExtractor;
var init_tweet_info_extractor = __esmMin((() => {
init_selectors();
init_logging();
init_url();
DEFAULT_TWEET_ORIGIN = "https://x.com";
extractFromElement = (element) => {
const dataId = element.dataset.tweetId;
if (dataId && /^\d+$/.test(dataId)) return {
tweetId: dataId,
username: element.dataset.user ?? "unknown",
tweetUrl: `https://x.com/i/status/${dataId}`,
extractionMethod: "element-attribute",
confidence: .9
};
const href = element.getAttribute("href");
if (href) {
const match = href.match(/\/status\/(\d+)/);
if (match?.[1]) return {
tweetId: match[1],
username: extractUsernameFromUrl(href) ?? "unknown",
tweetUrl: normalizeTweetUrl(href),
extractionMethod: "element-href",
confidence: .8
};
}
return null;
};
extractFromDOM = (element) => {
const container$2 = element.closest(TWEET_ARTICLE_SELECTOR);
if (!container$2) return null;
const statusLink = container$2.querySelector(STATUS_LINK_SELECTOR);
if (!statusLink) return null;
const href = statusLink.getAttribute("href");
if (!href) return null;
const match = href.match(/\/status\/(\d+)/);
if (!match || !match[1]) return null;
return {
tweetId: match[1],
username: extractUsernameFromUrl(href) ?? "unknown",
tweetUrl: normalizeTweetUrl(href),
extractionMethod: "dom-structure",
confidence: .85,
metadata: { containerTag: container$2.tagName.toLowerCase() }
};
};
extractFromMediaGridItem = (element) => {
const link = element.closest("a");
if (!link) return null;
const href = link.getAttribute("href");
if (!href) return null;
const match = href.match(/\/status\/(\d+)/);
if (!match || !match[1]) return null;
return {
tweetId: match[1],
username: extractUsernameFromUrl(href) ?? "unknown",
tweetUrl: normalizeTweetUrl(href),
extractionMethod: "media-grid-item",
confidence: .8
};
};
TweetInfoExtractor = class {
strategies = [
extractFromElement,
extractFromDOM,
extractFromMediaGridItem
];
async extract(element) {
for (const strategy of this.strategies) try {
const result = strategy(element);
if (result && this.isValid(result)) {
;
return result;
}
} catch {}
return null;
}
isValid(info) {
return !!info.tweetId && /^\d+$/.test(info.tweetId) && info.tweetId !== "unknown";
}
};
}));
function extractFilenameFromUrl(url) {
if (!url) return null;
const trimmed = url.trim();
if (!trimmed) return null;
if (!trimmed.startsWith("http://") && !trimmed.startsWith("https://") && !trimmed.startsWith("//") && !trimmed.startsWith("/") && !trimmed.startsWith("./") && !trimmed.startsWith("../")) return null;
const parsed = tryParseUrl(trimmed, "https://example.invalid");
if (!parsed) return null;
const filename = parsed.pathname.split("/").pop();
return filename && filename.length > 0 ? filename : null;
}
function getMediaDedupKey(media) {
const urlCandidate = typeof media.originalUrl === "string" && media.originalUrl.length > 0 ? media.originalUrl : typeof media.url === "string" && media.url.length > 0 ? media.url : null;
if (!urlCandidate) return null;
const typePrefix = media.type === "image" || media.type === "video" || media.type === "gif" ? `${media.type}:` : "";
const parsed = tryParseUrl(urlCandidate, "https://example.invalid");
if (parsed) {
const host = parsed.hostname;
const path = parsed.pathname;
const format = parsed.searchParams.get("format");
const formatSuffix = format ? `?format=${format}` : "";
if (host && path) return `${typePrefix}${host}${path}${formatSuffix}`;
}
const filename = extractFilenameFromUrl(urlCandidate);
return filename ? `${typePrefix}${filename}` : `${typePrefix}${urlCandidate}`;
}
function removeDuplicates(items, keyExtractor) {
const seen = /* @__PURE__ */ new Set();
const uniqueItems = [];
for (const item of items) {
if (item == null) continue;
const key = keyExtractor(item);
if (!key) continue;
if (!seen.has(key)) {
seen.add(key);
uniqueItems.push(item);
}
}
return uniqueItems;
}
function removeDuplicateMediaItems(mediaItems) {
if (!mediaItems?.length) return [];
return removeDuplicates(mediaItems, getMediaDedupKey);
}
function extractVisualIndexFromUrl(url) {
if (!url) return 0;
const match = url.match(/\/(photo|video)\/(\d+)(?:[?#].*)?$/);
const visualNumber = match?.[2] ? Number.parseInt(match[2], 10) : NaN;
return Number.isFinite(visualNumber) && visualNumber > 0 ? visualNumber - 1 : 0;
}
function sortMediaByVisualOrder(mediaItems) {
if (mediaItems.length <= 1) return mediaItems;
const withVisualIndex = mediaItems.map((media) => {
return {
media,
visualIndex: extractVisualIndexFromUrl(media.expanded_url || "")
};
});
withVisualIndex.sort((a, b) => a.visualIndex - b.visualIndex);
return withVisualIndex.map(({ media }, newIndex) => ({
...media,
index: newIndex
}));
}
function extractDimensionsFromUrl(url) {
if (!url) return null;
const match = url.match(/\/(\d{2,6})x(\d{2,6})(?:\/|\.|$)/);
if (!match) return null;
const width = Number.parseInt(match[1] ?? "", 10);
const height = Number.parseInt(match[2] ?? "", 10);
if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) return null;
return {
width,
height
};
}
function normalizeDimension(value) {
if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.round(value);
if (typeof value === "string") {
const parsed = Number.parseFloat(value);
if (Number.isFinite(parsed) && parsed > 0) return Math.round(parsed);
}
return null;
}
function normalizeMediaUrl(url) {
if (!url) return null;
const trimmed = url.trim();
if (!trimmed) return null;
if (!trimmed.startsWith("http://") && !trimmed.startsWith("https://") && !trimmed.startsWith("//") && !trimmed.startsWith("/") && !trimmed.startsWith("./") && !trimmed.startsWith("../")) return null;
const parsed = tryParseUrl(trimmed, "https://example.invalid");
if (!parsed) return null;
let filename = parsed.pathname.split("/").pop();
if (!filename) return null;
const dotIndex = filename.lastIndexOf(".");
if (dotIndex !== -1) filename = filename.substring(0, dotIndex);
return filename && filename.length > 0 ? filename : null;
}
function scaleAspectRatio(widthRatio, heightRatio) {
if (heightRatio <= 0 || widthRatio <= 0) return DEFAULT_DIMENSIONS;
const scaledHeight = STANDARD_GALLERY_HEIGHT;
return {
width: Math.max(1, Math.round(widthRatio / heightRatio * scaledHeight)),
height: scaledHeight
};
}
function extractDimensionsFromMetadataObject(dimensions) {
if (!dimensions) return null;
const width = normalizeDimension(dimensions.width);
const height = normalizeDimension(dimensions.height);
if (width && height) return {
width,
height
};
return null;
}
function extractDimensionsFromUrlCandidate(candidate) {
if (typeof candidate !== "string" || !candidate) return null;
return extractDimensionsFromUrl(candidate);
}
function deriveDimensionsFromMetadata(metadata) {
if (!metadata) return null;
const dimensions = extractDimensionsFromMetadataObject(metadata.dimensions);
if (dimensions) return dimensions;
const apiData = metadata.apiData;
if (!apiData) return null;
const originalWidth = normalizeDimension(apiData.original_width ?? apiData.originalWidth);
const originalHeight = normalizeDimension(apiData.original_height ?? apiData.originalHeight);
if (originalWidth && originalHeight) return {
width: originalWidth,
height: originalHeight
};
const fromDownloadUrl = extractDimensionsFromUrlCandidate(apiData.download_url);
if (fromDownloadUrl) return fromDownloadUrl;
const fromPreviewUrl = extractDimensionsFromUrlCandidate(apiData.preview_url);
if (fromPreviewUrl) return fromPreviewUrl;
const aspectRatio = apiData.aspect_ratio;
if (Array.isArray(aspectRatio) && aspectRatio.length >= 2) {
const ratioWidth = normalizeDimension(aspectRatio[0]);
const ratioHeight = normalizeDimension(aspectRatio[1]);
if (ratioWidth && ratioHeight) return scaleAspectRatio(ratioWidth, ratioHeight);
}
return null;
}
function deriveDimensionsFromMediaUrls(media) {
const candidates = [
media.url,
media.originalUrl,
media.thumbnailUrl
];
for (const candidate of candidates) {
const dimensions = extractDimensionsFromUrlCandidate(candidate);
if (dimensions) return dimensions;
}
return null;
}
function resolveMediaDimensionsWithIntrinsicFlag(media) {
if (!media) return {
dimensions: DEFAULT_DIMENSIONS,
hasIntrinsicSize: false
};
const directWidth = normalizeDimension(media.width);
const directHeight = normalizeDimension(media.height);
if (directWidth && directHeight) return {
dimensions: {
width: directWidth,
height: directHeight
},
hasIntrinsicSize: true
};
const fromMetadata = deriveDimensionsFromMetadata(media.metadata);
if (fromMetadata) return {
dimensions: fromMetadata,
hasIntrinsicSize: true
};
const fromUrls = deriveDimensionsFromMediaUrls(media);
if (fromUrls) return {
dimensions: fromUrls,
hasIntrinsicSize: true
};
return {
dimensions: DEFAULT_DIMENSIONS,
hasIntrinsicSize: false
};
}
function toRem(pixels) {
return `${(pixels / 16).toFixed(4)}rem`;
}
function createIntrinsicSizingStyle(dimensions) {
const ratio = dimensions.height > 0 ? dimensions.width / dimensions.height : 1;
return {
"--xeg-aspect-default": `${dimensions.width} / ${dimensions.height}`,
"--xeg-gallery-item-intrinsic-width": toRem(dimensions.width),
"--xeg-gallery-item-intrinsic-height": toRem(dimensions.height),
"--xeg-gallery-item-intrinsic-ratio": ratio.toFixed(6)
};
}
function adjustClickedIndexAfterDeduplication(originalItems, uniqueItems, originalClickedIndex) {
if (uniqueItems.length === 0) return 0;
const clickedItem = originalItems[clampIndex(originalClickedIndex, originalItems.length)];
if (!clickedItem) return 0;
const clickedKey = getMediaDedupKey(clickedItem);
if (!clickedKey) return 0;
const newIndex = uniqueItems.findIndex((item) => {
return getMediaDedupKey(item) === clickedKey;
});
return newIndex >= 0 ? newIndex : 0;
}
var STANDARD_GALLERY_HEIGHT, DEFAULT_DIMENSIONS;
var init_media_dimensions = __esmMin((() => {
init_url();
STANDARD_GALLERY_HEIGHT = 720;
DEFAULT_DIMENSIONS = {
width: 540,
height: STANDARD_GALLERY_HEIGHT
};
}));
function resolveDimensionsFromApiMedia(apiMedia) {
const widthFromOriginal = normalizeDimension(apiMedia.original_width);
const heightFromOriginal = normalizeDimension(apiMedia.original_height);
if (widthFromOriginal && heightFromOriginal) return {
width: widthFromOriginal,
height: heightFromOriginal
};
return null;
}
function createMediaInfoFromAPI(apiMedia, tweetInfo, index, tweetTextHTML) {
try {
const mediaType = apiMedia.type === "photo" ? "image" : "video";
const dimensions = resolveDimensionsFromApiMedia(apiMedia);
const metadata = {
apiIndex: index,
apiData: apiMedia
};
if (dimensions) metadata.dimensions = dimensions;
const username = apiMedia.screen_name || tweetInfo.username;
return {
id: `${tweetInfo.tweetId}_api_${index}`,
url: apiMedia.download_url,
type: mediaType,
filename: "",
tweetUsername: username,
tweetId: tweetInfo.tweetId,
tweetUrl: tweetInfo.tweetUrl,
tweetText: apiMedia.tweet_text,
tweetTextHTML,
originalUrl: apiMedia.download_url,
thumbnailUrl: apiMedia.preview_url,
alt: `${mediaType} ${index + 1}`,
...dimensions && {
width: dimensions.width,
height: dimensions.height
},
metadata
};
} catch (error$1) {
logger.error("Failed to create API MediaInfo:", error$1);
return null;
}
}
async function convertAPIMediaToMediaInfo(apiMedias, tweetInfo, tweetTextHTML) {
const mediaItems = [];
for (let i = 0; i < apiMedias.length; i++) {
const apiMedia = apiMedias[i];
if (!apiMedia) continue;
const mediaInfo = createMediaInfoFromAPI(apiMedia, tweetInfo, i, tweetTextHTML);
if (mediaInfo) mediaItems.push(mediaInfo);
}
return mediaItems;
}
var init_media_factory = __esmMin((() => {
init_logging();
init_media_dimensions();
}));
function buildTweetResultByRestIdUrl(args) {
const { host, queryId, variables, features, fieldToggles } = args;
const urlObj = new URL(`https://${host}/i/api/graphql/${queryId}/TweetResultByRestId`);
urlObj.searchParams.set("variables", JSON.stringify(variables));
urlObj.searchParams.set("features", JSON.stringify(features));
urlObj.searchParams.set("fieldToggles", JSON.stringify(fieldToggles));
return urlObj.toString();
}
function pickCandidateHost(hostname) {
if (hostname.includes("twitter.com")) return "twitter.com";
if (hostname.includes("x.com")) return "x.com";
return null;
}
function selectTwitterApiHostFromHostname(args) {
const { hostname, supportedHosts, defaultHost } = args;
if (!hostname) return defaultHost;
const candidate = pickCandidateHost(hostname);
if (!candidate) return defaultHost;
return supportedHosts.includes(candidate) ? candidate : defaultHost;
}
var init_twitter_api = __esmMin((() => {
}));
function getLocationLike() {
try {
return globalThis.location;
} catch {
return;
}
}
function getSafeHref() {
try {
return getLocationLike()?.href;
} catch {
return;
}
}
function getSafeOrigin() {
try {
return getLocationLike()?.origin;
} catch {
return;
}
}
function getSafeHostname() {
try {
return getLocationLike()?.hostname;
} catch {
return;
}
}
function getSafeLocationHeaders() {
const referer = getSafeHref();
const origin = getSafeOrigin();
if (!referer && !origin) return {};
return {
...referer ? { referer } : {},
...origin ? { origin } : {}
};
}
function createTimeoutController(ms) {
const controller = new AbortController();
let timeoutId = null;
timeoutId = globalTimerManager.setTimeout(() => {
controller.abort(new DOMException("The operation timed out.", "TimeoutError"));
}, ms);
const clear = () => {
if (timeoutId === null) return;
globalTimerManager.clearTimeout(timeoutId);
timeoutId = null;
};
controller.signal.addEventListener("abort", () => {
clear();
}, { once: true });
return {
signal: controller.signal,
cancel: clear
};
}
function combineSignals(signals) {
const validSignals = signals.filter((s) => Boolean(s));
if (validSignals.length === 0) return new AbortController().signal;
if (validSignals.length === 1) return validSignals[0];
if (typeof AbortSignal.any === "function") return AbortSignal.any(validSignals);
const controller = new AbortController();
let settled = false;
function cleanup$1() {
for (const s of validSignals) try {
s.removeEventListener("abort", onAbort);
} catch {}
}
function onAbort() {
if (settled) return;
settled = true;
const reason = validSignals.find((s) => s.aborted)?.reason;
controller.abort(reason);
cleanup$1();
}
for (const s of validSignals) if (s.aborted) {
controller.abort(s.reason);
return controller.signal;
}
for (const s of validSignals) s.addEventListener("abort", onAbort, { once: true });
return controller.signal;
}
function isAbortError(error$1) {
if (error$1 instanceof DOMException) return error$1.name === "AbortError" || error$1.name === "TimeoutError";
if (error$1 instanceof Error) return error$1.name === "AbortError" || error$1.message.includes("aborted");
return false;
}
var init_signal_utils = __esmMin((() => {
init_timer_management();
}));
async function delay(ms, signal) {
if (ms <= 0) return;
if (signal?.aborted) throw getAbortReasonOrAbortErrorFromSignal(signal);
return new Promise((resolve$1, reject) => {
const timerId = globalTimerManager.setTimeout(() => {
cleanup$1();
resolve$1();
}, ms);
const onAbort = () => {
cleanup$1();
reject(getAbortReasonOrAbortErrorFromSignal(signal));
};
const cleanup$1 = () => {
globalTimerManager.clearTimeout(timerId);
signal?.removeEventListener("abort", onAbort);
};
signal?.addEventListener("abort", onAbort, { once: true });
});
}
var init_delay = __esmMin((() => {
init_signal_utils();
init_cancellation();
init_timer_management();
}));
function getExponentialBackoffDelayMs(args) {
const { attempt, baseDelayMs } = args;
return baseDelayMs * 2 ** attempt;
}
var init_async$1 = __esmMin((() => {
}));
function escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function decode(value) {
if (!value) return void 0;
try {
return decodeURIComponent(value);
} catch {
return value;
}
}
function resolveCookieAPI() {
try {
const userscript = getUserscript();
if (userscript.cookie) return userscript.cookie;
} catch (error$1) {}
return null;
}
function getCookieAPI() {
if (cachedCookieAPI === void 0) cachedCookieAPI = resolveCookieAPI();
return cachedCookieAPI;
}
function listFromDocument(options) {
if (typeof document === "undefined" || typeof document.cookie !== "string") return [];
const domain = typeof document.location?.hostname === "string" ? document.location.hostname : void 0;
const records = document.cookie.split(";").map((entry) => entry.trim()).filter(Boolean).map((entry) => {
const [rawName, ...rest] = entry.split("=");
const nameDecoded = decode(rawName);
if (!nameDecoded) return null;
return {
name: nameDecoded,
value: decode(rest.join("=")) ?? "",
path: "/",
session: true,
...domain ? { domain } : {}
};
}).filter((record) => Boolean(record));
return options?.name ? records.filter((record) => record.name === options.name) : records;
}
async function listCookies(options) {
const gmCookie = getCookieAPI();
if (!gmCookie?.list) return listFromDocument(options);
return promisifyCallback((callback) => gmCookie?.list(options, (cookies, error$1) => {
if (error$1) ;
callback(error$1 ? void 0 : (cookies ?? []).map((c) => ({ ...c })), error$1);
}), { fallback: () => listFromDocument(options) });
}
async function getCookieValue(name, options) {
if (!name) return void 0;
if (getCookieAPI()?.list) {
const value = (await listCookies({
...options,
name
}))[0]?.value;
if (value) return value;
}
return getCookieValueSync(name);
}
function getCookieValueSync(name) {
if (!name) return void 0;
if (typeof document === "undefined" || typeof document.cookie !== "string") return;
const pattern = /* @__PURE__ */ new RegExp(`(?:^|;\\s*)${escapeRegex(name)}=([^;]*)`);
return decode(document.cookie.match(pattern)?.[1]);
}
var cachedCookieAPI;
var init_cookie_utils = __esmMin((() => {
init_userscript();
init_logging();
init_async$1();
}));
var init_cookie = __esmMin((() => {
init_cookie_utils();
}));
function getBackoffDelay(attempt) {
return getExponentialBackoffDelayMs({
attempt,
baseDelayMs: BASE_RETRY_DELAY_MS
});
}
async function fetchTokenWithRetry() {
const syncToken = getCookieValueSync("ct0");
if (syncToken) {
;
return syncToken;
}
for (let attempt = 0; attempt < MAX_RETRY_ATTEMPTS; attempt++) try {
const value = await getCookieValue("ct0");
if (value) {
;
return value;
}
if (attempt < MAX_RETRY_ATTEMPTS - 1) {
const delayMs = getBackoffDelay(attempt);
;
await delay(delayMs);
}
} catch (error$1) {
if (attempt < MAX_RETRY_ATTEMPTS - 1) {
const delayMs = getBackoffDelay(attempt);
;
await delay(delayMs);
} else void 0;
}
;
}
function initializeTokensSync() {
if (_tokensInitialized) return;
const syncToken = getCookieValueSync("ct0");
if (syncToken) {
_csrfToken = syncToken;
_tokensInitialized = true;
;
}
}
async function initTokens() {
if (_tokensInitialized && _csrfToken) return _csrfToken;
if (_initPromise) return _initPromise;
_initPromise = (async () => {
try {
const token = await fetchTokenWithRetry();
_csrfToken = token;
_tokensInitialized = true;
return token;
} finally {
_initPromise = null;
}
})();
return _initPromise;
}
function getCsrfToken() {
initializeTokensSync();
return _csrfToken;
}
async function getCsrfTokenAsync() {
if (_tokensInitialized && _csrfToken) return _csrfToken;
return initTokens();
}
var MAX_RETRY_ATTEMPTS, BASE_RETRY_DELAY_MS, _csrfToken, _tokensInitialized, _initPromise;
var init_twitter_auth$1 = __esmMin((() => {
init_delay();
init_logging();
init_cookie();
MAX_RETRY_ATTEMPTS = 3;
BASE_RETRY_DELAY_MS = 100;
_tokensInitialized = false;
_initPromise = null;
}));
var init_twitter_auth = __esmMin((() => {
init_twitter_auth$1();
}));
function resolveDimensions(media, mediaUrl) {
const dimensionsFromUrl = extractDimensionsFromUrl(mediaUrl);
const widthFromOriginal = normalizeDimension(media.original_info?.width);
const heightFromOriginal = normalizeDimension(media.original_info?.height);
const widthFromUrl = normalizeDimension(dimensionsFromUrl?.width);
const heightFromUrl = normalizeDimension(dimensionsFromUrl?.height);
const width = widthFromOriginal ?? widthFromUrl;
const height = heightFromOriginal ?? heightFromUrl;
const result = {};
if (width !== null && width !== void 0) result.width = width;
if (height !== null && height !== void 0) result.height = height;
return result;
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function removeUrlTokensFromText(text, urls) {
let result = text;
for (const url of urls) {
if (!url) continue;
const token = escapeRegExp(url);
const re = new RegExp(`(^|\\s+)${token}(?=\\s+|$)`, "g");
result = result.replace(re, (_match, leadingWs) => leadingWs);
}
result = result.replace(/[ \t\f\v\u00A0]{2,}/g, " ").replace(/ ?\n ?/g, "\n").replace(/\n{3,}/g, "\n\n");
return result.trim();
}
function resolveAspectRatio(media, dimensions) {
const aspectRatioValues = Array.isArray(media.video_info?.aspect_ratio) ? media.video_info?.aspect_ratio : void 0;
const aspectRatioWidth = normalizeDimension(aspectRatioValues?.[0]);
const aspectRatioHeight = normalizeDimension(aspectRatioValues?.[1]);
if (aspectRatioWidth && aspectRatioHeight) return [aspectRatioWidth, aspectRatioHeight];
if (dimensions.width && dimensions.height) return [dimensions.width, dimensions.height];
}
function getPhotoHighQualityUrl(mediaUrlHttps) {
if (!mediaUrlHttps) return mediaUrlHttps;
const isAbsolute = /^(https?:)?\/\//i.test(mediaUrlHttps);
const parsed = tryParseUrl(mediaUrlHttps, "https://pbs.twimg.com");
if (!parsed) {
const [pathPart = "", existingQuery = ""] = mediaUrlHttps.split("?");
const pathMatch$1 = pathPart.match(/\.(jpe?g|png)$/i);
if (!pathMatch$1) return mediaUrlHttps;
const ext$1 = (pathMatch$1[1] ?? "").toLowerCase();
const params = new URLSearchParams(existingQuery);
if (!Array.from(params.keys()).some((k) => k.toLowerCase() === "format")) params.set("format", ext$1);
params.set("name", "orig");
const query = params.toString();
return query ? `${pathPart}?${query}` : pathPart;
}
const hasParamCaseInsensitive = (key) => {
return Array.from(parsed.searchParams.keys()).some((k) => k.toLowerCase() === key);
};
const setParamCaseInsensitive = (key, value) => {
for (const k of Array.from(parsed.searchParams.keys())) if (k !== key && k.toLowerCase() === key) parsed.searchParams.delete(k);
parsed.searchParams.set(key, value);
};
const pathMatch = parsed.pathname.match(/\.(jpe?g|png)$/i);
if (!pathMatch) return mediaUrlHttps;
const ext = (pathMatch[1] ?? "").toLowerCase();
if (!hasParamCaseInsensitive("format")) setParamCaseInsensitive("format", ext);
setParamCaseInsensitive("name", "orig");
if (isAbsolute) return parsed.toString();
return `${parsed.pathname}${parsed.search}`;
}
function getVideoHighQualityUrl(media) {
const mp4Variants = (media.video_info?.variants ?? []).filter((v) => v.content_type === "video/mp4");
if (mp4Variants.length === 0) return null;
return mp4Variants.reduce((best, current) => {
return (current.bitrate ?? 0) > (best.bitrate ?? 0) ? current : best;
}).url;
}
function getHighQualityMediaUrl(media) {
if (media.type === "photo") return getPhotoHighQualityUrl(media.media_url_https) ?? null;
if (media.type === "video" || media.type === "animated_gif") return getVideoHighQualityUrl(media);
return null;
}
function createMediaEntry(media, mediaUrl, screenName, tweetId, tweetText, index, typeIndex, typeIndexOriginal, sourceLocation) {
const mediaType = media.type === "animated_gif" ? "video" : media.type;
const dimensions = resolveDimensions(media, mediaUrl);
const aspectRatio = resolveAspectRatio(media, dimensions);
const entry = {
screen_name: screenName,
tweet_id: tweetId,
download_url: mediaUrl,
type: mediaType,
typeOriginal: media.type,
index,
typeIndex,
typeIndexOriginal,
preview_url: media.media_url_https,
media_id: media.id_str,
media_key: media.media_key ?? "",
expanded_url: media.expanded_url ?? "",
short_expanded_url: media.display_url ?? "",
short_tweet_url: media.url ?? "",
tweet_text: tweetText,
sourceLocation
};
if (dimensions.width) entry.original_width = dimensions.width;
if (dimensions.height) entry.original_height = dimensions.height;
if (aspectRatio) entry.aspect_ratio = aspectRatio;
return entry;
}
function extractMediaFromTweet(tweetResult, tweetUser, sourceLocation = "original") {
const quotedResult = tweetResult.quoted_status_result?.result;
const parseTarget = sourceLocation === "quoted" && quotedResult ? quotedResult : tweetResult;
if (!parseTarget.extended_entities?.media) return [];
const mediaItems = [];
const typeIndex = {};
const screenName = tweetUser.screen_name ?? "";
const tweetId = parseTarget.rest_id ?? parseTarget.id_str ?? "";
const inlineMedia = parseTarget.note_tweet?.note_tweet_results?.result?.media?.inline_media;
const inlineMediaOrder = /* @__PURE__ */ new Map();
if (Array.isArray(inlineMedia)) {
for (const item of inlineMedia) if (item.media_id && typeof item.index === "number") inlineMediaOrder.set(item.media_id, item.index);
}
const orderedMedia = (() => {
const mediaList = parseTarget.extended_entities?.media ?? [];
if (inlineMediaOrder.size === 0) return mediaList;
return mediaList.map((m, originalIndex) => ({
m,
originalIndex
})).sort((a, b) => {
const aInline = inlineMediaOrder.get(a.m.id_str);
const bInline = inlineMediaOrder.get(b.m.id_str);
if (aInline !== void 0 && bInline !== void 0) return aInline - bInline;
if (aInline !== void 0) return -1;
if (bInline !== void 0) return 1;
return a.originalIndex - b.originalIndex;
}).map((x) => x.m);
})();
const normalizedTweetText = removeUrlTokensFromText((parseTarget.full_text ?? "").trim(), orderedMedia.map((m) => m.url).filter((u) => typeof u === "string" && u.length > 0));
for (let index = 0; index < orderedMedia.length; index++) {
const media = orderedMedia[index];
if (!media?.type) continue;
if (!media.id_str) continue;
if (!media.media_url_https) continue;
try {
const mediaUrl = getHighQualityMediaUrl(media);
if (!mediaUrl) continue;
const mediaType = media.type === "animated_gif" ? "video" : media.type;
typeIndex[mediaType] = (typeIndex[mediaType] ?? -1) + 1;
typeIndex[media.type] = typeIndex[media.type] ?? typeIndex[mediaType];
const entry = createMediaEntry(media, mediaUrl, screenName, tweetId, normalizedTweetText, index, typeIndex[mediaType] ?? 0, typeIndex[media.type] ?? 0, sourceLocation);
mediaItems.push(entry);
} catch (error$1) {}
}
return mediaItems;
}
function normalizeLegacyTweet(tweet) {
if (tweet.legacy) {
if (!tweet.extended_entities && tweet.legacy.extended_entities) tweet.extended_entities = tweet.legacy.extended_entities;
if (!tweet.full_text && tweet.legacy.full_text) tweet.full_text = tweet.legacy.full_text;
if (!tweet.id_str && tweet.legacy.id_str) tweet.id_str = tweet.legacy.id_str;
}
const noteTweetText = tweet.note_tweet?.note_tweet_results?.result?.text;
if (noteTweetText) tweet.full_text = noteTweetText;
}
function normalizeLegacyUser(user) {
if (user.legacy) {
if (!user.screen_name && user.legacy.screen_name) user.screen_name = user.legacy.screen_name;
if (!user.name && user.legacy.name) user.name = user.legacy.name;
}
}
var init_twitter_response_parser = __esmMin((() => {
init_media_dimensions();
init_url();
}));
var init_twitter_parser = __esmMin((() => {
init_twitter_response_parser();
}));
function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash |= 0;
}
return hash.toString(36);
}
function getSafeHost() {
return selectTwitterApiHostFromHostname({
hostname: getSafeHostname(),
supportedHosts: TWITTER_API_CONFIG.SUPPORTED_HOSTS,
defaultHost: TWITTER_API_CONFIG.DEFAULT_HOST
});
}
var TwitterAPI;
var init_twitter_api_client = __esmMin((() => {
init_constants$1();
init_twitter_api();
init_logging();
init_http_request_service();
init_twitter_auth();
init_twitter_parser();
init_media_dimensions();
TwitterAPI = class TwitterAPI {
static requestCache = /* @__PURE__ */ new Map();
static CACHE_LIMIT = 16;
static async getTweetMedias(tweetId) {
const url = TwitterAPI.createTweetEndpointUrl(tweetId);
const json = await TwitterAPI.apiRequest(url);
if (!json.data?.tweetResult?.result) return [];
let tweetResult = json.data.tweetResult.result;
if (tweetResult.tweet) tweetResult = tweetResult.tweet;
const tweetUser = tweetResult.core?.user_results?.result;
normalizeLegacyTweet(tweetResult);
if (!tweetUser) return [];
normalizeLegacyUser(tweetUser);
let result = extractMediaFromTweet(tweetResult, tweetUser, "original");
result = sortMediaByVisualOrder(result);
if (tweetResult.quoted_status_result?.result) {
let quotedTweet = tweetResult.quoted_status_result.result;
if (quotedTweet.tweet) quotedTweet = quotedTweet.tweet;
const quotedUser = quotedTweet.core?.user_results?.result;
if (quotedTweet && quotedUser) {
normalizeLegacyTweet(quotedTweet);
normalizeLegacyUser(quotedUser);
const sortedQuotedMedia = sortMediaByVisualOrder(extractMediaFromTweet(quotedTweet, quotedUser, "quoted"));
const adjustedResult = result.map((media) => ({
...media,
index: media.index + sortedQuotedMedia.length
}));
result = [...sortedQuotedMedia, ...adjustedResult];
}
}
return result;
}
static clearCache() {
TwitterAPI.requestCache.clear();
}
static getCacheSize() {
return TwitterAPI.requestCache.size;
}
static async apiRequest(url) {
const csrfToken = await getCsrfTokenAsync() ?? getCsrfToken() ?? "";
const csrfHash = simpleHash(csrfToken);
const cached = TwitterAPI.requestCache.get(url);
if (cached) {
const now = Date.now();
const isExpired = now - cached.timestamp > TWITTER_API_CONFIG.CACHE_TTL_MS;
if (!isExpired && !(cached.csrfHash !== csrfHash)) {
TwitterAPI.requestCache.delete(url);
TwitterAPI.requestCache.set(url, cached);
;
return cached.response;
}
TwitterAPI.requestCache.delete(url);
;
}
const headers = new Headers({
authorization: TWITTER_API_CONFIG.GUEST_AUTHORIZATION,
"x-csrf-token": csrfToken,
"x-twitter-client-language": "en",
"x-twitter-active-user": "yes",
"content-type": "application/json",
"x-twitter-auth-type": "OAuth2Session"
});
const locationHeaders = getSafeLocationHeaders();
if (locationHeaders.referer) headers.append("referer", locationHeaders.referer);
if (locationHeaders.origin) headers.append("origin", locationHeaders.origin);
try {
const response = await HttpRequestService.getInstance().get(url, {
headers: Object.fromEntries(headers.entries()),
responseType: "json"
});
if (!response.ok) {
TwitterAPI.requestCache.delete(url);
;
throw new Error(`Twitter API request failed: ${response.status} ${response.statusText}`);
}
const json = response.data;
if (json.errors && json.errors.length > 0) ;
else {
if (TwitterAPI.requestCache.size >= TwitterAPI.CACHE_LIMIT) {
const firstKey = TwitterAPI.requestCache.keys().next().value;
if (firstKey) TwitterAPI.requestCache.delete(firstKey);
}
TwitterAPI.requestCache.set(url, {
response: json,
timestamp: Date.now(),
csrfHash
});
}
return json;
} catch (error$1) {
TwitterAPI.requestCache.delete(url);
logger.error("Twitter API request failed:", error$1);
throw error$1;
}
}
static createTweetEndpointUrl(tweetId) {
return buildTweetResultByRestIdUrl({
host: getSafeHost(),
queryId: TWITTER_API_CONFIG.TWEET_RESULT_BY_REST_ID_QUERY_ID,
tweetId,
variables: {
tweetId,
withCommunity: false,
includePromotedContent: false,
withVoice: false
},
features: {
creator_subscriptions_tweet_preview_api_enabled: true,
premium_content_api_read_enabled: false,
communities_web_enable_tweet_community_results_fetch: true,
c9s_tweet_anatomy_moderator_badge_enabled: true,
responsive_web_grok_analyze_button_fetch_trends_enabled: false,
responsive_web_grok_analyze_post_followups_enabled: false,
responsive_web_jetfuel_frame: false,
responsive_web_grok_share_attachment_enabled: true,
articles_preview_enabled: true,
responsive_web_edit_tweet_api_enabled: true,
graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
view_counts_everywhere_api_enabled: true,
longform_notetweets_consumption_enabled: true,
responsive_web_twitter_article_tweet_consumption_enabled: true,
tweet_awards_web_tipping_enabled: false,
responsive_web_grok_show_grok_translated_post: false,
responsive_web_grok_analysis_button_from_backend: false,
creator_subscriptions_quote_tweet_preview_enabled: false,
freedom_of_speech_not_reach_fetch_enabled: true,
standardized_nudges_misinfo: true,
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
longform_notetweets_rich_text_read_enabled: true,
longform_notetweets_inline_media_enabled: true,
profile_label_improvements_pcf_label_in_post_enabled: true,
rweb_tipjar_consumption_enabled: true,
verified_phone_label_enabled: false,
responsive_web_grok_image_annotation_enabled: true,
responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
responsive_web_graphql_timeline_navigation_enabled: true,
responsive_web_enhance_cards_enabled: false
},
fieldToggles: {
withArticleRichContentState: true,
withArticlePlainText: false,
withGrokAnalyze: false,
withDisallowedReplyControls: false
}
});
}
};
}));
function isMediaElement(element) {
if (!element) return false;
return element.tagName === "IMG" || element.tagName === "VIDEO";
}
function findMediaElementInDOM(target, options = {}) {
const { maxDescendantDepth, maxAncestorHops } = {
...DEFAULT_TRAVERSAL_OPTIONS,
...options
};
if (isMediaElement(target)) return target;
const descendant = findMediaDescendant(target, {
includeRoot: false,
maxDepth: maxDescendantDepth
});
if (descendant) return descendant;
let branch = target;
for (let hops = 0; hops < maxAncestorHops && branch; hops++) {
branch = branch.parentElement;
if (!branch) break;
const ancestorMedia = findMediaDescendant(branch, {
includeRoot: true,
maxDepth: maxDescendantDepth
});
if (ancestorMedia) return ancestorMedia;
}
return null;
}
function extractMediaUrlFromElement(element) {
if (element instanceof HTMLImageElement) {
const attr = element.getAttribute("src");
return pickFirstTruthy([
element.currentSrc || null,
attr ? element.src : null,
attr
]);
}
if (element instanceof HTMLVideoElement) {
const attr = element.getAttribute("src");
const posterAttr = element.getAttribute("poster");
return pickFirstTruthy([
element.currentSrc || null,
attr ? element.src : null,
attr,
posterAttr ? element.poster : null,
posterAttr
]);
}
return null;
}
function findMediaDescendant(root, { includeRoot, maxDepth }) {
const queue = [{
node: root,
depth: 0
}];
while (queue.length) {
const current = queue.shift();
if (!current) break;
const { node, depth } = current;
if ((includeRoot || node !== root) && isMediaElement(node)) return node;
if (depth >= maxDepth) continue;
for (const child of Array.from(node.children)) if (child instanceof HTMLElement) queue.push({
node: child,
depth: depth + 1
});
}
return null;
}
function pickFirstTruthy(values) {
for (const value of values) if (value) return value;
return null;
}
var DEFAULT_MAX_DESCENDANT_DEPTH, DEFAULT_MAX_ANCESTOR_HOPS, DEFAULT_TRAVERSAL_OPTIONS;
var init_media_element_utils = __esmMin((() => {
DEFAULT_MAX_DESCENDANT_DEPTH = 6;
DEFAULT_MAX_ANCESTOR_HOPS = 3;
DEFAULT_TRAVERSAL_OPTIONS = {
maxDescendantDepth: DEFAULT_MAX_DESCENDANT_DEPTH,
maxAncestorHops: DEFAULT_MAX_ANCESTOR_HOPS
};
}));
function determineClickedIndex(clickedElement, mediaItems) {
try {
const mediaElement = findMediaElementInDOM(clickedElement);
if (!mediaElement) return 0;
const elementUrl = extractMediaUrlFromElement(mediaElement);
if (!elementUrl) return 0;
const normalizedElementUrl = normalizeMediaUrl(elementUrl);
if (!normalizedElementUrl) return 0;
const index = mediaItems.findIndex((item, i) => {
if (!item) return false;
const itemUrl = item.url || item.originalUrl;
if (itemUrl && normalizeMediaUrl(itemUrl) === normalizedElementUrl) {
;
return true;
}
if (item.thumbnailUrl && normalizeMediaUrl(item.thumbnailUrl) === normalizedElementUrl) {
;
return true;
}
return false;
});
if (index !== -1) return index;
;
return 0;
} catch (error$1) {
;
return 0;
}
}
var init_determine_clicked_index = __esmMin((() => {
init_logging();
init_media_dimensions();
init_media_element_utils();
}));
function sanitizeHTML(html, config = DEFAULT_CONFIG) {
if (!html || typeof html !== "string") return "";
const doc = new DOMParser().parseFromString(html, "text/html");
function sanitizeNode(node) {
if (node.nodeType === Node.TEXT_NODE) return node.cloneNode(false);
if (node.nodeType !== Node.ELEMENT_NODE) return null;
const element = node;
const tagName = element.tagName.toLowerCase();
if (!config.allowedTags.includes(tagName)) return document.createTextNode(element.textContent || "");
const sanitized = document.createElement(tagName);
const allowedAttrs = config.allowedAttributes[tagName] || [];
for (const attr of Array.from(element.attributes)) {
const attrName = attr.name.toLowerCase();
if (attrName.startsWith("on")) continue;
if (!allowedAttrs.includes(attrName)) continue;
if ((attrName === "href" || attrName === "src") && !isSafeAttributeUrl(attr.value)) continue;
sanitized.setAttribute(attrName, attr.value);
}
if (tagName === "a" && sanitized.getAttribute("target") === "_blank") sanitized.setAttribute("rel", "noopener noreferrer");
for (const child of Array.from(element.childNodes)) {
const sanitizedChild = sanitizeNode(child);
if (sanitizedChild) sanitized.appendChild(sanitizedChild);
}
return sanitized;
}
const bodyContent = doc.body;
const sanitizedBody = document.createElement("div");
for (const child of Array.from(bodyContent.childNodes)) {
const sanitized = sanitizeNode(child);
if (sanitized) sanitizedBody.appendChild(sanitized);
}
return sanitizedBody.innerHTML;
}
function isSafeAttributeUrl(url) {
return isUrlAllowed(url, HTML_ATTRIBUTE_URL_POLICY);
}
var DEFAULT_CONFIG;
var init_html_sanitizer = __esmMin((() => {
init_url();
DEFAULT_CONFIG = {
allowedTags: [
"a",
"span",
"br",
"strong",
"em",
"img"
],
allowedAttributes: {
a: [
"href",
"title",
"rel",
"target",
"dir"
],
span: ["class", "dir"],
img: [
"alt",
"src",
"draggable"
]
}
};
}));
function extractTweetTextHTML(tweetArticle) {
if (!tweetArticle) return void 0;
try {
const tweetTextElement = tweetArticle.querySelector(TWEET_TEXT_SELECTOR);
if (!tweetTextElement) {
;
return;
}
const rawHTML = tweetTextElement.innerHTML;
if (!rawHTML?.trim()) {
;
return;
}
const sanitized = sanitizeHTML(rawHTML);
if (!sanitized?.trim()) {
;
return;
}
;
return sanitized;
} catch (error$1) {
logger.error("[extractTweetTextHTML] Error extracting tweet text HTML:", error$1);
return;
}
}
function extractTweetTextHTMLFromClickedElement(element, maxDepth = 10) {
let current = element;
let depth = 0;
while (current && depth < maxDepth) {
if (current.tagName === "ARTICLE" && (current.hasAttribute("data-testid") || current.querySelector("article[data-testid=\"tweet\"]"))) return extractTweetTextHTML(current);
current = current.parentElement;
depth++;
}
;
}
var init_tweet_extractor = __esmMin((() => {
init_selectors();
init_logging();
init_html_sanitizer();
}));
var TwitterAPIExtractor;
var init_twitter_api_extractor = __esmMin((() => {
init_logging();
init_media_factory();
init_twitter_api_client();
init_determine_clicked_index();
init_tweet_extractor();
TwitterAPIExtractor = class {
async extract(tweetInfo, clickedElement, _options, extractionId) {
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
const startedAt = now();
try {
;
const apiMedias = await TwitterAPI.getTweetMedias(tweetInfo.tweetId);
if (!apiMedias || apiMedias.length === 0) {
const totalProcessingTime$1 = Math.max(0, now() - startedAt);
const failure = this.createFailureResult("No media found in API response");
return {
...failure,
metadata: {
...failure.metadata ?? {},
totalProcessingTime: totalProcessingTime$1
}
};
}
const mediaItems = await convertAPIMediaToMediaInfo(apiMedias, tweetInfo, extractTweetTextHTMLFromClickedElement(clickedElement));
const clickedIndex = determineClickedIndex(clickedElement, mediaItems);
const totalProcessingTime = Math.max(0, now() - startedAt);
return {
success: true,
mediaItems,
clickedIndex,
metadata: {
extractedAt: Date.now(),
sourceType: "twitter-api",
strategy: "api-extraction",
totalProcessingTime,
apiMediaCount: apiMedias.length
},
tweetInfo
};
} catch (error$1) {
;
const totalProcessingTime = Math.max(0, now() - startedAt);
const failure = this.createFailureResult(getErrorMessage(error$1) || "API extraction failed");
return {
...failure,
metadata: {
...failure.metadata ?? {},
totalProcessingTime
}
};
}
}
createFailureResult(error$1) {
return {
success: false,
mediaItems: [],
clickedIndex: 0,
metadata: {
extractedAt: Date.now(),
sourceType: "twitter-api",
strategy: "api-extraction-failed",
error: error$1,
totalProcessingTime: 0
},
tweetInfo: null
};
}
};
}));
var ExtractionError;
var init_media_types = __esmMin((() => {
ExtractionError = class extends Error {
constructor(code, message, originalError) {
super(message);
this.code = code;
this.originalError = originalError;
this.name = "ExtractionError";
}
};
}));
var ErrorCode;
var init_result_types = __esmMin((() => {
ErrorCode = {
NONE: "NONE",
CANCELLED: "CANCELLED",
NETWORK: "NETWORK",
TIMEOUT: "TIMEOUT",
EMPTY_INPUT: "EMPTY_INPUT",
ALL_FAILED: "ALL_FAILED",
PARTIAL_FAILED: "PARTIAL_FAILED",
UNKNOWN: "UNKNOWN",
ELEMENT_NOT_FOUND: "ELEMENT_NOT_FOUND",
INVALID_ELEMENT: "INVALID_ELEMENT",
NO_MEDIA_FOUND: "NO_MEDIA_FOUND",
INVALID_URL: "INVALID_URL",
PERMISSION_DENIED: "PERMISSION_DENIED"
};
}));
function createId() {
try {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID().replaceAll("-", "");
} catch {}
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
}
function createPrefixedId(prefix, separator$1 = "_") {
return `${prefix}${separator$1}${createId()}`;
}
function createContextId(context) {
return createPrefixedId(context, ":");
}
var media_extraction_service_exports = /* @__PURE__ */ __export({ MediaExtractionService: () => MediaExtractionService }, 1);
var MediaExtractionService;
var init_media_extraction_service = __esmMin((() => {
init_selectors();
init_logging();
init_tweet_info_extractor();
init_twitter_api_extractor();
init_media_types();
init_result_types();
init_media_dimensions();
MediaExtractionService = class {
tweetInfoExtractor;
apiExtractor;
constructor() {
this.tweetInfoExtractor = new TweetInfoExtractor();
this.apiExtractor = new TwitterAPIExtractor();
}
async extractFromClickedElement(element, options = {}) {
const extractionId = this.generateExtractionId();
;
try {
const tweetInfo = await this.tweetInfoExtractor.extract(element);
if (!tweetInfo?.tweetId) {
;
return this.createErrorResult("No tweet information found");
}
const apiResult = await this.apiExtractor.extract(tweetInfo, element, options, extractionId);
if (apiResult.success && apiResult.mediaItems.length > 0) return this.finalizeResult({
...apiResult,
tweetInfo: this.mergeTweetInfoMetadata(tweetInfo, apiResult.tweetInfo)
});
logger.error(`[MediaExtractor] ${extractionId}: API extraction failed`);
return this.createApiErrorResult(apiResult, tweetInfo);
} catch (error$1) {
logger.error(`[MediaExtractor] ${extractionId}: Extraction error`, error$1);
return this.createErrorResult(error$1);
}
}
async extractAllFromContainer(container$2, options = {}) {
try {
const firstMedia = container$2.querySelector(TWITTER_MEDIA_SELECTOR);
if (!firstMedia || !(firstMedia instanceof HTMLElement)) return this.createErrorResult("No media found in container");
return this.extractFromClickedElement(firstMedia, options);
} catch (error$1) {
return this.createErrorResult(error$1);
}
}
generateExtractionId() {
return createPrefixedId("simp");
}
createErrorResult(error$1) {
const errorMessage = getErrorMessage(error$1) || "Unknown error";
return {
success: false,
mediaItems: [],
clickedIndex: 0,
metadata: {
extractedAt: Date.now(),
sourceType: "extraction-failed",
strategy: "media-extraction",
error: errorMessage
},
tweetInfo: null,
errors: [new ExtractionError(ErrorCode.NO_MEDIA_FOUND, errorMessage)]
};
}
createApiErrorResult(apiResult, tweetInfo) {
const apiErrorMessage = apiResult.metadata?.error ?? apiResult.errors?.[0]?.message ?? "API extraction failed";
const mergedTweetInfo = this.mergeTweetInfoMetadata(tweetInfo, apiResult.tweetInfo);
return {
success: false,
mediaItems: [],
clickedIndex: apiResult.clickedIndex ?? 0,
metadata: {
...apiResult.metadata ?? {},
strategy: "api-extraction",
sourceType: "extraction-failed"
},
tweetInfo: mergedTweetInfo,
errors: [new ExtractionError(ErrorCode.NO_MEDIA_FOUND, apiErrorMessage)]
};
}
finalizeResult(result) {
if (!result.success) return result;
const uniqueItems = removeDuplicateMediaItems(result.mediaItems);
if (uniqueItems.length === 0) return {
...result,
mediaItems: [],
clickedIndex: 0
};
const adjustedIndex = adjustClickedIndexAfterDeduplication(result.mediaItems, uniqueItems, result.clickedIndex ?? 0);
return {
...result,
mediaItems: uniqueItems,
clickedIndex: adjustedIndex
};
}
mergeTweetInfoMetadata(base, override) {
if (!base) return override ?? null;
if (!override) return base;
return {
...base,
...override,
metadata: {
...base.metadata ?? {},
...override.metadata ?? {}
}
};
}
};
}));
function sanitize(name) {
return name.replace(/[<>:"/\\|?*]/g, "_").replace(/^[\s.]+|[\s.]+$/g, "").slice(0, 255) || "media";
}
function resolveNowMs$1(nowMs$1) {
return Number.isFinite(nowMs$1) ? nowMs$1 : 0;
}
function getExtension(url) {
try {
const path = url.split("?")[0];
if (!path) return "jpg";
const ext = path.split(".").pop();
if (ext && /^(jpg|jpeg|png|gif|webp|mp4|mov|avi)$/i.test(ext)) return ext.toLowerCase();
} catch {}
return "jpg";
}
function getIndexFromMediaId(mediaId) {
if (!mediaId) return null;
const match = mediaId.match(/_media_(\d+)$/) || mediaId.match(/_(\d+)$/);
if (match) {
const idx = safeParseInt(match[1], 10);
return mediaId.includes("_media_") ? (idx + 1).toString() : match[1] ?? null;
}
return null;
}
function normalizeIndex(index) {
if (index === void 0 || index === null) return "1";
const num = typeof index === "string" ? safeParseInt(index, 10) : index;
return Number.isNaN(num) || num < 1 ? "1" : num.toString();
}
function resolveMetadata(media, fallbackUsername) {
let username = null;
let tweetId = null;
if (media.sourceLocation === "quoted" && media.quotedUsername && media.quotedTweetId) {
username = media.quotedUsername;
tweetId = media.quotedTweetId;
} else {
tweetId = media.tweetId ?? null;
if (media.tweetUsername && media.tweetUsername !== "unknown") username = media.tweetUsername;
else {
const url = ("originalUrl" in media ? media.originalUrl : null) || media.url;
if (typeof url === "string") username = extractUsernameFromUrl(url, { strictHost: true });
}
}
if (!username && fallbackUsername) username = fallbackUsername;
return {
username,
tweetId
};
}
function generateMediaFilename(media, options = {}) {
try {
if (media.filename) return sanitize(media.filename);
const nowMs$1 = resolveNowMs$1(options.nowMs);
const extension = options.extension ?? getExtension(media.originalUrl ?? media.url);
const index = getIndexFromMediaId(media.id) ?? normalizeIndex(options.index);
const { username, tweetId } = resolveMetadata(media, options.fallbackUsername);
if (username && tweetId) return sanitize(`${username}_${tweetId}_${index}.${extension}`);
if (tweetId && /^\d+$/.test(tweetId)) return sanitize(`tweet_${tweetId}_${index}.${extension}`);
return sanitize(`${options.fallbackPrefix ?? "media"}_${nowMs$1}_${index}.${extension}`);
} catch {
return `media_${resolveNowMs$1(options.nowMs)}.${options.extension || "jpg"}`;
}
}
function generateZipFilename(mediaItems, options = {}) {
try {
const firstItem = mediaItems[0];
if (firstItem) {
const { username, tweetId } = resolveMetadata(firstItem);
if (username && tweetId) return sanitize(`${username}_${tweetId}.zip`);
}
return sanitize(`${options.fallbackPrefix ?? "xcom_gallery"}_${resolveNowMs$1(options.nowMs)}.zip`);
} catch {
return `download_${resolveNowMs$1(options.nowMs)}.zip`;
}
}
var init_filename_utils = __esmMin((() => {
init_url();
}));
var init_filename$1 = __esmMin((() => {
init_filename_utils();
}));
function planSingleDownload(input) {
const { method, mediaUrl, filename, hasProvidedBlob } = input;
if (method === "fetch_blob") {
if (hasProvidedBlob) return {
strategy: "anchor_blob",
filename
};
return {
strategy: "fetch_blob",
url: mediaUrl,
filename
};
}
if (method === "gm_download") return {
strategy: "gm_download",
url: mediaUrl,
filename,
useBlobUrl: hasProvidedBlob
};
return {
strategy: "none",
filename,
error: "No download method"
};
}
function planBulkDownload(input) {
return {
items: input.mediaItems.map((media) => ({
url: media.url,
desiredName: input.nowMs === void 0 ? generateMediaFilename(media) : generateMediaFilename(media, { nowMs: input.nowMs }),
blob: input.prefetchedBlobs?.get(media.url)
})),
zipFilename: input.zipFilename || (input.nowMs === void 0 ? generateZipFilename(input.mediaItems) : generateZipFilename(input.mediaItems, { nowMs: input.nowMs }))
};
}
function planZipSave(method) {
if (method === "gm_download") return "gm_download";
if (method === "fetch_blob") return "anchor";
return "none";
}
var init_download_plan = __esmMin((() => {
init_filename$1();
}));
function detectDownloadCapability() {
const rawGMDownload = resolveGMDownload();
const gmDownload = typeof rawGMDownload === "function" ? rawGMDownload : void 0;
const hasGMDownload = isGMAPIAvailable("download") && Boolean(gmDownload);
const hasFetch = typeof fetch === "function";
const hasBlob = typeof Blob !== "undefined" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function";
let method = "none";
if (hasGMDownload) method = "gm_download";
else if (hasFetch && hasBlob) method = "fetch_blob";
return {
hasGMDownload,
hasFetch,
hasBlob,
method,
gmDownload
};
}
async function downloadWithFetchBlob(url, filename, options = {}) {
const { signal, onProgress, timeout = 3e4 } = options;
if (signal?.aborted) return {
success: false,
error: USER_CANCELLED_MESSAGE
};
onProgress?.({
phase: "preparing",
current: 0,
total: 1,
percentage: 0,
filename
});
const timeoutController = createTimeoutController(timeout);
const combinedSignal = signal ? combineSignals([signal, timeoutController.signal]) : timeoutController.signal;
try {
if (combinedSignal.aborted) {
const reason = combinedSignal.reason;
if (isTimeoutError(reason)) return {
success: false,
error: "Download timeout"
};
return {
success: false,
error: USER_CANCELLED_MESSAGE
};
}
const response = await fetch(url, {
signal: combinedSignal,
mode: "cors",
credentials: "omit"
});
if (!response.ok) return {
success: false,
error: `HTTP ${response.status}: ${response.statusText}`
};
const contentLength = response.headers.get("content-length");
const totalBytes = contentLength ? parseInt(contentLength, 10) : 0;
let blob;
if (totalBytes > 0 && response.body) {
const reader = response.body.getReader();
const chunks = [];
let receivedBytes = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
chunks.push(value);
receivedBytes += value.length;
onProgress?.({
phase: "downloading",
current: 0,
total: 1,
percentage: Math.round(receivedBytes / totalBytes * 100),
filename
});
}
}
blob = new Blob(chunks, { type: response.headers.get("content-type") || "application/octet-stream" });
} else blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);
try {
await triggerAnchorDownload(blobUrl, filename);
onProgress?.({
phase: "complete",
current: 1,
total: 1,
percentage: 100,
filename
});
;
return {
success: true,
filename
};
} finally {
URL.revokeObjectURL(blobUrl);
}
} catch (error$1) {
if (combinedSignal.aborted) {
const reason = combinedSignal.reason;
if (isTimeoutError(reason)) return {
success: false,
error: "Download timeout"
};
return {
success: false,
error: USER_CANCELLED_MESSAGE
};
}
if (isAbortError(error$1)) return {
success: false,
error: USER_CANCELLED_MESSAGE
};
const errorMsg = getErrorMessage(error$1);
logger.error("[FallbackDownload] Download failed:", error$1);
onProgress?.({
phase: "complete",
current: 1,
total: 1,
percentage: 0,
filename
});
return {
success: false,
error: errorMsg
};
} finally {
timeoutController.cancel();
}
}
async function triggerAnchorDownload(url, filename) {
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = filename;
anchor.style.display = "none";
document.body.appendChild(anchor);
anchor.click();
await delay(100);
try {
anchor.remove();
} catch {}
}
async function downloadBlobWithAnchor(blob, filename, options = {}) {
const { onProgress } = options;
onProgress?.({
phase: "preparing",
current: 0,
total: 1,
percentage: 0,
filename
});
const blobUrl = URL.createObjectURL(blob);
try {
await triggerAnchorDownload(blobUrl, filename);
onProgress?.({
phase: "complete",
current: 1,
total: 1,
percentage: 100,
filename
});
;
return {
success: true,
filename
};
} catch (error$1) {
const errorMsg = getErrorMessage(error$1);
logger.error("[FallbackDownload] Blob download failed:", error$1);
onProgress?.({
phase: "complete",
current: 1,
total: 1,
percentage: 0,
filename
});
return {
success: false,
error: errorMsg
};
} finally {
URL.revokeObjectURL(blobUrl);
}
}
var init_fallback_download = __esmMin((() => {
init_delay();
init_signal_utils();
init_cancellation();
init_userscript();
init_logging();
}));
function toActionCommand(plan, timeoutMs) {
if (plan.strategy === "anchor_blob") return {
type: "DOWNLOAD_BLOB_WITH_ANCHOR",
filename: plan.filename
};
if (plan.strategy === "fetch_blob") return {
type: "DOWNLOAD_WITH_FETCH_BLOB",
url: plan.url,
filename: plan.filename,
timeoutMs
};
if (plan.strategy === "gm_download") return {
type: "DOWNLOAD_WITH_GM_DOWNLOAD",
url: plan.url,
filename: plan.filename,
timeoutMs,
useBlobUrl: plan.useBlobUrl
};
return {
type: "FAIL",
filename: plan.filename,
error: plan.error
};
}
function createSingleDownloadCommands(input) {
const timeoutMs = input.timeoutMs ?? 3e4;
const action = toActionCommand(planSingleDownload({
method: input.method,
mediaUrl: input.mediaUrl,
filename: input.filename,
hasProvidedBlob: input.hasProvidedBlob
}), timeoutMs);
return action.type === "DOWNLOAD_WITH_GM_DOWNLOAD" || action.type === "FAIL" ? [{
type: "REPORT_PROGRESS",
phase: "preparing",
percentage: 0,
filename: input.filename
}, action] : [action];
}
var init_single_download_commands = __esmMin((() => {
init_download_plan();
}));
var init_filename = __esmMin((() => {
init_filename_utils();
}));
async function executeSingleDownloadCommand(cmd, options, capability, blob) {
switch (cmd.type) {
case "FAIL": return {
success: false,
error: cmd.error
};
case "REPORT_PROGRESS":
options.onProgress?.({
phase: cmd.phase,
current: 0,
total: 1,
percentage: cmd.percentage,
filename: cmd.filename
});
return {
success: true,
filename: cmd.filename
};
case "DOWNLOAD_BLOB_WITH_ANCHOR":
if (!blob) return {
success: false,
error: "Blob unavailable"
};
return downloadBlobWithAnchor(blob, cmd.filename, {
signal: options.signal,
onProgress: options.onProgress
});
case "DOWNLOAD_WITH_FETCH_BLOB": return downloadWithFetchBlob(cmd.url, cmd.filename, {
signal: options.signal,
onProgress: options.onProgress,
timeout: cmd.timeoutMs
});
case "DOWNLOAD_WITH_GM_DOWNLOAD": {
const gmDownload = capability.gmDownload;
if (!gmDownload) return {
success: false,
error: "GM_download unavailable"
};
let url = cmd.url;
let isBlobUrl = false;
if (cmd.useBlobUrl) {
if (!blob) return {
success: false,
error: "Blob unavailable"
};
url = URL.createObjectURL(blob);
isBlobUrl = true;
}
return await new Promise((resolve$1) => {
let timer;
const cleanup$1 = () => {
if (isBlobUrl) URL.revokeObjectURL(url);
if (timer) {
globalTimerManager.clearTimeout(timer);
timer = void 0;
}
};
let settled = false;
const settle = (result, completePercentage) => {
if (settled) return;
settled = true;
if (completePercentage !== void 0) options.onProgress?.({
phase: "complete",
current: 1,
total: 1,
percentage: completePercentage,
filename: cmd.filename
});
cleanup$1();
resolve$1(result);
};
timer = globalTimerManager.setTimeout(() => {
settle({
success: false,
error: "Download timeout"
}, 0);
}, cmd.timeoutMs);
try {
gmDownload({
url,
name: cmd.filename,
onload: () => {
;
settle({
success: true,
filename: cmd.filename
}, 100);
},
onerror: (error$1) => {
const errorMsg = getErrorMessage(error$1);
logger.error("[SingleDownload] Download failed:", error$1);
settle({
success: false,
error: errorMsg
}, 0);
},
ontimeout: () => {
settle({
success: false,
error: "Download timeout"
}, 0);
},
onprogress: (progress) => {
if (settled) return;
if (options.onProgress && progress.total > 0) options.onProgress({
phase: "downloading",
current: 1,
total: 1,
percentage: Math.round(progress.loaded / progress.total * 100),
filename: cmd.filename
});
}
});
} catch (error$1) {
settle({
success: false,
error: getErrorMessage(error$1)
});
}
});
}
default: return {
success: false,
error: "Unknown download command"
};
}
}
async function downloadSingleFile(media, options = {}, capability) {
if (options.signal?.aborted) return {
success: false,
error: "User cancelled download"
};
const filename = generateMediaFilename(media, { nowMs: Date.now() });
const effectiveCapability = capability ?? detectDownloadCapability();
const cmds = createSingleDownloadCommands({
method: effectiveCapability.method,
mediaUrl: media.url,
filename,
hasProvidedBlob: Boolean(options.blob),
timeoutMs: 3e4
});
let lastOk = {
success: true,
filename
};
for (const cmd of cmds) {
const result = await executeSingleDownloadCommand(cmd, options, effectiveCapability, options.blob);
if (!result.success) return result;
lastOk = result;
}
return lastOk;
}
var init_single_download = __esmMin((() => {
init_single_download_commands();
init_logging();
init_filename();
init_timer_management();
init_fallback_download();
}));
function calculateBackoff(attempt, baseDelayMs, maxDelayMs) {
const exponentialDelay = baseDelayMs * 2 ** attempt;
const totalDelay = exponentialDelay + Math.random() * .25 * exponentialDelay;
return Math.min(Math.floor(totalDelay), maxDelayMs);
}
async function withRetry(operation, options = {}) {
const { maxAttempts = DEFAULT_OPTIONS.maxAttempts, baseDelayMs = DEFAULT_OPTIONS.baseDelayMs, maxDelayMs = DEFAULT_OPTIONS.maxDelayMs, signal, onRetry, shouldRetry = () => true } = options;
let lastError;
let attempt = 0;
while (attempt < maxAttempts) {
if (signal?.aborted) return {
success: false,
error: signal.reason ?? new DOMException("Operation was aborted", "AbortError"),
attempts: attempt
};
try {
return {
success: true,
data: await operation(),
attempts: attempt + 1
};
} catch (error$1) {
lastError = error$1;
attempt++;
if (isAbortError(error$1)) return {
success: false,
error: error$1,
attempts: attempt
};
if (!shouldRetry(error$1)) return {
success: false,
error: error$1,
attempts: attempt
};
if (attempt >= maxAttempts) break;
const delayMs = calculateBackoff(attempt - 1, baseDelayMs, maxDelayMs);
onRetry?.(attempt, error$1, delayMs);
try {
await delay(delayMs, signal);
} catch (delayError) {
if (isAbortError(delayError)) return {
success: false,
error: delayError,
attempts: attempt
};
throw delayError;
}
}
}
return {
success: false,
error: lastError,
attempts: attempt
};
}
var DEFAULT_OPTIONS;
var init_retry = __esmMin((() => {
init_delay();
DEFAULT_OPTIONS = {
maxAttempts: 3,
baseDelayMs: 200,
maxDelayMs: 1e4
};
}));
async function fetchArrayBufferWithRetry(url, retries, signal, backoffBaseMs = 200) {
if (signal?.aborted) throw getUserCancelledAbortErrorFromSignal(signal);
const httpService = HttpRequestService.getInstance();
const result = await withRetry(async () => {
if (signal?.aborted) throw getUserCancelledAbortErrorFromSignal(signal);
const options = {
responseType: "arraybuffer",
timeout: 3e4,
...signal ? { signal } : {}
};
const response = await httpService.get(url, options);
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
return new Uint8Array(response.data);
}, {
maxAttempts: Math.max(1, retries + 1),
baseDelayMs: backoffBaseMs,
...signal ? { signal } : {}
});
if (result.success) return result.data;
if (signal?.aborted) throw getUserCancelledAbortErrorFromSignal(signal);
if (isAbortError(result.error)) throw result.error;
throw result.error;
}
var init_retry_fetch = __esmMin((() => {
init_delay();
init_retry();
init_cancellation();
init_http_request_service();
}));
function ensureUniqueFilenameFactory() {
const usedNames = /* @__PURE__ */ new Set();
const baseCounts = /* @__PURE__ */ new Map();
return (desired) => {
if (!usedNames.has(desired)) {
usedNames.add(desired);
baseCounts.set(desired, 0);
return desired;
}
const lastDot = desired.lastIndexOf(".");
const name = lastDot > 0 ? desired.slice(0, lastDot) : desired;
const ext = lastDot > 0 ? desired.slice(lastDot) : "";
const baseKey = desired;
let count = baseCounts.get(baseKey) ?? 0;
while (true) {
count += 1;
const candidate = `${name}-${count}${ext}`;
if (!usedNames.has(candidate)) {
baseCounts.set(baseKey, count);
usedNames.add(candidate);
return candidate;
}
}
};
}
function ensureCRC32Table() {
if (crc32Table) return crc32Table;
const table = new Uint32Array(256);
const polynomial = 3988292384;
for (let i = 0; i < 256; i++) {
let crc = i;
for (let j = 0; j < 8; j++) crc = crc & 1 ? crc >>> 1 ^ polynomial : crc >>> 1;
table[i] = crc >>> 0;
}
crc32Table = table;
return table;
}
function encodeUtf8(value) {
return textEncoder.encode(value);
}
function calculateCRC32(data) {
const table = ensureCRC32Table();
let crc = 4294967295;
for (let i = 0; i < data.length; i++) crc = crc >>> 8 ^ (table[(crc ^ data[i]) & 255] ?? 0);
return (crc ^ 4294967295) >>> 0;
}
function writeUint16LEToBuffer(buffer, offset, value) {
buffer[offset] = value & 255;
buffer[offset + 1] = value >>> 8 & 255;
}
function writeUint32LEToBuffer(buffer, offset, value) {
buffer[offset] = value & 255;
buffer[offset + 1] = value >>> 8 & 255;
buffer[offset + 2] = value >>> 16 & 255;
buffer[offset + 3] = value >>> 24 & 255;
}
function writeUint16LE(value) {
const bytes = new Uint8Array(2);
writeUint16LEToBuffer(bytes, 0, value);
return bytes;
}
function writeUint32LE(value) {
const bytes = new Uint8Array(4);
writeUint32LEToBuffer(bytes, 0, value);
return bytes;
}
var textEncoder, crc32Table;
var init_zip_utils = __esmMin((() => {
textEncoder = new TextEncoder();
crc32Table = null;
}));
function assertZip32(condition, message) {
if (!condition) throw new Error(`ZIP format limit exceeded (Zip64 not supported): ${message}`);
}
var MAX_UINT16, MAX_UINT32, concat, StreamingZipWriter;
var init_streaming_zip_writer = __esmMin((() => {
init_zip_utils();
MAX_UINT16 = 65535;
MAX_UINT32 = 4294967295;
concat = (arrays) => {
let len = 0;
for (const array of arrays) len += array.length;
const result = new Uint8Array(len);
let offset = 0;
for (const array of arrays) {
result.set(array, offset);
offset += array.length;
}
return result;
};
StreamingZipWriter = class {
chunks = [];
entries = [];
currentOffset = 0;
addFile(filename, data) {
assertZip32(this.entries.length < MAX_UINT16 - 1, `too many entries (count=${this.entries.length + 1})`);
assertZip32(data.length < MAX_UINT32, `file too large (size=${data.length})`);
assertZip32(this.currentOffset < MAX_UINT32, `offset overflow (offset=${this.currentOffset})`);
const filenameBytes = encodeUtf8(filename);
const crc32 = calculateCRC32(data);
const localHeader = concat([
new Uint8Array([
80,
75,
3,
4
]),
writeUint16LE(20),
writeUint16LE(2048),
writeUint16LE(0),
writeUint16LE(0),
writeUint16LE(0),
writeUint32LE(crc32),
writeUint32LE(data.length),
writeUint32LE(data.length),
writeUint16LE(filenameBytes.length),
writeUint16LE(0),
filenameBytes
]);
assertZip32(this.currentOffset + localHeader.length + data.length < MAX_UINT32, `archive too large (offset=${this.currentOffset}, add=${localHeader.length + data.length})`);
this.chunks.push(localHeader, data);
this.entries.push({
filename,
data,
offset: this.currentOffset,
crc32
});
this.currentOffset += localHeader.length + data.length;
}
finalize() {
assertZip32(this.entries.length < MAX_UINT16, `too many entries (count=${this.entries.length})`);
const centralDirStart = this.currentOffset;
assertZip32(centralDirStart < MAX_UINT32, `central directory offset overflow (${centralDirStart})`);
const centralDirChunks = [];
for (const entry of this.entries) {
const filenameBytes = encodeUtf8(entry.filename);
assertZip32(entry.offset < MAX_UINT32, `entry offset overflow (${entry.offset})`);
assertZip32(entry.data.length < MAX_UINT32, `entry too large (size=${entry.data.length})`);
centralDirChunks.push(concat([
new Uint8Array([
80,
75,
1,
2
]),
writeUint16LE(20),
writeUint16LE(20),
writeUint16LE(2048),
writeUint16LE(0),
writeUint16LE(0),
writeUint16LE(0),
writeUint32LE(entry.crc32),
writeUint32LE(entry.data.length),
writeUint32LE(entry.data.length),
writeUint16LE(filenameBytes.length),
writeUint16LE(0),
writeUint16LE(0),
writeUint16LE(0),
writeUint16LE(0),
writeUint32LE(0),
writeUint32LE(entry.offset),
filenameBytes
]));
}
const centralDir = concat(centralDirChunks);
assertZip32(centralDir.length < MAX_UINT32, `central directory too large (size=${centralDir.length})`);
const endOfCentralDir = concat([
new Uint8Array([
80,
75,
5,
6
]),
writeUint16LE(0),
writeUint16LE(0),
writeUint16LE(this.entries.length),
writeUint16LE(this.entries.length),
writeUint32LE(centralDir.length),
writeUint32LE(centralDirStart),
writeUint16LE(0)
]);
return concat([
...this.chunks,
centralDir,
endOfCentralDir
]);
}
getEntryCount() {
return this.entries.length;
}
getCurrentSize() {
return this.currentOffset;
}
};
}));
var zip_exports = /* @__PURE__ */ __export({ StreamingZipWriter: () => StreamingZipWriter }, 1);
var init_zip = __esmMin((() => {
init_streaming_zip_writer();
}));
async function downloadAsZip(items, options = {}) {
const { StreamingZipWriter: StreamingZipWriter$1 } = await Promise.resolve().then(() => (init_zip(), zip_exports));
const writer = new StreamingZipWriter$1();
const concurrency = Math.min(8, Math.max(1, options.concurrency ?? 6));
const retries = Math.max(0, options.retries ?? 0);
const abortSignal = options.signal;
const total = items.length;
let processed = 0;
let successful = 0;
const failures = [];
const ensureUniqueFilename = ensureUniqueFilenameFactory();
const assignedFilenames = items.map((item) => ensureUniqueFilename(item.desiredName));
const usedFilenamesByIndex = new Array(total);
let currentIndex$1 = 0;
const runNext = async () => {
while (currentIndex$1 < total) {
if (abortSignal?.aborted) throw createUserCancelledAbortError(abortSignal.reason);
const index = currentIndex$1++;
const item = items[index];
if (!item) continue;
options.onProgress?.({
phase: "downloading",
current: processed + 1,
total,
percentage: Math.min(100, Math.max(0, Math.round((processed + 1) / total * 100))),
filename: assignedFilenames[index] ?? item.desiredName
});
try {
let data;
if (item.blob) {
const blob = item.blob instanceof Promise ? await item.blob : item.blob;
if (abortSignal?.aborted) throw createUserCancelledAbortError(abortSignal.reason);
data = new Uint8Array(await blob.arrayBuffer());
} else data = await fetchArrayBufferWithRetry(item.url, retries, abortSignal, 200);
if (abortSignal?.aborted) throw createUserCancelledAbortError(abortSignal.reason);
const filename = assignedFilenames[index] ?? item.desiredName;
writer.addFile(filename, data);
usedFilenamesByIndex[index] = filename;
successful++;
} catch (error$1) {
if (abortSignal?.aborted) throw createUserCancelledAbortError(abortSignal.reason);
failures.push({
url: item.url,
error: getErrorMessage(error$1)
});
} finally {
processed++;
}
}
};
const workers = Array.from({ length: concurrency }, () => runNext());
await Promise.all(workers);
const zipBytes = writer.finalize();
const usedFilenames = usedFilenamesByIndex.filter((value) => typeof value === "string");
return {
filesSuccessful: successful,
failures,
zipData: zipBytes,
usedFilenames
};
}
var init_zip_download = __esmMin((() => {
init_cancellation();
init_retry_fetch();
}));
var download_orchestrator_exports = /* @__PURE__ */ __export({ DownloadOrchestrator: () => DownloadOrchestrator }, 1);
var DownloadOrchestrator;
var init_download_orchestrator = __esmMin((() => {
init_download_plan();
init_logging();
init_fallback_download();
init_single_download();
init_zip_download();
init_result_types();
DownloadOrchestrator = class DownloadOrchestrator {
lifecycle;
static singleton = createSingleton(() => new DownloadOrchestrator());
capability = null;
constructor() {
this.lifecycle = createLifecycle("DownloadOrchestrator", {
onInitialize: () => this.onInitialize(),
onDestroy: () => this.onDestroy()
});
}
static getInstance() {
return DownloadOrchestrator.singleton.get();
}
async initialize() {
return this.lifecycle.initialize();
}
destroy() {
this.lifecycle.destroy();
}
isInitialized() {
return this.lifecycle.isInitialized();
}
async onInitialize() {
;
}
onDestroy() {
this.capability = null;
}
getCapability() {
if (!this.capability) this.capability = detectDownloadCapability();
return this.capability;
}
async downloadSingle(media, options = {}) {
return downloadSingleFile(media, options, this.getCapability());
}
async downloadBulk(mediaItems, options = {}) {
const plan = planBulkDownload({
mediaItems,
prefetchedBlobs: options.prefetchedBlobs,
zipFilename: options.zipFilename,
nowMs: Date.now()
});
const items = plan.items;
try {
const result = await downloadAsZip(items, options);
if (result.filesSuccessful === 0) return {
success: false,
status: "error",
filesProcessed: items.length,
filesSuccessful: 0,
error: "No files downloaded",
failures: result.failures,
code: ErrorCode.ALL_FAILED
};
const zipBlob = new Blob([result.zipData], { type: "application/zip" });
const filename = plan.zipFilename;
const capability = this.getCapability();
const saveResult = await this.saveZipBlob(zipBlob, filename, options, capability);
if (!saveResult.success) return {
success: false,
status: "error",
filesProcessed: items.length,
filesSuccessful: result.filesSuccessful,
error: saveResult.error || "Failed to save ZIP file",
failures: result.failures,
code: ErrorCode.ALL_FAILED
};
return {
success: true,
status: result.filesSuccessful === items.length ? "success" : "partial",
filesProcessed: items.length,
filesSuccessful: result.filesSuccessful,
filename,
failures: result.failures,
code: ErrorCode.NONE
};
} catch (error$1) {
return {
success: false,
status: "error",
filesProcessed: items.length,
filesSuccessful: 0,
error: getErrorMessage(error$1) || "Unknown error",
code: ErrorCode.ALL_FAILED
};
}
}
async saveZipBlob(zipBlob, filename, options, capability) {
const saveStrategy = planZipSave(capability.method);
if (saveStrategy === "gm_download" && capability.gmDownload) return this.saveWithGMDownload(capability.gmDownload, zipBlob, filename);
if (saveStrategy === "anchor") {
;
const fallbackResult = await downloadBlobWithAnchor(zipBlob, filename, {
signal: options.signal,
onProgress: options.onProgress
});
return fallbackResult.error ? {
success: fallbackResult.success,
error: fallbackResult.error
} : { success: fallbackResult.success };
}
return {
success: false,
error: "No download method"
};
}
async saveWithGMDownload(gmDownload, blob, filename) {
const url = URL.createObjectURL(blob);
try {
await new Promise((resolve$1, reject) => {
gmDownload({
url,
name: filename,
onload: () => resolve$1(),
onerror: (err) => reject(err),
ontimeout: () => reject(/* @__PURE__ */ new Error("Timeout"))
});
});
return { success: true };
} catch (error$1) {
return {
success: false,
error: getErrorMessage(error$1) || "GM_download failed"
};
} finally {
URL.revokeObjectURL(url);
}
}
};
}));
var MediaService;
var init_media_service = __esmMin((() => {
init_prefetch_manager();
MediaService = class MediaService {
lifecycle;
static singleton = createSingleton(() => new MediaService());
mediaExtraction = null;
webpSupported = null;
prefetchManager = new PrefetchManager(20);
didCleanup = false;
constructor(_options = {}) {
this.lifecycle = createLifecycle("MediaService", {
onInitialize: () => this.onInitialize(),
onDestroy: () => this.onDestroy()
});
}
async initialize() {
return this.lifecycle.initialize();
}
destroy() {
this.cleanupOnce();
this.lifecycle.destroy();
}
isInitialized() {
return this.lifecycle.isInitialized();
}
async onInitialize() {
{
const { MediaExtractionService: MediaExtractionService$1 } = await Promise.resolve().then(() => (init_media_extraction_service(), media_extraction_service_exports));
this.mediaExtraction = new MediaExtractionService$1();
}
await this.detectWebPSupport();
}
onDestroy() {
this.cleanupOnce();
}
static getInstance(_options) {
return MediaService.singleton.get();
}
cleanupOnce() {
if (this.didCleanup) return;
this.didCleanup = true;
this.prefetchManager.destroy();
}
async extractFromClickedElement(element, options = {}) {
if (!this.mediaExtraction) throw new Error("Media Extraction not initialized");
const result = await this.mediaExtraction.extractFromClickedElement(element, options);
if (result.success && result.mediaItems.length > 0) {
const items = result.mediaItems;
const clickedIndex = clampIndex(result.clickedIndex ?? 0, items.length);
const scheduled = /* @__PURE__ */ new Set();
const clickedItem = items[clickedIndex];
if (clickedItem) {
scheduled.add(clickedItem.url);
this.prefetchMedia(clickedItem, "immediate");
}
items.forEach((item, index) => {
if (!item) return;
if (index === clickedIndex) return;
if (scheduled.has(item.url)) return;
scheduled.add(item.url);
this.prefetchMedia(item, "idle");
});
}
return result;
}
async extractAllFromContainer(container$2, options = {}) {
if (!this.mediaExtraction) throw new Error("Media Extraction not initialized");
return this.mediaExtraction.extractAllFromContainer(container$2, options);
}
async detectWebPSupport() {
if (typeof document === "undefined") {
this.webpSupported = false;
return;
}
const canvas = document.createElement("canvas");
if (typeof canvas.toDataURL !== "function") {
this.webpSupported = false;
return;
}
this.webpSupported = canvas.toDataURL("image/webp").indexOf("data:image/webp") === 0;
}
isWebPSupported() {
return this.webpSupported ?? false;
}
getOptimizedImageUrl(originalUrl) {
if (!this.isWebPSupported()) return originalUrl;
return optimizePbsImageUrlToWebP(originalUrl);
}
optimizeWebP(url) {
return this.getOptimizedImageUrl(url);
}
async prefetchMedia(media, schedule = "idle") {
return this.prefetchManager.prefetch(media, schedule);
}
getCachedMedia(url) {
return this.prefetchManager.get(url);
}
cancelAllPrefetch() {
this.prefetchManager.cancelAll();
}
clearPrefetchCache() {
this.prefetchManager.clear();
}
async downloadSingle(media, options = {}) {
const { DownloadOrchestrator: DownloadOrchestrator$1 } = await Promise.resolve().then(() => (init_download_orchestrator(), download_orchestrator_exports));
const downloadService = DownloadOrchestrator$1.getInstance();
const pendingPromise = this.prefetchManager.get(media.url);
let blob;
if (pendingPromise) try {
blob = await pendingPromise;
} catch {}
return downloadService.downloadSingle(media, {
...options,
...blob ? { blob } : {}
});
}
async downloadMultiple(items, options = {}) {
const { DownloadOrchestrator: DownloadOrchestrator$1 } = await Promise.resolve().then(() => (init_download_orchestrator(), download_orchestrator_exports));
return DownloadOrchestrator$1.getInstance().downloadBulk(items, {
...options,
prefetchedBlobs: this.prefetchManager.getCache()
});
}
async downloadBulk(items, options = {}) {
return this.downloadMultiple(Array.from(items), options);
}
async cleanup() {
this.cancelAllPrefetch();
this.clearPrefetchCache();
this.onDestroy();
}
};
}));
var THEME_DOM_ATTRIBUTE;
var init_theme$1 = __esmMin((() => {
THEME_DOM_ATTRIBUTE = "data-theme";
}));
var init_constants = __esmMin((() => {
init_theme$1();
}));
function syncThemeAttributes(theme, options = {}) {
if (typeof document === "undefined") return;
const { scopes, includeDocumentRoot = false } = options;
if (includeDocumentRoot) document.documentElement?.setAttribute(THEME_DOM_ATTRIBUTE, theme);
const targets = scopes ?? document.querySelectorAll(".xeg-theme-scope");
for (const target of Array.from(targets)) if (target instanceof HTMLElement) target.setAttribute(THEME_DOM_ATTRIBUTE, theme);
}
var init_theme = __esmMin((() => {
init_constants();
}));
function wireAbortSignal(signal, onAbort) {
if (!signal) return {
shouldSkip: false,
cleanup: null
};
if (signal.aborted) return {
shouldSkip: true,
cleanup: null
};
const handler = () => {
onAbort();
};
try {
signal.addEventListener("abort", handler, { once: true });
} catch {
return {
shouldSkip: false,
cleanup: null
};
}
return {
shouldSkip: false,
cleanup: () => {
try {
signal.removeEventListener("abort", handler);
} catch {}
}
};
}
var AppEventManager;
var init_app_events = __esmMin((() => {
init_logging();
AppEventManager = class {
appListeners = /* @__PURE__ */ new Map();
constructor(subscriptionManager) {
this.subscriptionManager = subscriptionManager;
}
on(event, callback, options = {}) {
const { signal, once = false } = options;
const id = this.subscriptionManager.generateId("app", event);
if (signal?.aborted) return () => {};
if (!this.appListeners.has(event)) this.appListeners.set(event, /* @__PURE__ */ new Set());
const listeners$1 = this.appListeners.get(event);
let abortCleanup = null;
const wrappedCallback = once ? (data) => {
callback(data);
this.subscriptionManager.remove(id);
} : (data) => {
callback(data);
};
listeners$1.add(wrappedCallback);
this.subscriptionManager.add({
id,
type: "app",
context: event,
cleanup: () => {
listeners$1.delete(wrappedCallback);
abortCleanup?.();
abortCleanup = null;
}
});
if (signal) abortCleanup = wireAbortSignal(signal, () => this.subscriptionManager.remove(id)).cleanup;
;
return () => {
this.subscriptionManager.remove(id);
};
}
once(event, callback, options = {}) {
return this.on(event, callback, {
...options,
once: true
});
}
emit(event, data) {
const listeners$1 = this.appListeners.get(event);
if (!listeners$1 || listeners$1.size === 0) return;
;
listeners$1.forEach((callback) => {
try {
callback(data);
} catch (error$1) {
logger.error(`[AppEventManager] Listener error for "${String(event)}":`, error$1);
}
});
}
_clear() {
this.appListeners.clear();
}
};
}));
function generateListenerId(ctx) {
return ctx ? createContextId(ctx) : createId();
}
function addListener(element, type, listener, options, context) {
const id = generateListenerId(context);
if (!element || typeof element.addEventListener !== "function") {
;
return id;
}
try {
element.addEventListener(type, listener, options);
listeners.set(id, {
id,
element,
type,
listener,
options,
context,
created: 0
});
return id;
} catch (error$1) {
logger.error(`Failed to add listener: ${type}`, {
error: error$1,
context
});
return id;
}
}
function removeEventListenerManaged(id) {
const ctx = listeners.get(id);
if (!ctx) {
;
return false;
}
try {
ctx.element.removeEventListener(ctx.type, ctx.listener, ctx.options);
listeners.delete(id);
return true;
} catch (error$1) {
logger.error(`Failed to remove listener: ${id}`, error$1);
return false;
}
}
var listeners;
var init_listener_manager = __esmMin((() => {
init_logging();
listeners = /* @__PURE__ */ new Map();
}));
var EventManager;
var init_event_manager = __esmMin((() => {
init_logging();
init_listener_manager();
EventManager = class EventManager {
lifecycle;
static singleton = createSingleton(() => new EventManager());
isDestroyed = false;
ownedListenerContexts = /* @__PURE__ */ new Map();
constructor() {
this.lifecycle = createLifecycle("EventManager", {
onInitialize: () => this.onInitialize(),
onDestroy: () => this.onDestroy()
});
}
static getInstance() {
return EventManager.singleton.get();
}
async initialize() {
return this.lifecycle.initialize();
}
destroy() {
this.lifecycle.destroy();
}
isInitialized() {
return this.lifecycle.isInitialized();
}
async onInitialize() {
this.isDestroyed = false;
}
onDestroy() {
this.cleanup();
}
addListener(element, type, listener, options, context) {
if (this.isDestroyed) {
;
return null;
}
const id = addListener(element, type, listener, options, context);
if (id) this.ownedListenerContexts.set(id, context);
return id || null;
}
addEventListener(element, type, listener, options) {
const { context, ...listenerOptions } = options ?? {};
return this.addListener(element, type, listener, listenerOptions, context);
}
removeListener(id) {
if (!this.ownedListenerContexts.has(id)) return false;
this.ownedListenerContexts.delete(id);
return removeEventListenerManaged(id);
}
removeByContext(context) {
const toRemove = [];
for (const [id, ctx] of this.ownedListenerContexts) if (ctx === context) toRemove.push(id);
let count = 0;
for (const id of toRemove) {
this.ownedListenerContexts.delete(id);
if (removeEventListenerManaged(id)) count++;
}
return count;
}
getIsDestroyed() {
return this.isDestroyed;
}
getListenerStatus() {
return {
total: 0,
byContext: {},
byType: {}
};
}
cleanup() {
if (this.isDestroyed) return;
const ids = Array.from(this.ownedListenerContexts.keys());
this.ownedListenerContexts.clear();
for (const id of ids) try {
removeEventListenerManaged(id);
} catch {}
this.isDestroyed = true;
}
};
}));
var DOMEventManager;
var init_dom_events = __esmMin((() => {
init_logging();
init_event_manager();
DOMEventManager = class {
constructor(subscriptionManager) {
this.subscriptionManager = subscriptionManager;
}
addListener(element, type, listener, options = {}) {
const { signal, context, ...listenerOptions } = options;
const id = this.subscriptionManager.generateId("dom", context);
const eventManager = EventManager.getInstance();
if (signal?.aborted) {
;
return id;
}
if (!element || typeof element.addEventListener !== "function") {
;
return id;
}
try {
const managedId = eventManager.addEventListener(element, type, listener, context === void 0 ? listenerOptions : {
...listenerOptions,
context
});
if (!managedId) {
;
return id;
}
let abortCleanup = null;
this.subscriptionManager.add({
id,
type: "dom",
context,
cleanup: () => {
try {
eventManager.removeListener(managedId);
} catch (error$1) {
;
}
abortCleanup?.();
abortCleanup = null;
}
});
if (signal) abortCleanup = wireAbortSignal(signal, () => this.subscriptionManager.remove(id)).cleanup;
;
return id;
} catch (error$1) {
logger.error(`[DOMEventManager] Failed to add listener: ${type}`, error$1);
return id;
}
}
};
}));
var SubscriptionManager;
var init_event_context = __esmMin((() => {
init_logging();
SubscriptionManager = class {
subscriptions = /* @__PURE__ */ new Map();
idCounter = 0;
add(subscription) {
this.subscriptions.set(subscription.id, subscription);
return subscription.id;
}
remove(id) {
const subscription = this.subscriptions.get(id);
if (!subscription) return false;
subscription.cleanup();
this.subscriptions.delete(id);
return true;
}
removeByContext(context) {
const toRemove = [];
for (const [id, sub] of this.subscriptions) if (sub.context === context) toRemove.push(id);
for (const id of toRemove) this.remove(id);
if (toRemove.length > 0) ;
return toRemove.length;
}
removeAll() {
const count = this.subscriptions.size;
for (const subscription of this.subscriptions.values()) subscription.cleanup();
this.subscriptions.clear();
if (count > 0) ;
}
getStats() {
const stats = {
total: this.subscriptions.size,
dom: 0,
app: 0,
byContext: {}
};
for (const sub of this.subscriptions.values()) {
if (sub.type === "dom") stats.dom++;
else stats.app++;
if (sub.context) stats.byContext[sub.context] = (stats.byContext[sub.context] || 0) + 1;
}
return stats;
}
generateId(type, context) {
const id = `${type}:${++this.idCounter}`;
return context ? `${context}:${id}` : id;
}
get size() {
return this.subscriptions.size;
}
has(id) {
return this.subscriptions.has(id);
}
_reset() {
this.subscriptions.clear();
this.idCounter = 0;
}
};
}));
function getEventBus() {
return EventBus.getInstance();
}
var EventBus;
var init_event_bus = __esmMin((() => {
init_logging();
init_app_events();
init_dom_events();
init_event_context();
EventBus = class EventBus {
lifecycle;
static singleton = createSingleton(() => new EventBus());
subscriptionManager;
domEventManager;
appEventManager;
constructor() {
this.subscriptionManager = new SubscriptionManager();
this.domEventManager = new DOMEventManager(this.subscriptionManager);
this.appEventManager = new AppEventManager(this.subscriptionManager);
this.lifecycle = createLifecycle("EventBus", {
onInitialize: () => this.onInitialize(),
onDestroy: () => this.onDestroy()
});
}
static getInstance() {
return EventBus.singleton.get();
}
async initialize() {
return this.lifecycle.initialize();
}
destroy() {
this.lifecycle.destroy();
}
isInitialized() {
return this.lifecycle.isInitialized();
}
async onInitialize() {
;
}
onDestroy() {
this.removeAll();
;
}
addDOMListener(element, type, listener, options = {}) {
return this.domEventManager.addListener(element, type, listener, options);
}
on(event, callback, options = {}) {
return this.appEventManager.on(event, callback, options);
}
once(event, callback, options = {}) {
return this.appEventManager.once(event, callback, options);
}
emit(event, data) {
this.appEventManager.emit(event, data);
}
remove(id) {
return this.subscriptionManager.remove(id);
}
removeByContext(context) {
return this.subscriptionManager.removeByContext(context);
}
removeAll() {
this.subscriptionManager.removeAll();
this.appEventManager._clear();
}
getStats() {
return this.subscriptionManager.getStats();
}
createScopedController(context) {
const controller = new AbortController();
controller.signal.addEventListener("abort", () => {
this.removeByContext(context);
}, { once: true });
return controller;
}
};
}));
var init_events = __esmMin((() => {
init_event_bus();
}));
var ThemeService;
var init_theme_service = __esmMin((() => {
init_constants$1();
init_theme();
init_events();
init_logging();
init_persistent_storage();
ThemeService = class ThemeService {
lifecycle;
storage = getPersistentStorage();
mediaQueryList = null;
mediaQueryListener = null;
domEventsController = null;
currentTheme = "light";
themeSetting = "auto";
listeners = /* @__PURE__ */ new Set();
boundSettingsService = null;
settingsUnsubscribe = null;
observer = null;
static singleton = createSingleton(() => new ThemeService());
static getInstance() {
return ThemeService.singleton.get();
}
earlyRestoreFailed = false;
earlyRestorePromise = null;
didCleanup = false;
constructor() {
this.lifecycle = createLifecycle("ThemeService", {
onInitialize: () => this.onInitialize(),
onDestroy: () => this.onDestroy()
});
if (typeof window !== "undefined") {
this.mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
this.observer = new MutationObserver((mutations) => {
for (const m of mutations) m.addedNodes.forEach((node) => {
if (node instanceof Element) {
if (node.classList.contains("xeg-theme-scope")) syncThemeAttributes(this.currentTheme, { scopes: [node] });
node.querySelectorAll(".xeg-theme-scope").forEach((scope) => {
syncThemeAttributes(this.currentTheme, { scopes: [scope] });
});
}
});
});
if (document.documentElement) this.observer.observe(document.documentElement, {
childList: true,
subtree: true
});
else void 0;
}
this.themeSetting = this.loadThemeSync();
this.applyCurrentTheme(true);
this.scheduleEarlyAsyncRestore();
}
scheduleEarlyAsyncRestore() {
if (this.earlyRestorePromise) return;
this.earlyRestorePromise = (async () => {
try {
const saved = await this.loadThemeAsync();
if (saved && saved !== this.themeSetting) {
this.themeSetting = saved;
this.applyCurrentTheme(true);
}
} catch (error$1) {
this.earlyRestoreFailed = true;
;
} finally {
try {
this.initializeSystemDetection();
} catch (error$1) {
;
}
}
})();
}
async initialize() {
return this.lifecycle.initialize();
}
destroy() {
this.cleanupOnce();
this.lifecycle.destroy();
}
isInitialized() {
return this.lifecycle.isInitialized();
}
async onInitialize() {
await (this.earlyRestorePromise ?? Promise.resolve());
if (this.earlyRestoreFailed) {
const saved = await this.loadThemeAsync();
if (saved && saved !== this.themeSetting) {
this.themeSetting = saved;
this.applyCurrentTheme(true);
}
}
try {
const { tryGetSettingsManager: tryGetSettingsManager$1 } = await Promise.resolve().then(() => (init_service_accessors(), service_accessors_exports));
const settingsService = tryGetSettingsManager$1();
if (settingsService) this.bindSettingsService(settingsService);
} catch (err) {
;
}
}
bindSettingsService(settingsService) {
if (!settingsService || this.boundSettingsService === settingsService) return;
if (this.settingsUnsubscribe) this.settingsUnsubscribe();
this.boundSettingsService = settingsService;
const settingsTheme = settingsService.get?.("gallery.theme");
if (settingsTheme && [
"light",
"dark",
"auto"
].includes(settingsTheme)) {
if (settingsTheme !== this.themeSetting) {
this.themeSetting = settingsTheme;
this.applyCurrentTheme(true);
}
}
if (typeof settingsService.subscribe === "function") this.settingsUnsubscribe = settingsService.subscribe((event) => {
if (event?.key === "gallery.theme") {
const newVal = event.newValue;
if ([
"light",
"dark",
"auto"
].includes(newVal) && newVal !== this.themeSetting) {
this.themeSetting = newVal;
this.applyCurrentTheme();
}
}
});
}
setTheme(setting$1, options) {
this.themeSetting = [
"light",
"dark",
"auto"
].includes(setting$1) ? setting$1 : "light";
if (options?.persist !== false && this.boundSettingsService?.set) {
const result = this.boundSettingsService.set("gallery.theme", this.themeSetting);
if (result instanceof Promise) result.catch((error$1) => {
;
});
}
if (!this.applyCurrentTheme(options?.force)) this.notifyListeners();
}
getEffectiveTheme() {
if (this.themeSetting === "auto") return this.mediaQueryList?.matches ? "dark" : "light";
return this.themeSetting;
}
getCurrentTheme() {
return this.themeSetting;
}
isDarkMode() {
return this.getEffectiveTheme() === "dark";
}
onThemeChange(listener) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
onDestroy() {
this.cleanupOnce();
}
cleanupOnce() {
if (this.didCleanup) return;
this.didCleanup = true;
if (this.settingsUnsubscribe) {
this.settingsUnsubscribe();
this.settingsUnsubscribe = null;
}
this.listeners.clear();
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
if (this.domEventsController) {
this.domEventsController.abort();
this.domEventsController = null;
}
this.mediaQueryListener = null;
this.mediaQueryList = null;
}
initializeSystemDetection() {
if (this.mediaQueryList && !this.mediaQueryListener) {
if (!this.domEventsController || this.domEventsController.signal.aborted) this.domEventsController = new AbortController();
this.mediaQueryListener = () => {
if (this.themeSetting === "auto") this.applyCurrentTheme();
};
const bus = getEventBus();
const listener = this.mediaQueryListener;
if (!listener) return;
bus.addDOMListener(this.mediaQueryList, "change", listener, {
signal: this.domEventsController.signal,
context: "theme-service"
});
}
}
applyCurrentTheme(force = false) {
const effective = this.getEffectiveTheme();
if (force || this.currentTheme !== effective) {
this.currentTheme = effective;
syncThemeAttributes(this.currentTheme);
this.notifyListeners();
return true;
}
return false;
}
notifyListeners() {
this.listeners.forEach((l) => l(this.currentTheme, this.themeSetting));
}
loadThemeSync() {
try {
return this.storage.getSync("xeg-app-settings")?.gallery?.theme ?? "auto";
} catch {
return "auto";
}
}
async loadThemeAsync() {
try {
return (await this.storage.get("xeg-app-settings"))?.gallery?.theme ?? null;
} catch {
return null;
}
}
};
}));
function getThemeServiceInstance() {
return ThemeService.getInstance();
}
function getLanguageServiceInstance() {
return LanguageService.getInstance();
}
function getMediaServiceInstance() {
return MediaService.getInstance();
}
var init_singletons = __esmMin((() => {
init_language_service();
init_media_service();
init_theme_service();
}));
var lazy_services_exports = /* @__PURE__ */ __export({
__resetLazyServiceRegistration: () => __resetLazyServiceRegistration,
ensureDownloadServiceRegistered: () => ensureDownloadServiceRegistered
}, 1);
async function ensureDownloadServiceRegistered() {
if (downloadServiceRegistered) return;
try {
const { DownloadOrchestrator: DownloadOrchestrator$1 } = await Promise.resolve().then(() => (init_download_orchestrator(), download_orchestrator_exports));
const downloadService = DownloadOrchestrator$1.getInstance();
const { CoreService: CoreService$1 } = await Promise.resolve().then(() => (init_service_manager(), service_manager_exports));
const { SERVICE_KEYS: SERVICE_KEYS$1 } = await Promise.resolve().then(() => (init_constants$1(), constants_exports));
CoreService$1.getInstance().register(SERVICE_KEYS$1.GALLERY_DOWNLOAD, downloadService);
downloadServiceRegistered = true;
;
} catch (error$1) {
const message = normalizeErrorMessage(error$1);
logger.error("❌ Failed to lazily register DownloadService:", message);
throw error$1;
}
}
function __resetLazyServiceRegistration() {
downloadServiceRegistered = false;
}
var downloadServiceRegistered;
var init_lazy_services = __esmMin((() => {
init_logging();
downloadServiceRegistered = false;
}));
var service_accessors_exports = /* @__PURE__ */ __export({
CORE_BASE_SERVICE_IDENTIFIERS: () => CORE_BASE_SERVICE_IDENTIFIERS,
LANGUAGE_SERVICE_IDENTIFIER: () => LANGUAGE_SERVICE_IDENTIFIER,
MEDIA_SERVICE_IDENTIFIER: () => MEDIA_SERVICE_IDENTIFIER,
THEME_SERVICE_IDENTIFIER: () => THEME_SERVICE_IDENTIFIER,
getDownloadOrchestrator: () => getDownloadOrchestrator,
getGalleryRenderer: () => getGalleryRenderer,
getLanguageService: () => getLanguageService,
getMediaService: () => getMediaService,
getThemeService: () => getThemeService,
registerGalleryRenderer: () => registerGalleryRenderer,
registerSettingsManager: () => registerSettingsManager,
tryGetSettingsManager: () => tryGetSettingsManager,
warmupCriticalServices: () => warmupCriticalServices,
warmupNonCriticalServices: () => warmupNonCriticalServices
}, 1);
function tryGetFromCoreService(key) {
try {
const coreService = CoreService.getInstance();
if (coreService.has(key)) return coreService.get(key);
} catch (error$1) {}
return null;
}
function getThemeService() {
return tryGetFromCoreService(SERVICE_KEYS.THEME) ?? getThemeServiceInstance();
}
function getLanguageService() {
return tryGetFromCoreService(SERVICE_KEYS.LANGUAGE) ?? getLanguageServiceInstance();
}
function getMediaService() {
return tryGetFromCoreService(SERVICE_KEYS.MEDIA_SERVICE) ?? getMediaServiceInstance();
}
function getGalleryRenderer() {
return CoreService.getInstance().get(SERVICE_KEYS.GALLERY_RENDERER);
}
async function getDownloadOrchestrator() {
const coreService = CoreService.getInstance();
if (coreService.has(SERVICE_KEYS.GALLERY_DOWNLOAD)) return coreService.get(SERVICE_KEYS.GALLERY_DOWNLOAD);
const { ensureDownloadServiceRegistered: ensureDownloadServiceRegistered$1 } = await Promise.resolve().then(() => (init_lazy_services(), lazy_services_exports));
await ensureDownloadServiceRegistered$1();
const { DownloadOrchestrator: DownloadOrchestrator$1 } = await Promise.resolve().then(() => (init_download_orchestrator(), download_orchestrator_exports));
return DownloadOrchestrator$1.getInstance();
}
function registerGalleryRenderer(renderer) {
CoreService.getInstance().register(SERVICE_KEYS.GALLERY_RENDERER, renderer);
}
function registerSettingsManager(settings) {
CoreService.getInstance().register(SERVICE_KEYS.SETTINGS, settings);
}
function tryGetSettingsManager() {
return CoreService.getInstance().tryGet(SERVICE_KEYS.SETTINGS);
}
function warmupCriticalServices() {
try {
getMediaService();
} catch (error$1) {}
}
function warmupNonCriticalServices() {
try {
getThemeService();
} catch (error$1) {}
}
var THEME_SERVICE_IDENTIFIER, LANGUAGE_SERVICE_IDENTIFIER, MEDIA_SERVICE_IDENTIFIER, CORE_BASE_SERVICE_IDENTIFIERS;
var init_service_accessors = __esmMin((() => {
init_constants$1();
init_service_manager();
init_singletons();
THEME_SERVICE_IDENTIFIER = SERVICE_KEYS.THEME;
LANGUAGE_SERVICE_IDENTIFIER = SERVICE_KEYS.LANGUAGE;
MEDIA_SERVICE_IDENTIFIER = SERVICE_KEYS.MEDIA_SERVICE;
CORE_BASE_SERVICE_IDENTIFIERS = [
THEME_SERVICE_IDENTIFIER,
LANGUAGE_SERVICE_IDENTIFIER,
MEDIA_SERVICE_IDENTIFIER
];
}));
init_service_accessors();
init_constants$1();
init_service_manager();
init_singletons();
async function registerCoreServices() {
const core = CoreService.getInstance();
core.register(SERVICE_KEYS.THEME, getThemeServiceInstance());
core.register(SERVICE_KEYS.LANGUAGE, getLanguageServiceInstance());
core.register(SERVICE_KEYS.MEDIA_SERVICE, getMediaServiceInstance());
}
var devLogger = null;
async function initializeCriticalSystems() {
devLogger?.debug("[critical] initialization started");
await registerCoreServices();
warmupCriticalServices();
devLogger?.debug("[critical] initialization complete");
}
async function initializeEnvironment() {}
init_events();
function wireGlobalEvents(onBeforeUnload) {
if (!(typeof window !== "undefined" && Boolean(window.addEventListener))) return () => {};
let disposed = false;
const bus = getEventBus();
const controller = new AbortController();
const invokeOnce = () => {
if (disposed) return;
disposed = true;
controller.abort();
onBeforeUnload();
};
const handler = () => {
invokeOnce();
};
bus.addDOMListener(window, "pagehide", handler, {
once: true,
passive: true,
signal: controller.signal,
context: "bootstrap:pagehide"
});
return () => {
if (disposed) return;
disposed = true;
controller.abort();
};
}
function formatContextTag(context, code) {
const base = `[${context.charAt(0).toUpperCase()}${context.slice(1)}]`;
return code ? `${base}[${code}]` : base;
}
var DEFAULT_SEVERITY, AppErrorReporter, bootstrapErrorReporter, galleryErrorReporter, mediaErrorReporter, settingsErrorReporter;
var init_app_error_reporter = __esmMin((() => {
init_logging();
DEFAULT_SEVERITY = "error";
AppErrorReporter = class AppErrorReporter {
static notificationCallback = null;
static setNotificationCallback(callback) {
AppErrorReporter.notificationCallback = callback;
}
static report(error$1, options) {
const severity = options.severity ?? DEFAULT_SEVERITY;
const message = normalizeErrorMessage(error$1);
const tag = formatContextTag(options.context, options.code);
if (severity === "info") ;
else if (severity === "warning") ;
else {
const logPayload = {
context: options.context,
severity
};
if (options.metadata) logPayload.metadata = options.metadata;
logger.error(`${tag} ${message}`, logPayload);
}
if (options.notify && AppErrorReporter.notificationCallback) AppErrorReporter.notificationCallback(message, options.context);
const result = {
reported: true,
message,
context: options.context,
severity
};
if (severity === "critical") throw error$1 instanceof Error ? error$1 : new Error(message);
return result;
}
static reportAndReturn(error$1, options, defaultValue) {
const effectiveOptions = {
...options,
severity: options.severity === "critical" ? "error" : options.severity
};
AppErrorReporter.report(error$1, effectiveOptions);
return defaultValue;
}
static forContext(context) {
return {
critical: (error$1, options) => AppErrorReporter.report(error$1, {
...options,
context,
severity: "critical"
}),
error: (error$1, options) => AppErrorReporter.report(error$1, {
...options,
context,
severity: "error"
}),
warn: (error$1, options) => AppErrorReporter.report(error$1, {
...options,
context,
severity: "warning"
}),
info: (error$1, options) => AppErrorReporter.report(error$1, {
...options,
context,
severity: "info"
})
};
}
};
bootstrapErrorReporter = AppErrorReporter.forContext("bootstrap");
galleryErrorReporter = AppErrorReporter.forContext("gallery");
mediaErrorReporter = AppErrorReporter.forContext("media");
AppErrorReporter.forContext("download");
settingsErrorReporter = AppErrorReporter.forContext("settings");
AppErrorReporter.forContext("event");
AppErrorReporter.forContext("network");
AppErrorReporter.forContext("storage");
AppErrorReporter.forContext("ui");
}));
function getContextId(count) {
const num = String(count), len = num.length - 1;
return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : "") + num;
}
function setHydrateContext(context) {
sharedConfig.context = context;
}
function nextHydrateContext() {
return {
...sharedConfig.context,
id: sharedConfig.getNextContextId(),
count: 0
};
}
function createRoot(fn, detachedOwner) {
const listener = Listener, owner = Owner, unowned = fn.length === 0, current = detachedOwner === void 0 ? owner : detachedOwner, root = unowned ? UNOWNED : {
owned: null,
cleanups: null,
context: current ? current.context : null,
owner: current
}, updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root)));
Owner = root;
Listener = null;
try {
return runUpdates(updateFn, true);
} finally {
Listener = listener;
Owner = owner;
}
}
function createSignal(value, options) {
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
const s = {
value,
observers: null,
observerSlots: null,
comparator: options.equals || void 0
};
const setter = (value$1) => {
if (typeof value$1 === "function") if (Transition && Transition.running && Transition.sources.has(s)) value$1 = value$1(s.tValue);
else value$1 = value$1(s.value);
return writeSignal(s, value$1);
};
return [readSignal.bind(s), setter];
}
function createComputed(fn, value, options) {
const c = createComputation(fn, value, true, STALE);
if (Scheduler && Transition && Transition.running) Updates.push(c);
else updateComputation(c);
}
function createRenderEffect(fn, value, options) {
const c = createComputation(fn, value, false, STALE);
if (Scheduler && Transition && Transition.running) Updates.push(c);
else updateComputation(c);
}
function createEffect(fn, value, options) {
runEffects = runUserEffects;
const c = createComputation(fn, value, false, STALE), s = SuspenseContext && useContext(SuspenseContext);
if (s) c.suspense = s;
if (!options || !options.render) c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}
function createMemo(fn, value, options) {
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
const c = createComputation(fn, value, true, 0);
c.observers = null;
c.observerSlots = null;
c.comparator = options.equals || void 0;
if (Scheduler && Transition && Transition.running) {
c.tState = STALE;
Updates.push(c);
} else updateComputation(c);
return readSignal.bind(c);
}
function isPromise(v) {
return v && typeof v === "object" && "then" in v;
}
function createResource(pSource, pFetcher, pOptions) {
let source;
let fetcher;
let options;
if (typeof pFetcher === "function") {
source = pSource;
fetcher = pFetcher;
options = pOptions || {};
} else {
source = true;
fetcher = pSource;
options = pFetcher || {};
}
let pr = null, initP = NO_INIT, id = null, loadedUnderTransition = false, scheduled = false, resolved = "initialValue" in options, dynamic = typeof source === "function" && createMemo(source);
const contexts = /* @__PURE__ */ new Set(), [value, setValue] = (options.storage || createSignal)(options.initialValue), [error$1, setError$1] = createSignal(void 0), [track, trigger] = createSignal(void 0, { equals: false }), [state, setState] = createSignal(resolved ? "ready" : "unresolved");
if (sharedConfig.context) {
id = sharedConfig.getNextContextId();
if (options.ssrLoadFrom === "initial") initP = options.initialValue;
else if (sharedConfig.load && sharedConfig.has(id)) initP = sharedConfig.load(id);
}
function loadEnd(p, v, error$2, key) {
if (pr === p) {
pr = null;
key !== void 0 && (resolved = true);
if ((p === initP || v === initP) && options.onHydrated) queueMicrotask(() => options.onHydrated(key, { value: v }));
initP = NO_INIT;
if (Transition && p && loadedUnderTransition) {
Transition.promises.delete(p);
loadedUnderTransition = false;
runUpdates(() => {
Transition.running = true;
completeLoad(v, error$2);
}, false);
} else completeLoad(v, error$2);
}
return v;
}
function completeLoad(v, err) {
runUpdates(() => {
if (err === void 0) setValue(() => v);
setState(err !== void 0 ? "errored" : resolved ? "ready" : "unresolved");
setError$1(err);
for (const c of contexts.keys()) c.decrement();
contexts.clear();
}, false);
}
function read() {
const c = SuspenseContext && useContext(SuspenseContext), v = value(), err = error$1();
if (err !== void 0 && !pr) throw err;
if (Listener && !Listener.user && c) createComputed(() => {
track();
if (pr) {
if (c.resolved && Transition && loadedUnderTransition) Transition.promises.add(pr);
else if (!contexts.has(c)) {
c.increment();
contexts.add(c);
}
}
});
return v;
}
function load(refetching = true) {
if (refetching !== false && scheduled) return;
scheduled = false;
const lookup = dynamic ? dynamic() : source;
loadedUnderTransition = Transition && Transition.running;
if (lookup == null || lookup === false) {
loadEnd(pr, untrack(value));
return;
}
if (Transition && pr) Transition.promises.delete(pr);
let error$2;
const p = initP !== NO_INIT ? initP : untrack(() => {
try {
return fetcher(lookup, {
value: value(),
refetching
});
} catch (fetcherError) {
error$2 = fetcherError;
}
});
if (error$2 !== void 0) {
loadEnd(pr, void 0, castError(error$2), lookup);
return;
} else if (!isPromise(p)) {
loadEnd(pr, p, void 0, lookup);
return p;
}
pr = p;
if ("v" in p) {
if (p.s === 1) loadEnd(pr, p.v, void 0, lookup);
else loadEnd(pr, void 0, castError(p.v), lookup);
return p;
}
scheduled = true;
queueMicrotask(() => scheduled = false);
runUpdates(() => {
setState(resolved ? "refreshing" : "pending");
trigger();
}, false);
return p.then((v) => loadEnd(p, v, void 0, lookup), (e) => loadEnd(p, void 0, castError(e), lookup));
}
Object.defineProperties(read, {
state: { get: () => state() },
error: { get: () => error$1() },
loading: { get() {
const s = state();
return s === "pending" || s === "refreshing";
} },
latest: { get() {
if (!resolved) return read();
const err = error$1();
if (err && !pr) throw err;
return value();
} }
});
let owner = Owner;
if (dynamic) createComputed(() => (owner = Owner, load(false)));
else load(false);
return [read, {
refetch: (info) => runWithOwner(owner, () => load(info)),
mutate: setValue
}];
}
function batch(fn) {
return runUpdates(fn, false);
}
function untrack(fn) {
if (!ExternalSourceConfig && Listener === null) return fn();
const listener = Listener;
Listener = null;
try {
if (ExternalSourceConfig) return ExternalSourceConfig.untrack(fn);
return fn();
} finally {
Listener = listener;
}
}
function on(deps, fn, options) {
const isArray = Array.isArray(deps);
let prevInput;
let defer = options && options.defer;
return (prevValue) => {
let input;
if (isArray) {
input = Array(deps.length);
for (let i = 0; i < deps.length; i++) input[i] = deps[i]();
} else input = deps();
if (defer) {
defer = false;
return prevValue;
}
const result = untrack(() => fn(input, prevInput, prevValue));
prevInput = input;
return result;
};
}
function onMount(fn) {
createEffect(() => untrack(fn));
}
function onCleanup(fn) {
if (Owner === null);
else if (Owner.cleanups === null) Owner.cleanups = [fn];
else Owner.cleanups.push(fn);
return fn;
}
function catchError(fn, handler) {
ERROR || (ERROR = Symbol("error"));
Owner = createComputation(void 0, void 0, true);
Owner.context = {
...Owner.context,
[ERROR]: [handler]
};
if (Transition && Transition.running) Transition.sources.add(Owner);
try {
return fn();
} catch (err) {
handleError(err);
} finally {
Owner = Owner.owner;
}
}
function getOwner() {
return Owner;
}
function runWithOwner(o, fn) {
const prev = Owner;
const prevListener = Listener;
Owner = o;
Listener = null;
try {
return runUpdates(fn, true);
} catch (err) {
handleError(err);
} finally {
Owner = prev;
Listener = prevListener;
}
}
function startTransition(fn) {
if (Transition && Transition.running) {
fn();
return Transition.done;
}
const l = Listener;
const o = Owner;
return Promise.resolve().then(() => {
Listener = l;
Owner = o;
let t;
if (Scheduler || SuspenseContext) {
t = Transition || (Transition = {
sources: /* @__PURE__ */ new Set(),
effects: [],
promises: /* @__PURE__ */ new Set(),
disposed: /* @__PURE__ */ new Set(),
queue: /* @__PURE__ */ new Set(),
running: true
});
t.done || (t.done = new Promise((res) => t.resolve = res));
t.running = true;
}
runUpdates(fn, false);
Listener = Owner = null;
return t ? t.done : void 0;
});
}
function resumeEffects(e) {
Effects.push.apply(Effects, e);
e.length = 0;
}
function createContext(defaultValue, options) {
const id = Symbol("context");
return {
id,
Provider: createProvider(id),
defaultValue
};
}
function useContext(context) {
let value;
return Owner && Owner.context && (value = Owner.context[context.id]) !== void 0 ? value : context.defaultValue;
}
function children(fn) {
const children$1 = createMemo(fn);
const memo$1 = createMemo(() => resolveChildren(children$1()));
memo$1.toArray = () => {
const c = memo$1();
return Array.isArray(c) ? c : c != null ? [c] : [];
};
return memo$1;
}
function getSuspenseContext() {
return SuspenseContext || (SuspenseContext = createContext());
}
function readSignal() {
const runningTransition = Transition && Transition.running;
if (this.sources && (runningTransition ? this.tState : this.state)) if ((runningTransition ? this.tState : this.state) === STALE) updateComputation(this);
else {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(this), false);
Updates = updates;
}
if (Listener) {
const sSlot = this.observers ? this.observers.length : 0;
if (!Listener.sources) {
Listener.sources = [this];
Listener.sourceSlots = [sSlot];
} else {
Listener.sources.push(this);
Listener.sourceSlots.push(sSlot);
}
if (!this.observers) {
this.observers = [Listener];
this.observerSlots = [Listener.sources.length - 1];
} else {
this.observers.push(Listener);
this.observerSlots.push(Listener.sources.length - 1);
}
}
if (runningTransition && Transition.sources.has(this)) return this.tValue;
return this.value;
}
function writeSignal(node, value, isComp) {
let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;
if (!node.comparator || !node.comparator(current, value)) {
if (Transition) {
const TransitionRunning = Transition.running;
if (TransitionRunning || !isComp && Transition.sources.has(node)) {
Transition.sources.add(node);
node.tValue = value;
}
if (!TransitionRunning) node.value = value;
} else node.value = value;
if (node.observers && node.observers.length) runUpdates(() => {
for (let i = 0; i < node.observers.length; i += 1) {
const o = node.observers[i];
const TransitionRunning = Transition && Transition.running;
if (TransitionRunning && Transition.disposed.has(o)) continue;
if (TransitionRunning ? !o.tState : !o.state) {
if (o.pure) Updates.push(o);
else Effects.push(o);
if (o.observers) markDownstream(o);
}
if (!TransitionRunning) o.state = STALE;
else o.tState = STALE;
}
if (Updates.length > 1e6) {
Updates = [];
throw new Error();
}
}, false);
}
return value;
}
function updateComputation(node) {
if (!node.fn) return;
cleanNode(node);
const time = ExecCount;
runComputation(node, Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value, time);
if (Transition && !Transition.running && Transition.sources.has(node)) queueMicrotask(() => {
runUpdates(() => {
Transition && (Transition.running = true);
Listener = Owner = node;
runComputation(node, node.tValue, time);
Listener = Owner = null;
}, false);
});
}
function runComputation(node, value, time) {
let nextValue;
const owner = Owner, listener = Listener;
Listener = Owner = node;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) if (Transition && Transition.running) {
node.tState = STALE;
node.tOwned && node.tOwned.forEach(cleanNode);
node.tOwned = void 0;
} else {
node.state = STALE;
node.owned && node.owned.forEach(cleanNode);
node.owned = null;
}
node.updatedAt = time + 1;
return handleError(err);
} finally {
Listener = listener;
Owner = owner;
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in node) writeSignal(node, nextValue, true);
else if (Transition && Transition.running && node.pure) {
Transition.sources.add(node);
node.tValue = nextValue;
} else node.value = nextValue;
node.updatedAt = time;
}
}
function createComputation(fn, init, pure, state = STALE, options) {
const c = {
fn,
state,
updatedAt: null,
owned: null,
sources: null,
sourceSlots: null,
cleanups: null,
value: init,
owner: Owner,
context: Owner ? Owner.context : null,
pure
};
if (Transition && Transition.running) {
c.state = 0;
c.tState = state;
}
if (Owner === null);
else if (Owner !== UNOWNED) if (Transition && Transition.running && Owner.pure) if (!Owner.tOwned) Owner.tOwned = [c];
else Owner.tOwned.push(c);
else if (!Owner.owned) Owner.owned = [c];
else Owner.owned.push(c);
if (ExternalSourceConfig && c.fn) {
const [track, trigger] = createSignal(void 0, { equals: false });
const ordinary = ExternalSourceConfig.factory(c.fn, trigger);
onCleanup(() => ordinary.dispose());
const triggerInTransition = () => startTransition(trigger).then(() => inTransition.dispose());
const inTransition = ExternalSourceConfig.factory(c.fn, triggerInTransition);
c.fn = (x) => {
track();
return Transition && Transition.running ? inTransition.track(x) : ordinary.track(x);
};
}
return c;
}
function runTop(node) {
const runningTransition = Transition && Transition.running;
if ((runningTransition ? node.tState : node.state) === 0) return;
if ((runningTransition ? node.tState : node.state) === PENDING) return lookUpstream(node);
if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node);
const ancestors = [node];
while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {
if (runningTransition && Transition.disposed.has(node)) return;
if (runningTransition ? node.tState : node.state) ancestors.push(node);
}
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if (runningTransition) {
let top = node, prev = ancestors[i + 1];
while ((top = top.owner) && top !== prev) if (Transition.disposed.has(top)) return;
}
if ((runningTransition ? node.tState : node.state) === STALE) updateComputation(node);
else if ((runningTransition ? node.tState : node.state) === PENDING) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
}
function runUpdates(fn, init) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;
else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!wait) Effects = null;
Updates = null;
handleError(err);
}
}
function completeUpdates(wait) {
if (Updates) {
if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
else runQueue(Updates);
Updates = null;
}
if (wait) return;
let res;
if (Transition) {
if (!Transition.promises.size && !Transition.queue.size) {
const sources = Transition.sources;
const disposed = Transition.disposed;
Effects.push.apply(Effects, Transition.effects);
res = Transition.resolve;
for (const e$1 of Effects) {
"tState" in e$1 && (e$1.state = e$1.tState);
delete e$1.tState;
}
Transition = null;
runUpdates(() => {
for (const d of disposed) cleanNode(d);
for (const v of sources) {
v.value = v.tValue;
if (v.owned) for (let i = 0, len = v.owned.length; i < len; i++) cleanNode(v.owned[i]);
if (v.tOwned) v.owned = v.tOwned;
delete v.tValue;
delete v.tOwned;
v.tState = 0;
}
setTransPending(false);
}, false);
} else if (Transition.running) {
Transition.running = false;
Transition.effects.push.apply(Transition.effects, Effects);
Effects = null;
setTransPending(true);
return;
}
}
const e = Effects;
Effects = null;
if (e.length) runUpdates(() => runEffects(e), false);
if (res) res();
}
function runQueue(queue) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
function scheduleQueue(queue) {
for (let i = 0; i < queue.length; i++) {
const item = queue[i];
const tasks = Transition.queue;
if (!tasks.has(item)) {
tasks.add(item);
Scheduler(() => {
tasks.delete(item);
runUpdates(() => {
Transition.running = true;
runTop(item);
}, false);
Transition && (Transition.running = false);
});
}
}
}
function runUserEffects(queue) {
let i, userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);
else queue[userLength++] = e;
}
if (sharedConfig.context) {
if (sharedConfig.count) {
sharedConfig.effects || (sharedConfig.effects = []);
sharedConfig.effects.push(...queue.slice(0, userLength));
return;
}
setHydrateContext();
}
if (sharedConfig.effects && (sharedConfig.done || !sharedConfig.count)) {
queue = [...sharedConfig.effects, ...queue];
userLength += sharedConfig.effects.length;
delete sharedConfig.effects;
}
for (i = 0; i < userLength; i++) runTop(queue[i]);
}
function lookUpstream(node, ignore) {
const runningTransition = Transition && Transition.running;
if (runningTransition) node.tState = 0;
else node.state = 0;
for (let i = 0; i < node.sources.length; i += 1) {
const source = node.sources[i];
if (source.sources) {
const state = runningTransition ? source.tState : source.state;
if (state === STALE) {
if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount)) runTop(source);
} else if (state === PENDING) lookUpstream(source, ignore);
}
}
}
function markDownstream(node) {
const runningTransition = Transition && Transition.running;
for (let i = 0; i < node.observers.length; i += 1) {
const o = node.observers[i];
if (runningTransition ? !o.tState : !o.state) {
if (runningTransition) o.tState = PENDING;
else o.state = PENDING;
if (o.pure) Updates.push(o);
else Effects.push(o);
o.observers && markDownstream(o);
}
}
}
function cleanNode(node) {
let i;
if (node.sources) while (node.sources.length) {
const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers;
if (obs && obs.length) {
const n = obs.pop(), s = source.observerSlots.pop();
if (index < obs.length) {
n.sourceSlots[s] = index;
obs[index] = n;
source.observerSlots[index] = s;
}
}
}
if (node.tOwned) {
for (i = node.tOwned.length - 1; i >= 0; i--) cleanNode(node.tOwned[i]);
delete node.tOwned;
}
if (Transition && Transition.running && node.pure) reset(node, true);
else if (node.owned) {
for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]);
node.owned = null;
}
if (node.cleanups) {
for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i]();
node.cleanups = null;
}
if (Transition && Transition.running) node.tState = 0;
else node.state = 0;
}
function reset(node, top) {
if (!top) {
node.tState = 0;
Transition.disposed.add(node);
}
if (node.owned) for (let i = 0; i < node.owned.length; i++) reset(node.owned[i]);
}
function castError(err) {
if (err instanceof Error) return err;
return new Error(typeof err === "string" ? err : "Unknown error", { cause: err });
}
function runErrors(err, fns, owner) {
try {
for (const f of fns) f(err);
} catch (e) {
handleError(e, owner && owner.owner || null);
}
}
function handleError(err, owner = Owner) {
const fns = ERROR && owner && owner.context && owner.context[ERROR];
const error$1 = castError(err);
if (!fns) throw error$1;
if (Effects) Effects.push({
fn() {
runErrors(error$1, fns, owner);
},
state: STALE
});
else runErrors(error$1, fns, owner);
}
function resolveChildren(children$1) {
if (typeof children$1 === "function" && !children$1.length) return resolveChildren(children$1());
if (Array.isArray(children$1)) {
const results = [];
for (let i = 0; i < children$1.length; i++) {
const result = resolveChildren(children$1[i]);
Array.isArray(result) ? results.push.apply(results, result) : results.push(result);
}
return results;
}
return children$1;
}
function createProvider(id, options) {
return function provider(props) {
let res;
createRenderEffect(() => res = untrack(() => {
Owner.context = {
...Owner.context,
[id]: props.value
};
return children(() => props.children);
}), void 0);
return res;
};
}
function dispose(d) {
for (let i = 0; i < d.length; i++) d[i]();
}
function mapArray(list, mapFn, options = {}) {
let items = [], mapped = [], disposers = [], len = 0, indexes = mapFn.length > 1 ? [] : null;
onCleanup(() => dispose(disposers));
return () => {
let newItems = list() || [], newLen = newItems.length, i, j;
newItems[$TRACK];
return untrack(() => {
let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item;
if (newLen === 0) {
if (len !== 0) {
dispose(disposers);
disposers = [];
items = [];
mapped = [];
len = 0;
indexes && (indexes = []);
}
if (options.fallback) {
items = [FALLBACK];
mapped[0] = createRoot((disposer) => {
disposers[0] = disposer;
return options.fallback();
});
len = 1;
}
} else if (len === 0) {
mapped = new Array(newLen);
for (j = 0; j < newLen; j++) {
items[j] = newItems[j];
mapped[j] = createRoot(mapper);
}
len = newLen;
} else {
temp = new Array(newLen);
tempdisposers = new Array(newLen);
indexes && (tempIndexes = new Array(newLen));
for (start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++);
for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) {
temp[newEnd] = mapped[end];
tempdisposers[newEnd] = disposers[end];
indexes && (tempIndexes[newEnd] = indexes[end]);
}
newIndices = /* @__PURE__ */ new Map();
newIndicesNext = new Array(newEnd + 1);
for (j = newEnd; j >= start; j--) {
item = newItems[j];
i = newIndices.get(item);
newIndicesNext[j] = i === void 0 ? -1 : i;
newIndices.set(item, j);
}
for (i = start; i <= end; i++) {
item = items[i];
j = newIndices.get(item);
if (j !== void 0 && j !== -1) {
temp[j] = mapped[i];
tempdisposers[j] = disposers[i];
indexes && (tempIndexes[j] = indexes[i]);
j = newIndicesNext[j];
newIndices.set(item, j);
} else disposers[i]();
}
for (j = start; j < newLen; j++) if (j in temp) {
mapped[j] = temp[j];
disposers[j] = tempdisposers[j];
if (indexes) {
indexes[j] = tempIndexes[j];
indexes[j](j);
}
} else mapped[j] = createRoot(mapper);
mapped = mapped.slice(0, len = newLen);
items = newItems.slice(0);
}
return mapped;
});
function mapper(disposer) {
disposers[j] = disposer;
if (indexes) {
const [s, set] = createSignal(j);
indexes[j] = set;
return mapFn(newItems[j], s);
}
return mapFn(newItems[j]);
}
};
}
function createComponent(Comp, props) {
if (hydrationEnabled) {
if (sharedConfig.context) {
const c = sharedConfig.context;
setHydrateContext(nextHydrateContext());
const r = untrack(() => Comp(props || {}));
setHydrateContext(c);
return r;
}
}
return untrack(() => Comp(props || {}));
}
function trueFn() {
return true;
}
function resolveSource(s) {
return !(s = typeof s === "function" ? s() : s) ? {} : s;
}
function resolveSources() {
for (let i = 0, length = this.length; i < length; ++i) {
const v = this[i]();
if (v !== void 0) return v;
}
}
function mergeProps(...sources) {
let proxy = false;
for (let i = 0; i < sources.length; i++) {
const s = sources[i];
proxy = proxy || !!s && $PROXY in s;
sources[i] = typeof s === "function" ? (proxy = true, createMemo(s)) : s;
}
if (SUPPORTS_PROXY && proxy) return new Proxy({
get(property) {
for (let i = sources.length - 1; i >= 0; i--) {
const v = resolveSource(sources[i])[property];
if (v !== void 0) return v;
}
},
has(property) {
for (let i = sources.length - 1; i >= 0; i--) if (property in resolveSource(sources[i])) return true;
return false;
},
keys() {
const keys = [];
for (let i = 0; i < sources.length; i++) keys.push(...Object.keys(resolveSource(sources[i])));
return [...new Set(keys)];
}
}, propTraps);
const sourcesMap = {};
const defined = Object.create(null);
for (let i = sources.length - 1; i >= 0; i--) {
const source = sources[i];
if (!source) continue;
const sourceKeys = Object.getOwnPropertyNames(source);
for (let i$1 = sourceKeys.length - 1; i$1 >= 0; i$1--) {
const key = sourceKeys[i$1];
if (key === "__proto__" || key === "constructor") continue;
const desc = Object.getOwnPropertyDescriptor(source, key);
if (!defined[key]) defined[key] = desc.get ? {
enumerable: true,
configurable: true,
get: resolveSources.bind(sourcesMap[key] = [desc.get.bind(source)])
} : desc.value !== void 0 ? desc : void 0;
else {
const sources$1 = sourcesMap[key];
if (sources$1) {
if (desc.get) sources$1.push(desc.get.bind(source));
else if (desc.value !== void 0) sources$1.push(() => desc.value);
}
}
}
}
const target = {};
const definedKeys = Object.keys(defined);
for (let i = definedKeys.length - 1; i >= 0; i--) {
const key = definedKeys[i], desc = defined[key];
if (desc && desc.get) Object.defineProperty(target, key, desc);
else target[key] = desc ? desc.value : void 0;
}
return target;
}
function splitProps(props, ...keys) {
const len = keys.length;
if (SUPPORTS_PROXY && $PROXY in props) {
const blocked = len > 1 ? keys.flat() : keys[0];
const res = keys.map((k) => {
return new Proxy({
get(property) {
return k.includes(property) ? props[property] : void 0;
},
has(property) {
return k.includes(property) && property in props;
},
keys() {
return k.filter((property) => property in props);
}
}, propTraps);
});
res.push(new Proxy({
get(property) {
return blocked.includes(property) ? void 0 : props[property];
},
has(property) {
return blocked.includes(property) ? false : property in props;
},
keys() {
return Object.keys(props).filter((k) => !blocked.includes(k));
}
}, propTraps));
return res;
}
const objects = [];
for (let i = 0; i <= len; i++) objects[i] = {};
for (const propName of Object.getOwnPropertyNames(props)) {
let keyIndex = len;
for (let i = 0; i < keys.length; i++) if (keys[i].includes(propName)) {
keyIndex = i;
break;
}
const desc = Object.getOwnPropertyDescriptor(props, propName);
!desc.get && !desc.set && desc.enumerable && desc.writable && desc.configurable ? objects[keyIndex][propName] = desc.value : Object.defineProperty(objects[keyIndex], propName, desc);
}
return objects;
}
function lazy(fn) {
let comp;
let p;
const wrap = (props) => {
const ctx = sharedConfig.context;
if (ctx) {
const [s, set] = createSignal();
sharedConfig.count || (sharedConfig.count = 0);
sharedConfig.count++;
(p || (p = fn())).then((mod) => {
!sharedConfig.done && setHydrateContext(ctx);
sharedConfig.count--;
set(() => mod.default);
setHydrateContext();
});
comp = s;
} else if (!comp) {
const [s] = createResource(() => (p || (p = fn())).then((mod) => mod.default));
comp = s;
}
let Comp;
return createMemo(() => (Comp = comp()) ? untrack(() => {
if (!ctx || sharedConfig.done) return Comp(props);
const c = sharedConfig.context;
setHydrateContext(ctx);
const r = Comp(props);
setHydrateContext(c);
return r;
}) : "");
};
wrap.preload = () => p || ((p = fn()).then((mod) => comp = () => mod.default), p);
return wrap;
}
function For(props) {
const fallback = "fallback" in props && { fallback: () => props.fallback };
return createMemo(mapArray(() => props.each, props.children, fallback || void 0));
}
function Show(props) {
const keyed = props.keyed;
const conditionValue = createMemo(() => props.when, void 0, void 0);
const condition = keyed ? conditionValue : createMemo(conditionValue, void 0, { equals: (a, b) => !a === !b });
return createMemo(() => {
const c = condition();
if (c) {
const child = props.children;
return typeof child === "function" && child.length > 0 ? untrack(() => child(keyed ? c : () => {
if (!untrack(condition)) throw narrowedError("Show");
return conditionValue();
})) : child;
}
return props.fallback;
}, void 0, void 0);
}
function Switch(props) {
const chs = children(() => props.children);
const switchFunc = createMemo(() => {
const ch = chs();
const mps = Array.isArray(ch) ? ch : [ch];
let func = () => void 0;
for (let i = 0; i < mps.length; i++) {
const index = i;
const mp = mps[i];
const prevFunc = func;
const conditionValue = createMemo(() => prevFunc() ? void 0 : mp.when, void 0, void 0);
const condition = mp.keyed ? conditionValue : createMemo(conditionValue, void 0, { equals: (a, b) => !a === !b });
func = () => prevFunc() || (condition() ? [
index,
conditionValue,
mp
] : void 0);
}
return func;
});
return createMemo(() => {
const sel = switchFunc()();
if (!sel) return props.fallback;
const [index, conditionValue, mp] = sel;
const child = mp.children;
return typeof child === "function" && child.length > 0 ? untrack(() => child(mp.keyed ? conditionValue() : () => {
if (untrack(switchFunc)()?.[0] !== index) throw narrowedError("Match");
return conditionValue();
})) : child;
}, void 0, void 0);
}
function Match(props) {
return props;
}
function ErrorBoundary$1(props) {
let err;
if (sharedConfig.context && sharedConfig.load) err = sharedConfig.load(sharedConfig.getContextId());
const [errored, setErrored] = createSignal(err, void 0);
Errors || (Errors = /* @__PURE__ */ new Set());
Errors.add(setErrored);
onCleanup(() => Errors.delete(setErrored));
return createMemo(() => {
let e;
if (e = errored()) {
const f = props.fallback;
return typeof f === "function" && f.length ? untrack(() => f(e, () => setErrored())) : f;
}
return catchError(() => props.children, setErrored);
}, void 0, void 0);
}
function Suspense(props) {
let counter = 0, show, ctx, p, flicker, error$1;
const [inFallback, setFallback] = createSignal(false), SuspenseContext$1 = getSuspenseContext(), store = {
increment: () => {
if (++counter === 1) setFallback(true);
},
decrement: () => {
if (--counter === 0) setFallback(false);
},
inFallback,
effects: [],
resolved: false
}, owner = getOwner();
if (sharedConfig.context && sharedConfig.load) {
const key = sharedConfig.getContextId();
let ref = sharedConfig.load(key);
if (ref) if (typeof ref !== "object" || ref.s !== 1) p = ref;
else sharedConfig.gather(key);
if (p && p !== "$$f") {
const [s, set] = createSignal(void 0, { equals: false });
flicker = s;
p.then(() => {
if (sharedConfig.done) return set();
sharedConfig.gather(key);
setHydrateContext(ctx);
set();
setHydrateContext();
}, (err) => {
error$1 = err;
set();
});
}
}
const listContext = useContext(SuspenseListContext);
if (listContext) show = listContext.register(store.inFallback);
let dispose$1;
onCleanup(() => dispose$1 && dispose$1());
return createComponent(SuspenseContext$1.Provider, {
value: store,
get children() {
return createMemo(() => {
if (error$1) throw error$1;
ctx = sharedConfig.context;
if (flicker) {
flicker();
flicker = void 0;
return;
}
if (ctx && p === "$$f") setHydrateContext();
const rendered = createMemo(() => props.children);
return createMemo((prev) => {
const inFallback$1 = store.inFallback(), { showContent = true, showFallback = true } = show ? show() : {};
if ((!inFallback$1 || p && p !== "$$f") && showContent) {
store.resolved = true;
dispose$1 && dispose$1();
dispose$1 = ctx = p = void 0;
resumeEffects(store.effects);
return rendered();
}
if (!showFallback) return;
if (dispose$1) return prev;
return createRoot((disposer) => {
dispose$1 = disposer;
if (ctx) {
setHydrateContext({
id: ctx.id + "F",
count: 0
});
ctx = void 0;
}
return props.fallback;
}, owner);
});
});
}
});
}
var sharedConfig, equalFn, $PROXY, SUPPORTS_PROXY, $TRACK, signalOptions, ERROR, runEffects, STALE, PENDING, UNOWNED, NO_INIT, Owner, Transition, Scheduler, ExternalSourceConfig, Listener, Updates, Effects, ExecCount, transPending, setTransPending, SuspenseContext, FALLBACK, hydrationEnabled, propTraps, narrowedError, Errors, SuspenseListContext;
var init_solid = __esmMin((() => {
sharedConfig = {
context: void 0,
registry: void 0,
effects: void 0,
done: false,
getContextId() {
return getContextId(this.context.count);
},
getNextContextId() {
return getContextId(this.context.count++);
}
};
equalFn = (a, b) => a === b;
$PROXY = Symbol("solid-proxy");
SUPPORTS_PROXY = typeof Proxy === "function";
$TRACK = Symbol("solid-track");
signalOptions = { equals: equalFn };
ERROR = null;
runEffects = runQueue;
STALE = 1;
PENDING = 2;
UNOWNED = {
owned: null,
cleanups: null,
context: null,
owner: null
};
NO_INIT = {};
Owner = null;
Transition = null;
Scheduler = null;
ExternalSourceConfig = null;
Listener = null;
Updates = null;
Effects = null;
ExecCount = 0;
[transPending, setTransPending] = /* @__PURE__ */ createSignal(false);
FALLBACK = Symbol("fallback");
hydrationEnabled = false;
propTraps = {
get(_, property, receiver) {
if (property === $PROXY) return receiver;
return _.get(property);
},
has(_, property) {
if (property === $PROXY) return true;
return _.has(property);
},
set: trueFn,
deleteProperty: trueFn,
getOwnPropertyDescriptor(_, property) {
return {
configurable: true,
enumerable: true,
get() {
return _.get(property);
},
set: trueFn,
deleteProperty: trueFn
};
},
ownKeys(_) {
return _.keys();
}
};
narrowedError = (name) => `Stale read from <${name}>.`;
SuspenseListContext = /* @__PURE__ */ createContext();
}));
function getPropAlias(prop, tagName) {
const a = PropAliases[prop];
return typeof a === "object" ? a[tagName] ? a["$"] : void 0 : a;
}
function reconcileArrays(parentNode, a, b) {
let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = a[aEnd - 1].nextSibling, map = null;
while (aStart < aEnd || bStart < bEnd) {
if (a[aStart] === b[bStart]) {
aStart++;
bStart++;
continue;
}
while (a[aEnd - 1] === b[bEnd - 1]) {
aEnd--;
bEnd--;
}
if (aEnd === aStart) {
const node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after;
while (bStart < bEnd) parentNode.insertBefore(b[bStart++], node);
} else if (bEnd === bStart) while (aStart < aEnd) {
if (!map || !map.has(a[aStart])) a[aStart].remove();
aStart++;
}
else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
const node = a[--aEnd].nextSibling;
parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling);
parentNode.insertBefore(b[--bEnd], node);
a[aEnd] = b[bEnd];
} else {
if (!map) {
map = /* @__PURE__ */ new Map();
let i = bStart;
while (i < bEnd) map.set(b[i], i++);
}
const index = map.get(a[aStart]);
if (index != null) if (bStart < index && index < bEnd) {
let i = aStart, sequence = 1, t;
while (++i < aEnd && i < bEnd) {
if ((t = map.get(a[i])) == null || t !== index + sequence) break;
sequence++;
}
if (sequence > index - bStart) {
const node = a[aStart];
while (bStart < index) parentNode.insertBefore(b[bStart++], node);
} else parentNode.replaceChild(b[bStart++], a[aStart++]);
} else aStart++;
else a[aStart++].remove();
}
}
}
function render(code, element, init, options = {}) {
let disposer;
createRoot((dispose$1) => {
disposer = dispose$1;
element === document ? code() : insert(element, code(), element.firstChild ? null : void 0, init);
}, options.owner);
return () => {
disposer();
element.textContent = "";
};
}
function template(html, isImportNode, isSVG, isMathML) {
let node;
const create = () => {
const t = isMathML ? document.createElementNS("http://www.w3.org/1998/Math/MathML", "template") : document.createElement("template");
t.innerHTML = html;
return isSVG ? t.content.firstChild.firstChild : isMathML ? t.firstChild : t.content.firstChild;
};
const fn = isImportNode ? () => untrack(() => document.importNode(node || (node = create()), true)) : () => (node || (node = create())).cloneNode(true);
fn.cloneNode = fn;
return fn;
}
function delegateEvents(eventNames, document$1 = window.document) {
const e = document$1[$$EVENTS] || (document$1[$$EVENTS] = /* @__PURE__ */ new Set());
for (let i = 0, l = eventNames.length; i < l; i++) {
const name = eventNames[i];
if (!e.has(name)) {
e.add(name);
document$1.addEventListener(name, eventHandler);
}
}
}
function setAttribute(node, name, value) {
if (isHydrating(node)) return;
if (value == null) node.removeAttribute(name);
else node.setAttribute(name, value);
}
function setAttributeNS(node, namespace, name, value) {
if (isHydrating(node)) return;
if (value == null) node.removeAttributeNS(namespace, name);
else node.setAttributeNS(namespace, name, value);
}
function setBoolAttribute(node, name, value) {
if (isHydrating(node)) return;
value ? node.setAttribute(name, "") : node.removeAttribute(name);
}
function className(node, value) {
if (isHydrating(node)) return;
if (value == null) node.removeAttribute("class");
else node.className = value;
}
function addEventListener(node, name, handler, delegate) {
if (delegate) if (Array.isArray(handler)) {
node[`$$${name}`] = handler[0];
node[`$$${name}Data`] = handler[1];
} else node[`$$${name}`] = handler;
else if (Array.isArray(handler)) {
const handlerFn = handler[0];
node.addEventListener(name, handler[0] = (e) => handlerFn.call(node, handler[1], e));
} else node.addEventListener(name, handler, typeof handler !== "function" && handler);
}
function classList(node, value, prev = {}) {
const classKeys = Object.keys(value || {}), prevKeys = Object.keys(prev);
let i, len;
for (i = 0, len = prevKeys.length; i < len; i++) {
const key = prevKeys[i];
if (!key || key === "undefined" || value[key]) continue;
toggleClassKey(node, key, false);
delete prev[key];
}
for (i = 0, len = classKeys.length; i < len; i++) {
const key = classKeys[i], classValue = !!value[key];
if (!key || key === "undefined" || prev[key] === classValue || !classValue) continue;
toggleClassKey(node, key, true);
prev[key] = classValue;
}
return prev;
}
function style(node, value, prev) {
if (!value) return prev ? setAttribute(node, "style") : value;
const nodeStyle = node.style;
if (typeof value === "string") return nodeStyle.cssText = value;
typeof prev === "string" && (nodeStyle.cssText = prev = void 0);
prev || (prev = {});
value || (value = {});
let v, s;
for (s in prev) {
value[s] ?? nodeStyle.removeProperty(s);
delete prev[s];
}
for (s in value) {
v = value[s];
if (v !== prev[s]) {
nodeStyle.setProperty(s, v);
prev[s] = v;
}
}
return prev;
}
function setStyleProperty(node, name, value) {
value != null ? node.style.setProperty(name, value) : node.style.removeProperty(name);
}
function spread(node, props = {}, isSVG, skipChildren) {
const prevProps = {};
if (!skipChildren) createRenderEffect(() => prevProps.children = insertExpression(node, props.children, prevProps.children));
createRenderEffect(() => typeof props.ref === "function" && use(props.ref, node));
createRenderEffect(() => assign(node, props, isSVG, true, prevProps, true));
return prevProps;
}
function use(fn, element, arg) {
return untrack(() => fn(element, arg));
}
function insert(parent, accessor, marker, initial) {
if (marker !== void 0 && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial);
}
function assign(node, props, isSVG, skipChildren, prevProps = {}, skipRef = false) {
props || (props = {});
for (const prop in prevProps) if (!(prop in props)) {
if (prop === "children") continue;
prevProps[prop] = assignProp(node, prop, null, prevProps[prop], isSVG, skipRef, props);
}
for (const prop in props) {
if (prop === "children") {
if (!skipChildren) insertExpression(node, props.children);
continue;
}
const value = props[prop];
prevProps[prop] = assignProp(node, prop, value, prevProps[prop], isSVG, skipRef, props);
}
}
function isHydrating(node) {
return !!sharedConfig.context && !sharedConfig.done && (!node || node.isConnected);
}
function toPropertyName(name) {
return name.toLowerCase().replace(/-([a-z])/g, (_, w) => w.toUpperCase());
}
function toggleClassKey(node, key, value) {
const classNames = key.trim().split(/\s+/);
for (let i = 0, nameLen = classNames.length; i < nameLen; i++) node.classList.toggle(classNames[i], value);
}
function assignProp(node, prop, value, prev, isSVG, skipRef, props) {
let isCE, isProp, isChildProp, propAlias, forceProp;
if (prop === "style") return style(node, value, prev);
if (prop === "classList") return classList(node, value, prev);
if (value === prev) return prev;
if (prop === "ref") {
if (!skipRef) value(node);
} else if (prop.slice(0, 3) === "on:") {
const e = prop.slice(3);
prev && node.removeEventListener(e, prev, typeof prev !== "function" && prev);
value && node.addEventListener(e, value, typeof value !== "function" && value);
} else if (prop.slice(0, 10) === "oncapture:") {
const e = prop.slice(10);
prev && node.removeEventListener(e, prev, true);
value && node.addEventListener(e, value, true);
} else if (prop.slice(0, 2) === "on") {
const name = prop.slice(2).toLowerCase();
const delegate = DelegatedEvents.has(name);
if (!delegate && prev) node.removeEventListener(name, Array.isArray(prev) ? prev[0] : prev);
if (delegate || value) {
addEventListener(node, name, value, delegate);
delegate && delegateEvents([name]);
}
} else if (prop.slice(0, 5) === "attr:") setAttribute(node, prop.slice(5), value);
else if (prop.slice(0, 5) === "bool:") setBoolAttribute(node, prop.slice(5), value);
else if ((forceProp = prop.slice(0, 5) === "prop:") || (isChildProp = ChildProperties.has(prop)) || !isSVG && ((propAlias = getPropAlias(prop, node.tagName)) || (isProp = Properties.has(prop))) || (isCE = node.nodeName.includes("-") || "is" in props)) {
if (forceProp) {
prop = prop.slice(5);
isProp = true;
} else if (isHydrating(node)) return value;
if (prop === "class" || prop === "className") className(node, value);
else if (isCE && !isProp && !isChildProp) node[toPropertyName(prop)] = value;
else node[propAlias || prop] = value;
} else {
const ns = isSVG && prop.indexOf(":") > -1 && SVGNamespace[prop.split(":")[0]];
if (ns) setAttributeNS(node, ns, prop, value);
else setAttribute(node, Aliases[prop] || prop, value);
}
return value;
}
function eventHandler(e) {
if (sharedConfig.registry && sharedConfig.events) {
if (sharedConfig.events.find(([el, ev]) => ev === e)) return;
}
let node = e.target;
const key = `$$${e.type}`;
const oriTarget = e.target;
const oriCurrentTarget = e.currentTarget;
const retarget = (value) => Object.defineProperty(e, "target", {
configurable: true,
value
});
const handleNode = () => {
const handler = node[key];
if (handler && !node.disabled) {
const data = node[`${key}Data`];
data !== void 0 ? handler.call(node, data, e) : handler.call(node, e);
if (e.cancelBubble) return;
}
node.host && typeof node.host !== "string" && !node.host._$host && node.contains(e.target) && retarget(node.host);
return true;
};
const walkUpTree = () => {
while (handleNode() && (node = node._$host || node.parentNode || node.host));
};
Object.defineProperty(e, "currentTarget", {
configurable: true,
get() {
return node || document;
}
});
if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = _$HY.done = true;
if (e.composedPath) {
const path = e.composedPath();
retarget(path[0]);
for (let i = 0; i < path.length - 2; i++) {
node = path[i];
if (!handleNode()) break;
if (node._$host) {
node = node._$host;
walkUpTree();
break;
}
if (node.parentNode === oriCurrentTarget) break;
}
} else walkUpTree();
retarget(oriTarget);
}
function insertExpression(parent, value, current, marker, unwrapArray) {
const hydrating = isHydrating(parent);
if (hydrating) {
!current && (current = [...parent.childNodes]);
let cleaned = [];
for (let i = 0; i < current.length; i++) {
const node = current[i];
if (node.nodeType === 8 && node.data.slice(0, 2) === "!$") node.remove();
else cleaned.push(node);
}
current = cleaned;
}
while (typeof current === "function") current = current();
if (value === current) return current;
const t = typeof value, multi = marker !== void 0;
parent = multi && current[0] && current[0].parentNode || parent;
if (t === "string" || t === "number") {
if (hydrating) return current;
if (t === "number") {
value = value.toString();
if (value === current) return current;
}
if (multi) {
let node = current[0];
if (node && node.nodeType === 3) node.data !== value && (node.data = value);
else node = document.createTextNode(value);
current = cleanChildren(parent, current, marker, node);
} else if (current !== "" && typeof current === "string") current = parent.firstChild.data = value;
else current = parent.textContent = value;
} else if (value == null || t === "boolean") {
if (hydrating) return current;
current = cleanChildren(parent, current, marker);
} else if (t === "function") {
createRenderEffect(() => {
let v = value();
while (typeof v === "function") v = v();
current = insertExpression(parent, v, current, marker);
});
return () => current;
} else if (Array.isArray(value)) {
const array = [];
const currentArray = current && Array.isArray(current);
if (normalizeIncomingArray(array, value, current, unwrapArray)) {
createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));
return () => current;
}
if (hydrating) {
if (!array.length) return current;
if (marker === void 0) return current = [...parent.childNodes];
let node = array[0];
if (node.parentNode !== parent) return current;
const nodes = [node];
while ((node = node.nextSibling) !== marker) nodes.push(node);
return current = nodes;
}
if (array.length === 0) {
current = cleanChildren(parent, current, marker);
if (multi) return current;
} else if (currentArray) if (current.length === 0) appendNodes(parent, array, marker);
else reconcileArrays(parent, current, array);
else {
current && cleanChildren(parent);
appendNodes(parent, array);
}
current = array;
} else if (value.nodeType) {
if (hydrating && value.parentNode) return current = multi ? [value] : value;
if (Array.isArray(current)) {
if (multi) return current = cleanChildren(parent, current, marker, value);
cleanChildren(parent, current, null, value);
} else if (current == null || current === "" || !parent.firstChild) parent.appendChild(value);
else parent.replaceChild(value, parent.firstChild);
current = value;
}
return current;
}
function normalizeIncomingArray(normalized, array, current, unwrap) {
let dynamic = false;
for (let i = 0, len = array.length; i < len; i++) {
let item = array[i], prev = current && current[normalized.length], t;
if (item == null || item === true || item === false);
else if ((t = typeof item) === "object" && item.nodeType) normalized.push(item);
else if (Array.isArray(item)) dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic;
else if (t === "function") if (unwrap) {
while (typeof item === "function") item = item();
dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic;
} else {
normalized.push(item);
dynamic = true;
}
else {
const value = String(item);
if (prev && prev.nodeType === 3 && prev.data === value) normalized.push(prev);
else normalized.push(document.createTextNode(value));
}
}
return dynamic;
}
function appendNodes(parent, array, marker = null) {
for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker);
}
function cleanChildren(parent, current, marker, replacement) {
if (marker === void 0) return parent.textContent = "";
const node = replacement || document.createTextNode("");
if (current.length) {
let inserted = false;
for (let i = current.length - 1; i >= 0; i--) {
const el = current[i];
if (node !== el) {
const isParent = el.parentNode === parent;
if (!inserted && !i) isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker);
else isParent && el.remove();
} else inserted = true;
}
} else parent.insertBefore(node, marker);
return [node];
}
var Properties, ChildProperties, Aliases, PropAliases, DelegatedEvents, SVGNamespace, memo, $$EVENTS;
var init_web = __esmMin((() => {
init_solid();
Properties = /* @__PURE__ */ new Set([
"className",
"value",
"readOnly",
"noValidate",
"formNoValidate",
"isMap",
"noModule",
"playsInline",
"adAuctionHeaders",
"allowFullscreen",
"browsingTopics",
"defaultChecked",
"defaultMuted",
"defaultSelected",
"disablePictureInPicture",
"disableRemotePlayback",
"preservesPitch",
"shadowRootClonable",
"shadowRootCustomElementRegistry",
"shadowRootDelegatesFocus",
"shadowRootSerializable",
"sharedStorageWritable",
...[
"allowfullscreen",
"async",
"alpha",
"autofocus",
"autoplay",
"checked",
"controls",
"default",
"disabled",
"formnovalidate",
"hidden",
"indeterminate",
"inert",
"ismap",
"loop",
"multiple",
"muted",
"nomodule",
"novalidate",
"open",
"playsinline",
"readonly",
"required",
"reversed",
"seamless",
"selected",
"adauctionheaders",
"browsingtopics",
"credentialless",
"defaultchecked",
"defaultmuted",
"defaultselected",
"defer",
"disablepictureinpicture",
"disableremoteplayback",
"preservespitch",
"shadowrootclonable",
"shadowrootcustomelementregistry",
"shadowrootdelegatesfocus",
"shadowrootserializable",
"sharedstoragewritable"
]
]);
ChildProperties = /* @__PURE__ */ new Set([
"innerHTML",
"textContent",
"innerText",
"children"
]);
Aliases = /* @__PURE__ */ Object.assign(Object.create(null), {
className: "class",
htmlFor: "for"
});
PropAliases = /* @__PURE__ */ Object.assign(Object.create(null), {
class: "className",
novalidate: {
$: "noValidate",
FORM: 1
},
formnovalidate: {
$: "formNoValidate",
BUTTON: 1,
INPUT: 1
},
ismap: {
$: "isMap",
IMG: 1
},
nomodule: {
$: "noModule",
SCRIPT: 1
},
playsinline: {
$: "playsInline",
VIDEO: 1
},
readonly: {
$: "readOnly",
INPUT: 1,
TEXTAREA: 1
},
adauctionheaders: {
$: "adAuctionHeaders",
IFRAME: 1
},
allowfullscreen: {
$: "allowFullscreen",
IFRAME: 1
},
browsingtopics: {
$: "browsingTopics",
IMG: 1
},
defaultchecked: {
$: "defaultChecked",
INPUT: 1
},
defaultmuted: {
$: "defaultMuted",
AUDIO: 1,
VIDEO: 1
},
defaultselected: {
$: "defaultSelected",
OPTION: 1
},
disablepictureinpicture: {
$: "disablePictureInPicture",
VIDEO: 1
},
disableremoteplayback: {
$: "disableRemotePlayback",
AUDIO: 1,
VIDEO: 1
},
preservespitch: {
$: "preservesPitch",
AUDIO: 1,
VIDEO: 1
},
shadowrootclonable: {
$: "shadowRootClonable",
TEMPLATE: 1
},
shadowrootdelegatesfocus: {
$: "shadowRootDelegatesFocus",
TEMPLATE: 1
},
shadowrootserializable: {
$: "shadowRootSerializable",
TEMPLATE: 1
},
sharedstoragewritable: {
$: "sharedStorageWritable",
IFRAME: 1,
IMG: 1
}
});
DelegatedEvents = /* @__PURE__ */ new Set([
"beforeinput",
"click",
"dblclick",
"contextmenu",
"focusin",
"focusout",
"input",
"keydown",
"keyup",
"mousedown",
"mousemove",
"mouseout",
"mouseover",
"mouseup",
"pointerdown",
"pointermove",
"pointerout",
"pointerover",
"pointerup",
"touchend",
"touchmove",
"touchstart"
]);
SVGNamespace = {
xlink: "http://www.w3.org/1999/xlink",
xml: "http://www.w3.org/XML/1998/namespace"
};
memo = (fn) => createMemo(() => fn());
$$EVENTS = "_$DX_DELEGATE";
}));
function Icon({ size = "var(--xeg-icon-size)", className: className$1 = "", children: children$1, "aria-label": ariaLabel, ...otherProps }) {
const accessibilityProps = {};
if (ariaLabel) {
accessibilityProps.role = "img";
accessibilityProps["aria-label"] = ariaLabel;
} else accessibilityProps["aria-hidden"] = "true";
const sizeValue = typeof size === "number" ? `${size}px` : size;
return (() => {
var _el$ = _tmpl$$10();
setAttribute(_el$, "width", sizeValue);
setAttribute(_el$, "height", sizeValue);
setAttribute(_el$, "class", className$1);
spread(_el$, mergeProps(accessibilityProps, otherProps), true, true);
insert(_el$, children$1);
return _el$;
})();
}
var _tmpl$$10;
var init_Icon$1 = __esmMin((() => {
init_web();
_tmpl$$10 = /* @__PURE__ */ template(`<svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 24 24"fill=none stroke="var(--xeg-icon-color, currentColor)"stroke-width=var(--xeg-icon-stroke-width) stroke-linecap=round stroke-linejoin=round>`);
}));
var ICON_PATHS, MULTI_PATH_ICONS;
var init_icon_paths = __esmMin((() => {
ICON_PATHS = {
download: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3",
arrowSmallLeft: "M19.5 12H4.5m0 0l6.75 6.75M4.5 12l6.75-6.75",
arrowSmallRight: "M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75",
arrowsPointingIn: "M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5M15 15l5.25 5.25",
arrowsPointingOut: "M3.75 3.75v4.5m0-4.5h4.5M3.75 3.75L9 9m-5.25 11.25v-4.5m0 4.5h4.5M3.75 20.25L9 15m11.25-11.25h-4.5m4.5 0v4.5M20.25 3.75L15 9m5.25 11.25h-4.5m4.5 0v-4.5M20.25 20.25L15 15",
arrowsRightLeft: "M7.5 21L3 16.5M3 16.5l4.5-4.5M3 16.5h13.5M16.5 3l4.5 4.5M21 7.5l-4.5 4.5M21 7.5H7.5",
arrowsUpDown: "M3 7.5l4.5-4.5M7.5 3l4.5 4.5M7.5 3v13.5M21 16.5l-4.5 4.5M16.5 21l-4.5-4.5M16.5 21V7.5",
arrowDownOnSquareStack: "M7.5 7.5h-.75a2.25 2.25 0 00-2.25 2.25v7.5a2.25 2.25 0 002.25 2.25h7.5a2.25 2.25 0 002.25-2.25v-7.5a2.25 2.25 0 00-2.25-2.25h-.75m-6 3.75l3 3m0 0l3-3m-3 3V1.5m6 9h.75a2.25 2.25 0 012.25 2.25v7.5a2.25 2.25 0 01-2.25 2.25h-7.5a2.25 2.25 0 01-2.25-2.25v-.75",
arrowLeftOnRectangle: "M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m-3.75-6l-3 3m0 0l3 3m-3-3H21.75",
chatBubbleLeftRight: "M20.25 8.511a1.5 1.5 0 011.5 1.497v4.286a1.5 1.5 0 01-1.33 1.488c-.31.025-.62.047-.93.064v3.091L15.75 17.25c-1.353 0-2.693-.055-4.02-.163a1.5 1.5 0 01-.825-.241m9.345-8.335a4.125 4.125 0 00-.477-.095A59.924 59.924 0 0015.75 8.25c-1.355 0-2.697.056-4.023.167A1.5 1.5 0 009.75 10.608v4.286c0 .838.46 1.582 1.155 1.952m9.345-8.335V6.637a3.375 3.375 0 00-2.76-3.235A60.508 60.508 0 0011.25 3C9.135 3 7.052 3.137 5.01 3.402A3.375 3.375 0 002.25 6.637v6.225a3.375 3.375 0 002.76 3.236c.577.075 1.157.14 1.74.194V21l4.155-4.155"
};
MULTI_PATH_ICONS = { cog6Tooth: ["M9.593 3.94a1.125 1.125 0 011.11-.94h2.594a1.125 1.125 0 011.11.94l.214 1.281a1.125 1.125 0 00.644.87l.22.122a1.125 1.125 0 001.076-.053l1.216-.456a1.125 1.125 0 011.369.487l1.297 2.247a1.125 1.125 0 01-.259 1.41l-1.004.827a1.125 1.125 0 00-.429.908l.001.127v.255c0 .042 0 .084-.001.127a1.125 1.125 0 00.429.908l1.004.827a1.125 1.125 0 01.259 1.41l-1.297 2.246a1.125 1.125 0 01-1.369.488l-1.216-.457a1.125 1.125 0 00-1.076-.053l-.22.122a1.125 1.125 0 00-.644.87l-.214 1.281a1.125 1.125 0 01-1.11.94H10.703a1.125 1.125 0 01-1.11-.94l-.214-1.281a1.125 1.125 0 00-.644-.87l-.22-.122a1.125 1.125 0 00-1.076.053l-1.216.457a1.125 1.125 0 01-1.369-.488L3.757 15.38a1.125 1.125 0 01.259-1.41l1.005-.827a1.125 1.125 0 00.429-.908c0-.042-.001-.084-.001-.127v-.255c0-.042 0-.084.001-.127a1.125 1.125 0 00-.429-.908L4.016 9.81a1.125 1.125 0 01-.259-1.41l1.297-2.247a1.125 1.125 0 011.369-.487l1.216.456a1.125 1.125 0 001.076.052l.22-.121a1.125 1.125 0 00.644-.871L9.593 3.94z", "M15 12a3 3 0 11-6 0 3 3 0 016 0z"] };
}));
function createSinglePathIcon(name) {
return function IconComponent(props) {
return createComponent(Icon, mergeProps(props, { get children() {
var _el$ = _tmpl$$9();
createRenderEffect(() => setAttribute(_el$, "d", ICON_PATHS[name]));
return _el$;
} }));
};
}
function createMultiPathIcon(name) {
return function IconComponent(props) {
return createComponent(Icon, mergeProps(props, { get children() {
return createComponent(For, {
get each() {
return MULTI_PATH_ICONS[name];
},
children: (pathData) => (() => {
var _el$2 = _tmpl$$9();
setAttribute(_el$2, "d", pathData);
return _el$2;
})()
});
} }));
};
}
var _tmpl$$9, HeroDownload, HeroArrowSmallLeft, HeroArrowSmallRight, HeroArrowsPointingIn, HeroArrowsPointingOut, HeroArrowsRightLeft, HeroArrowsUpDown, HeroArrowDownOnSquareStack, HeroArrowLeftOnRectangle, HeroChatBubbleLeftRight, HeroCog6Tooth;
var init_hero_icons = __esmMin((() => {
init_web();
init_Icon$1();
init_solid();
init_icon_paths();
_tmpl$$9 = /* @__PURE__ */ template(`<svg><path></svg>`, false, true, false);
HeroDownload = createSinglePathIcon("download");
HeroArrowSmallLeft = createSinglePathIcon("arrowSmallLeft");
HeroArrowSmallRight = createSinglePathIcon("arrowSmallRight");
HeroArrowsPointingIn = createSinglePathIcon("arrowsPointingIn");
HeroArrowsPointingOut = createSinglePathIcon("arrowsPointingOut");
HeroArrowsRightLeft = createSinglePathIcon("arrowsRightLeft");
HeroArrowsUpDown = createSinglePathIcon("arrowsUpDown");
HeroArrowDownOnSquareStack = createSinglePathIcon("arrowDownOnSquareStack");
HeroArrowLeftOnRectangle = createSinglePathIcon("arrowLeftOnRectangle");
HeroChatBubbleLeftRight = createSinglePathIcon("chatBubbleLeftRight");
HeroCog6Tooth = createMultiPathIcon("cog6Tooth");
}));
var init_Icon = __esmMin((() => {
init_hero_icons();
}));
function formatTweetText(text) {
if (!text) return [];
const tokens = [];
const lines = text.split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (!line) {
if (i < lines.length - 1) tokens.push({ type: "break" });
continue;
}
let lastIndex = 0;
let match;
ENTITY_PATTERN.lastIndex = 0;
while ((match = ENTITY_PATTERN.exec(line)) !== null) {
const entity = match[0];
const matchIndex = match.index;
if (matchIndex > lastIndex) {
const textContent = line.slice(lastIndex, matchIndex);
if (textContent) tokens.push({
type: "text",
content: textContent
});
}
tokens.push(...createEntityTokens(entity));
lastIndex = matchIndex + entity.length;
}
if (lastIndex < line.length) {
const textContent = line.slice(lastIndex);
if (textContent) tokens.push({
type: "text",
content: textContent
});
}
if (i < lines.length - 1) tokens.push({ type: "break" });
}
return tokens;
}
function splitUrlTrailingPunctuation(url) {
let trimmed = url;
let trailing = "";
while (trimmed.length > 0) {
const last = trimmed.at(-1);
if (!last) break;
if (TRAILING_URL_PUNCTUATION.has(last)) {
trailing = last + trailing;
trimmed = trimmed.slice(0, -1);
continue;
}
let strippedBracket = false;
for (const [close, open] of TRAILING_URL_BRACKET_PAIRS) if (last === close && !trimmed.includes(open)) {
trailing = close + trailing;
trimmed = trimmed.slice(0, -1);
strippedBracket = true;
break;
}
if (strippedBracket) continue;
break;
}
if (!trimmed) return {
url,
trailing: ""
};
return {
url: trimmed,
trailing
};
}
function createEntityTokens(entity) {
if (entity.startsWith("http")) {
const { url, trailing } = splitUrlTrailingPunctuation(entity);
const out = [{
type: "link",
content: url,
href: url
}];
if (trailing) out.push({
type: "text",
content: trailing
});
return out;
}
if (entity.startsWith("@")) return [{
type: "mention",
content: entity,
href: `https://x.com/${entity.slice(1)}`
}];
if (entity.startsWith("#")) {
const tag = entity.slice(1);
return [{
type: "hashtag",
content: entity,
href: `https://x.com/hashtag/${encodeURIComponent(tag)}`
}];
}
if (entity.startsWith("$")) {
const symbol = entity.slice(1);
return [{
type: "cashtag",
content: entity,
href: `https://x.com/search?q=${encodeURIComponent(`$${symbol}`)}`
}];
}
return [{
type: "text",
content: entity
}];
}
function shortenUrl(url, maxLength = 50) {
if (url.length <= maxLength) return url;
try {
const urlObj = new URL(url);
const domain = urlObj.hostname;
const path = urlObj.pathname;
const segments = path.split("/").filter(Boolean);
const base = `${urlObj.protocol}//${domain}`;
const allowedPathLen = maxLength - base.length;
if (base.length >= maxLength) return `${base}${path}`;
if (segments.length <= 2 || path.length <= Math.min(20, Math.max(0, allowedPathLen))) return `${base}${path}`;
if (url.length > maxLength && segments.length > 2) return `${base}/${segments[0]}/.../${segments[segments.length - 1]}`;
return `${urlObj.protocol}//${domain}${path}`;
} catch {
return url.length > maxLength ? `${url.slice(0, maxLength)}...` : url;
}
}
function cx(...inputs) {
const classes = [];
for (const input of inputs) {
if (!input) continue;
if (typeof input === "string") classes.push(input);
else if (typeof input === "number") classes.push(String(input));
else if (Array.isArray(input)) {
const nested = cx(...input);
if (nested) classes.push(nested);
} else if (typeof input === "object") {
for (const [key, value] of Object.entries(input)) if (value) classes.push(key);
}
}
return classes.join(" ");
}
var ENTITY_PATTERN, TRAILING_URL_PUNCTUATION, TRAILING_URL_BRACKET_PAIRS;
var init_formatting = __esmMin((() => {
ENTITY_PATTERN = /(https?:\/\/[^\s]+|@[a-zA-Z0-9_]{1,15}|#[\p{L}\p{N}_]{1,50}|\$[A-Z]{1,6}(?:\.[A-Z]{1,2})?)/gu;
TRAILING_URL_PUNCTUATION = new Set([
".",
",",
"!",
"?",
";",
":"
]);
TRAILING_URL_BRACKET_PAIRS = [
[")", "("],
["]", "["],
["}", "{"]
];
}));
function IconButton(props) {
const [local, rest] = splitProps(props, [
"children",
"class",
"type",
"size"
]);
return (() => {
var _el$ = _tmpl$$8();
spread(_el$, mergeProps(rest, {
get type() {
return local.type ?? "button";
},
get ["class"]() {
return cx(local.class);
}
}), false, true);
insert(_el$, () => local.children);
return _el$;
})();
}
var _tmpl$$8;
var init_IconButton = __esmMin((() => {
init_web();
init_formatting();
init_solid();
_tmpl$$8 = /* @__PURE__ */ template(`<button>`);
}));
function resolve(value) {
return typeof value === "function" ? value() : value;
}
function resolveOptional(value) {
if (value === void 0) return;
return resolve(value);
}
function toAccessor(value) {
return typeof value === "function" ? value : () => value;
}
function toRequiredAccessor(resolver, fallback) {
return () => {
return resolveOptional(resolver()) ?? fallback;
};
}
function toOptionalAccessor(resolver) {
return () => resolveOptional(resolver());
}
var body, bodyCompact, setting, settingCompact, label, compactLabel, select, SettingsControls_module_default;
var init_SettingsControls_module = __esmMin((() => {
body = "xeg_EeShbY";
bodyCompact = "xeg_nm9B3P";
setting = "xeg_PI5CjL";
settingCompact = "xeg_VUTt8w";
label = "xeg_vhT3QS";
compactLabel = "xeg_Y62M5l";
select = "xeg_jpiS5y";
SettingsControls_module_default = {
body,
bodyCompact,
setting,
settingCompact,
label,
compactLabel,
select
};
}));
var SettingsControls_exports = /* @__PURE__ */ __export({ SettingsControls: () => SettingsControls }, 1);
function SettingsControls(props) {
const languageService = getLanguageService();
const [revision, setRevision] = createSignal(0);
onMount(() => {
onCleanup(languageService.onLanguageChange(() => setRevision((v) => v + 1)));
});
const strings = createMemo(() => {
revision();
return {
theme: {
title: languageService.translate("settings.theme"),
labels: {
auto: languageService.translate("settings.themeAuto"),
light: languageService.translate("settings.themeLight"),
dark: languageService.translate("settings.themeDark")
}
},
language: {
title: languageService.translate("settings.language"),
labels: {
auto: languageService.translate("settings.languageAuto"),
ko: languageService.translate("settings.languageKo"),
en: languageService.translate("settings.languageEn"),
ja: languageService.translate("settings.languageJa")
}
}
};
});
const selectClass = cx("xeg-inline-center", SettingsControls_module_default.select);
const containerClass = cx(SettingsControls_module_default.body, props.compact && SettingsControls_module_default.bodyCompact);
const settingClass = cx(SettingsControls_module_default.setting, props.compact && SettingsControls_module_default.settingCompact);
const labelClass = cx(SettingsControls_module_default.label, props.compact && SettingsControls_module_default.compactLabel);
const themeValue = createMemo(() => resolve(props.currentTheme));
const languageValue = createMemo(() => resolve(props.currentLanguage));
const themeSelectId = props["data-testid"] ? `${props["data-testid"]}-theme-select` : "settings-theme-select";
const languageSelectId = props["data-testid"] ? `${props["data-testid"]}-language-select` : "settings-language-select";
const themeStrings = () => strings().theme;
const languageStrings = () => strings().language;
return (() => {
var _el$ = _tmpl$$7(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling, _el$5 = _el$2.nextSibling, _el$6 = _el$5.firstChild, _el$7 = _el$6.nextSibling;
className(_el$, containerClass);
className(_el$2, settingClass);
setAttribute(_el$3, "for", themeSelectId);
className(_el$3, labelClass);
insert(_el$3, () => themeStrings().title);
addEventListener(_el$4, "change", props.onThemeChange);
setAttribute(_el$4, "id", themeSelectId);
className(_el$4, selectClass);
insert(_el$4, () => THEME_OPTIONS.map((option) => (() => {
var _el$8 = _tmpl$2$4();
_el$8.value = option;
insert(_el$8, () => themeStrings().labels[option]);
return _el$8;
})()));
className(_el$5, settingClass);
setAttribute(_el$6, "for", languageSelectId);
className(_el$6, labelClass);
insert(_el$6, () => languageStrings().title);
addEventListener(_el$7, "change", props.onLanguageChange);
setAttribute(_el$7, "id", languageSelectId);
className(_el$7, selectClass);
insert(_el$7, () => LANGUAGE_OPTIONS.map((option) => (() => {
var _el$9 = _tmpl$2$4();
_el$9.value = option;
insert(_el$9, () => languageStrings().labels[option]);
return _el$9;
})()));
createRenderEffect((_p$) => {
var _v$ = props["data-testid"], _v$2 = themeStrings().title, _v$3 = themeStrings().title, _v$4 = props["data-testid"] ? `${props["data-testid"]}-theme` : void 0, _v$5 = languageStrings().title, _v$6 = languageStrings().title, _v$7 = props["data-testid"] ? `${props["data-testid"]}-language` : void 0;
_v$ !== _p$.e && setAttribute(_el$, "data-testid", _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$4, "aria-label", _p$.t = _v$2);
_v$3 !== _p$.a && setAttribute(_el$4, "title", _p$.a = _v$3);
_v$4 !== _p$.o && setAttribute(_el$4, "data-testid", _p$.o = _v$4);
_v$5 !== _p$.i && setAttribute(_el$7, "aria-label", _p$.i = _v$5);
_v$6 !== _p$.n && setAttribute(_el$7, "title", _p$.n = _v$6);
_v$7 !== _p$.s && setAttribute(_el$7, "data-testid", _p$.s = _v$7);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0
});
createRenderEffect(() => _el$4.value = themeValue());
createRenderEffect(() => _el$7.value = languageValue());
return _el$;
})();
}
var _tmpl$$7, _tmpl$2$4, THEME_OPTIONS, LANGUAGE_OPTIONS;
var init_SettingsControls = __esmMin((() => {
init_web();
init_service_accessors();
init_formatting();
init_solid();
init_SettingsControls_module();
_tmpl$$7 = /* @__PURE__ */ template(`<div><div><label></label><select></select></div><div><label></label><select>`), _tmpl$2$4 = /* @__PURE__ */ template(`<option>`);
THEME_OPTIONS = [
"auto",
"light",
"dark"
];
LANGUAGE_OPTIONS = [
"auto",
"ko",
"en",
"ja"
];
}));
var _tmpl$$6, LazySettingsControls, SettingsControlsFallback, SettingsControlsLazy;
var init_SettingsControlsLazy = __esmMin((() => {
init_web();
init_solid();
_tmpl$$6 = /* @__PURE__ */ template(`<div style=height:7.5rem>`);
LazySettingsControls = lazy(() => Promise.resolve().then(() => (init_SettingsControls(), SettingsControls_exports)).then((module) => ({ default: module.SettingsControls })));
SettingsControlsFallback = () => {
return _tmpl$$6();
};
SettingsControlsLazy = (props) => createComponent(Suspense, {
get fallback() {
return createComponent(SettingsControlsFallback, {});
},
get children() {
return createComponent(LazySettingsControls, props);
}
});
}));
function safeEventPrevent(event) {
if (!event) return;
event.preventDefault();
event.stopPropagation();
}
function safeEventPreventAll(event) {
if (!event) return;
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation?.();
}
function findScrollableAncestor(target, scrollableSelector) {
if (!(target instanceof HTMLElement)) return null;
return target.closest(scrollableSelector);
}
function canConsumeWheelEvent(element, deltaY, tolerance = 1) {
const overflow = element.scrollHeight - element.clientHeight;
if (overflow <= tolerance) return false;
if (deltaY < 0) return element.scrollTop > tolerance;
if (deltaY > 0) return element.scrollTop < overflow - tolerance;
return true;
}
function shouldAllowWheelDefault(event, options) {
const scrollable = findScrollableAncestor(event.target, options.scrollableSelector);
if (!scrollable) return false;
return canConsumeWheelEvent(scrollable, event.deltaY, options.tolerance);
}
var toolbarButton$1, galleryToolbar, settingsExpanded, tweetPanelExpanded, stateIdle, stateLoading, stateDownloading, stateError, toolbarContent, toolbarControls, counterBlock, separator, downloadCurrent, downloadAll, closeButton, downloadButton, mediaCounterWrapper, mediaCounter, currentIndex, totalCount, progressBar, progressFill, fitButton, settingsPanel, tweetPanel, panelExpanded, tweetPanelBody, tweetContent, tweetLink, Toolbar_module_default;
var init_Toolbar_module = __esmMin((() => {
toolbarButton$1 = "xeg_4eojab";
galleryToolbar = "xeg_fLg7uD";
settingsExpanded = "xeg_ZpP8ej";
tweetPanelExpanded = "xeg_t4eqv-";
stateIdle = "xeg_ojCWl4";
stateLoading = "xeg_Y6KFai";
stateDownloading = "xeg_n-abf0";
stateError = "xeg_bEzlgK";
toolbarContent = "xeg_f8g4ur";
toolbarControls = "xeg_Ix3ja2";
counterBlock = "xeg_0EHq9g";
separator = "xeg_FKnOOH";
downloadCurrent = "xeg_njlfQM";
downloadAll = "xeg_AU-dPz";
closeButton = "xeg_Vn14NE";
downloadButton = "xeg_atmJJM";
mediaCounterWrapper = "xeg_GG869J";
mediaCounter = "xeg_2cjmvu";
currentIndex = "xeg_JEXmPu";
totalCount = "xeg_d1et2f";
progressBar = "xeg_vB6NL3";
progressFill = "xeg_LWQwIA";
fitButton = "xeg_Q7dUY4";
settingsPanel = "xeg_JcF-YS";
tweetPanel = "xeg_yRtvAY";
panelExpanded = "xeg_4a2L8u";
tweetPanelBody = "xeg_w56Ci4";
tweetContent = "xeg_jmjGCs";
tweetLink = "xeg_ZzP6Op";
Toolbar_module_default = {
toolbarButton: toolbarButton$1,
galleryToolbar,
settingsExpanded,
tweetPanelExpanded,
stateIdle,
stateLoading,
stateDownloading,
stateError,
toolbarContent,
toolbarControls,
counterBlock,
separator,
downloadCurrent,
downloadAll,
closeButton,
downloadButton,
mediaCounterWrapper,
mediaCounter,
currentIndex,
totalCount,
progressBar,
progressFill,
fitButton,
settingsPanel,
tweetPanel,
panelExpanded,
tweetPanelBody,
tweetContent,
tweetLink
};
}));
var TweetTextPanel_exports = /* @__PURE__ */ __export({ default: () => TweetTextPanel }, 1);
function renderTweetAnchor(accessor, kind, displayText) {
const token = accessor();
return (() => {
var _el$ = _tmpl$$5();
_el$.$$click = (e) => e.stopPropagation();
setAttribute(_el$, "data-kind", kind);
insert(_el$, () => displayText ?? token.content);
createRenderEffect((_p$) => {
var _v$ = token.href, _v$2 = Toolbar_module_default.tweetLink;
_v$ !== _p$.e && setAttribute(_el$, "href", _p$.e = _v$);
_v$2 !== _p$.t && className(_el$, _p$.t = _v$2);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$;
})();
}
function TweetTextPanel(props) {
return (() => {
var _el$2 = _tmpl$2$3(), _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$3.nextSibling;
insert(_el$4, () => getLanguageService().translate("toolbar.tweetText") || "Tweet text");
insert(_el$5, (() => {
var _c$ = memo(() => !!props.tweetTextHTML);
return () => _c$() ? (() => {
var _el$6 = _tmpl$3$1();
createRenderEffect(() => _el$6.innerHTML = sanitizeHTML(props.tweetTextHTML));
return _el$6;
})() : createComponent(For, {
get each() {
return formatTweetText(props.tweetText ?? "");
},
children: (token) => createComponent(Switch, { get children() {
return [
createComponent(Match, {
get when() {
return token.type === "link" && token;
},
children: (linkToken) => renderTweetAnchor(linkToken, "url", shortenUrl(linkToken().content, 40))
}),
createComponent(Match, {
get when() {
return token.type === "mention" && token;
},
children: (mentionToken) => renderTweetAnchor(mentionToken, "mention")
}),
createComponent(Match, {
get when() {
return token.type === "hashtag" && token;
},
children: (hashtagToken) => renderTweetAnchor(hashtagToken, "hashtag")
}),
createComponent(Match, {
get when() {
return token.type === "cashtag" && token;
},
children: (cashtagToken) => renderTweetAnchor(cashtagToken, "cashtag")
}),
createComponent(Match, {
get when() {
return token.type === "break";
},
get children() {
return _tmpl$4$1();
}
}),
createComponent(Match, {
get when() {
return token.type === "text" && token;
},
children: (textToken) => (() => {
var _el$8 = _tmpl$5$1();
insert(_el$8, () => textToken().content);
return _el$8;
})()
})
];
} })
});
})());
createRenderEffect((_p$) => {
var _v$3 = Toolbar_module_default.tweetPanelBody, _v$4 = Toolbar_module_default.tweetTextHeader, _v$5 = Toolbar_module_default.tweetTextLabel, _v$6 = Toolbar_module_default.tweetContent;
_v$3 !== _p$.e && className(_el$2, _p$.e = _v$3);
_v$4 !== _p$.t && className(_el$3, _p$.t = _v$4);
_v$5 !== _p$.a && className(_el$4, _p$.a = _v$5);
_v$6 !== _p$.o && className(_el$5, _p$.o = _v$6);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0
});
return _el$2;
})();
}
var _tmpl$$5, _tmpl$2$3, _tmpl$3$1, _tmpl$4$1, _tmpl$5$1;
var init_TweetTextPanel = __esmMin((() => {
init_web();
init_service_accessors();
init_formatting();
init_html_sanitizer();
init_solid();
init_Toolbar_module();
_tmpl$$5 = /* @__PURE__ */ template(`<a target=_blank rel="noopener noreferrer">`), _tmpl$2$3 = /* @__PURE__ */ template(`<div><div><span></span></div><div data-gallery-element=tweet-content data-gallery-scrollable=true>`), _tmpl$3$1 = /* @__PURE__ */ template(`<div>`), _tmpl$4$1 = /* @__PURE__ */ template(`<br>`), _tmpl$5$1 = /* @__PURE__ */ template(`<span>`);
delegateEvents(["click"]);
}));
function ToolbarView(props) {
const totalCount$1 = createMemo(() => resolve(props.totalCount));
const currentIndex$1 = createMemo(() => resolve(props.currentIndex));
const displayedIndex = createMemo(() => props.displayedIndex());
const isToolbarDisabled = createMemo(() => Boolean(resolveOptional(props.disabled)));
const activeFitMode = createMemo(() => props.activeFitMode());
const tweetText = createMemo(() => resolveOptional(props.tweetText) ?? null);
const tweetTextHTML = createMemo(() => resolveOptional(props.tweetTextHTML) ?? null);
const [toolbarElement, setToolbarElement] = createSignal(null);
const [counterElement, setCounterElement] = createSignal(null);
const [settingsPanelEl, setSettingsPanelEl] = createSignal(null);
const [tweetPanelEl, setTweetPanelEl] = createSignal(null);
const nav = createMemo(() => props.navState());
const assignToolbarRef = (element) => {
setToolbarElement(element);
props.settingsController.assignToolbarRef(element);
};
const assignSettingsPanelRef = (element) => {
setSettingsPanelEl(element);
props.settingsController.assignSettingsPanelRef(element);
};
createEffect(() => {
const current = String(currentIndex$1());
const focused$1 = String(displayedIndex());
const toolbar = toolbarElement();
if (toolbar) {
toolbar.dataset.currentIndex = current;
toolbar.dataset.focusedIndex = focused$1;
}
const counter = counterElement();
if (counter) {
counter.dataset.currentIndex = current;
counter.dataset.focusedIndex = focused$1;
}
});
const hasTweetContent = () => Boolean(tweetTextHTML() ?? tweetText());
const toolbarButtonClass = (...extra) => cx(Toolbar_module_default.toolbarButton, "xeg-inline-center", ...extra);
const toolbarStateClass = () => {
switch (props.toolbarDataState()) {
case "loading": return Toolbar_module_default.stateLoading;
case "downloading": return Toolbar_module_default.stateDownloading;
case "error": return Toolbar_module_default.stateError;
default: return Toolbar_module_default.stateIdle;
}
};
const handlePanelWheel = (event) => {
if (shouldAllowWheelDefault$1(event)) {
event.stopPropagation();
return;
}
};
const preventScrollChaining = (event) => {
if (shouldAllowWheelDefault$1(event)) {
event.stopPropagation();
return;
}
safeEventPreventAll(event);
};
const registerWheelListener = (getElement, handler, options) => {
createEffect(() => {
const element = getElement();
if (!element) return;
const bus = getEventBus();
const listener = (event) => handler(event);
const id = bus.addDOMListener(element, "wheel", listener, {
passive: options.passive,
context: options.context
});
onCleanup(() => bus.remove(id));
});
};
registerWheelListener(toolbarElement, preventScrollChaining, {
passive: false,
context: "toolbar:wheel:prevent-scroll-chaining"
});
registerWheelListener(settingsPanelEl, preventScrollChaining, {
passive: false,
context: "toolbar:wheel:prevent-scroll-chaining:settings-panel"
});
registerWheelListener(tweetPanelEl, handlePanelWheel, {
passive: true,
context: "toolbar:wheel:panel"
});
return (() => {
var _el$ = _tmpl$$4(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$4.firstChild, _el$6 = _el$5.firstChild, _el$7 = _el$6.firstChild, _el$8 = _el$7.nextSibling, _el$9 = _el$8.nextSibling, _el$0 = _el$6.nextSibling, _el$1 = _el$0.firstChild, _el$10 = _el$2.nextSibling, _el$11 = _el$10.nextSibling;
_el$.$$keydown = (event) => props.settingsController.handleToolbarKeyDown(event);
addEventListener(_el$, "blur", props.onBlur);
addEventListener(_el$, "focus", props.onFocus);
use(assignToolbarRef, _el$);
insert(_el$3, createComponent(IconButton, {
get ["class"]() {
return toolbarButtonClass();
},
size: "toolbar",
"aria-label": "Previous Media",
title: "Previous Media (Left Arrow)",
get disabled() {
return nav().prevDisabled;
},
get onClick() {
return props.onPreviousClick;
},
"data-gallery-element": "nav-previous",
get children() {
return createComponent(HeroArrowSmallLeft, { size: 18 });
}
}), _el$4);
insert(_el$3, createComponent(IconButton, {
get ["class"]() {
return toolbarButtonClass();
},
size: "toolbar",
"aria-label": "Next Media",
title: "Next Media (Right Arrow)",
get disabled() {
return nav().nextDisabled;
},
get onClick() {
return props.onNextClick;
},
"data-gallery-element": "nav-next",
get children() {
return createComponent(HeroArrowSmallRight, { size: 18 });
}
}), _el$4);
use((element) => {
setCounterElement(element);
}, _el$6);
insert(_el$7, () => displayedIndex() + 1);
insert(_el$9, totalCount$1);
insert(_el$3, () => props.fitModeOrder.map(({ mode, Icon: Icon$1 }) => {
const label$1 = props.fitModeLabels[mode];
return createComponent(IconButton, {
get ["class"]() {
return toolbarButtonClass(Toolbar_module_default.fitButton);
},
size: "toolbar",
get onClick() {
return props.handleFitModeClick(mode);
},
get disabled() {
return props.isFitDisabled(mode);
},
get ["aria-label"]() {
return label$1.label;
},
get title() {
return label$1.title;
},
get ["aria-pressed"]() {
return activeFitMode() === mode;
},
"data-gallery-element": `fit-${mode}`,
get children() {
return createComponent(Icon$1, { size: 18 });
}
});
}), null);
insert(_el$3, createComponent(IconButton, {
get ["class"]() {
return toolbarButtonClass(Toolbar_module_default.downloadButton, Toolbar_module_default.downloadCurrent);
},
size: "toolbar",
get onClick() {
return props.onDownloadCurrent;
},
get disabled() {
return nav().downloadDisabled;
},
"aria-label": "Download Current File",
title: "Download Current File (Ctrl+D)",
"data-gallery-element": "download-current",
get children() {
return createComponent(HeroDownload, { size: 18 });
}
}), null);
insert(_el$3, (() => {
var _c$ = memo(() => !!nav().canDownloadAll);
return () => _c$() && createComponent(IconButton, {
get ["class"]() {
return toolbarButtonClass(Toolbar_module_default.downloadButton, Toolbar_module_default.downloadAll);
},
size: "toolbar",
get onClick() {
return props.onDownloadAll;
},
get disabled() {
return nav().downloadDisabled;
},
get ["aria-label"]() {
return `Download all ${totalCount$1()} files as ZIP`;
},
get title() {
return `Download all ${totalCount$1()} files as ZIP`;
},
"data-gallery-element": "download-all",
get children() {
return createComponent(HeroArrowDownOnSquareStack, { size: 18 });
}
});
})(), null);
insert(_el$3, (() => {
var _c$2 = memo(() => !!props.showSettingsButton);
return () => _c$2() && createComponent(IconButton, {
ref(r$) {
var _ref$ = props.settingsController.assignSettingsButtonRef;
typeof _ref$ === "function" ? _ref$(r$) : props.settingsController.assignSettingsButtonRef = r$;
},
id: "settings-button",
get ["class"]() {
return toolbarButtonClass();
},
size: "toolbar",
"aria-label": "Open Settings",
get ["aria-expanded"]() {
return props.settingsController.isSettingsExpanded() ? "true" : "false";
},
"aria-controls": "toolbar-settings-panel",
title: "Settings",
get disabled() {
return isToolbarDisabled();
},
get onMouseDown() {
return props.settingsController.handleSettingsMouseDown;
},
get onClick() {
return props.settingsController.handleSettingsClick;
},
"data-gallery-element": "settings",
get children() {
return createComponent(HeroCog6Tooth, { size: 18 });
}
});
})(), null);
insert(_el$3, (() => {
var _c$3 = memo(() => !!hasTweetContent());
return () => _c$3() && createComponent(IconButton, {
id: "tweet-text-button",
get ["class"]() {
return toolbarButtonClass();
},
size: "toolbar",
get ["aria-label"]() {
return getLanguageService().translate("toolbar.tweetText") || "View tweet text";
},
get ["aria-expanded"]() {
return props.isTweetPanelExpanded() ? "true" : "false";
},
"aria-controls": "toolbar-tweet-panel",
get title() {
return getLanguageService().translate("toolbar.tweetText") || "Tweet text";
},
get disabled() {
return isToolbarDisabled();
},
get onClick() {
return props.toggleTweetPanelExpanded;
},
"data-gallery-element": "tweet-text",
get children() {
return createComponent(HeroChatBubbleLeftRight, { size: 18 });
}
});
})(), null);
insert(_el$3, createComponent(IconButton, {
get ["class"]() {
return toolbarButtonClass(Toolbar_module_default.closeButton);
},
size: "toolbar",
"aria-label": "Close Gallery",
title: "Close Gallery (Esc)",
get disabled() {
return isToolbarDisabled();
},
get onClick() {
return props.onCloseClick;
},
"data-gallery-element": "close",
get children() {
return createComponent(HeroArrowLeftOnRectangle, { size: 18 });
}
}), null);
addEventListener(_el$10, "click", props.settingsController.handlePanelClick, true);
addEventListener(_el$10, "mousedown", props.settingsController.handlePanelMouseDown, true);
use(assignSettingsPanelRef, _el$10);
insert(_el$10, createComponent(Show, {
get when() {
return props.settingsController.isSettingsExpanded();
},
get children() {
return createComponent(SettingsControlsLazy, {
get currentTheme() {
return props.settingsController.currentTheme;
},
get currentLanguage() {
return props.settingsController.currentLanguage;
},
get onThemeChange() {
return props.settingsController.handleThemeChange;
},
get onLanguageChange() {
return props.settingsController.handleLanguageChange;
},
compact: true,
"data-testid": "settings-controls"
});
}
}));
use(setTweetPanelEl, _el$11);
insert(_el$11, createComponent(Show, {
get when() {
return memo(() => !!props.isTweetPanelExpanded())() && hasTweetContent();
},
get children() {
return createComponent(Suspense, {
get fallback() {
return (() => {
var _el$12 = _tmpl$2$2();
createRenderEffect(() => className(_el$12, Toolbar_module_default.tweetPanelLoading));
return _el$12;
})();
},
get children() {
return createComponent(TweetTextPanelLazy, {
get tweetText() {
return tweetText() ?? void 0;
},
get tweetTextHTML() {
return tweetTextHTML() ?? void 0;
}
});
}
});
}
}));
createRenderEffect((_p$) => {
var _v$ = cx(props.toolbarClass(), toolbarStateClass(), props.settingsController.isSettingsExpanded() ? Toolbar_module_default.settingsExpanded : void 0, props.isTweetPanelExpanded() ? Toolbar_module_default.tweetPanelExpanded : void 0), _v$2 = props.role ?? "toolbar", _v$3 = props["aria-label"] ?? "Gallery Toolbar", _v$4 = props["aria-describedby"], _v$5 = isToolbarDisabled(), _v$6 = props["data-testid"], _v$7 = props.tabIndex, _v$8 = cx(Toolbar_module_default.toolbarContent, "xeg-row-center"), _v$9 = Toolbar_module_default.toolbarControls, _v$0 = Toolbar_module_default.counterBlock, _v$1 = cx(Toolbar_module_default.mediaCounterWrapper, "xeg-inline-center"), _v$10 = cx(Toolbar_module_default.mediaCounter, "xeg-inline-center"), _v$11 = Toolbar_module_default.currentIndex, _v$12 = Toolbar_module_default.separator, _v$13 = Toolbar_module_default.totalCount, _v$14 = Toolbar_module_default.progressBar, _v$15 = Toolbar_module_default.progressFill, _v$16 = props.progressWidth(), _v$17 = cx(Toolbar_module_default.settingsPanel, props.settingsController.isSettingsExpanded() ? Toolbar_module_default.panelExpanded : void 0), _v$18 = cx(Toolbar_module_default.tweetPanel, props.isTweetPanelExpanded() ? Toolbar_module_default.panelExpanded : void 0), _v$19 = getLanguageService().translate("toolbar.tweetTextPanel") || "Tweet text panel";
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && setAttribute(_el$, "role", _p$.t = _v$2);
_v$3 !== _p$.a && setAttribute(_el$, "aria-label", _p$.a = _v$3);
_v$4 !== _p$.o && setAttribute(_el$, "aria-describedby", _p$.o = _v$4);
_v$5 !== _p$.i && setAttribute(_el$, "aria-disabled", _p$.i = _v$5);
_v$6 !== _p$.n && setAttribute(_el$, "data-testid", _p$.n = _v$6);
_v$7 !== _p$.s && setAttribute(_el$, "tabindex", _p$.s = _v$7);
_v$8 !== _p$.h && className(_el$2, _p$.h = _v$8);
_v$9 !== _p$.r && className(_el$3, _p$.r = _v$9);
_v$0 !== _p$.d && className(_el$4, _p$.d = _v$0);
_v$1 !== _p$.l && className(_el$5, _p$.l = _v$1);
_v$10 !== _p$.u && className(_el$6, _p$.u = _v$10);
_v$11 !== _p$.c && className(_el$7, _p$.c = _v$11);
_v$12 !== _p$.w && className(_el$8, _p$.w = _v$12);
_v$13 !== _p$.m && className(_el$9, _p$.m = _v$13);
_v$14 !== _p$.f && className(_el$0, _p$.f = _v$14);
_v$15 !== _p$.y && className(_el$1, _p$.y = _v$15);
_v$16 !== _p$.g && setStyleProperty(_el$1, "width", _p$.g = _v$16);
_v$17 !== _p$.p && className(_el$10, _p$.p = _v$17);
_v$18 !== _p$.b && className(_el$11, _p$.b = _v$18);
_v$19 !== _p$.T && setAttribute(_el$11, "aria-label", _p$.T = _v$19);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0,
n: void 0,
s: void 0,
h: void 0,
r: void 0,
d: void 0,
l: void 0,
u: void 0,
c: void 0,
w: void 0,
m: void 0,
f: void 0,
y: void 0,
g: void 0,
p: void 0,
b: void 0,
T: void 0
});
return _el$;
})();
}
var _tmpl$$4, _tmpl$2$2, TweetTextPanelLazy, SCROLLABLE_SELECTOR, SCROLL_LOCK_TOLERANCE, shouldAllowWheelDefault$1;
var init_ToolbarView = __esmMin((() => {
init_web();
init_IconButton();
init_Icon();
init_SettingsControlsLazy();
init_service_accessors();
init_events();
init_formatting();
init_solid();
init_Toolbar_module();
_tmpl$$4 = /* @__PURE__ */ template(`<div data-gallery-element=toolbar><div data-gallery-element=toolbar-content><div data-gallery-element=toolbar-controls><div data-gallery-element=counter-section><div><span aria-live=polite data-gallery-element=counter><span></span><span>/</span><span></span></span><div><div></div></div></div></div></div></div><div id=toolbar-settings-panel data-gallery-scrollable=true role=region aria-label="Settings Panel"aria-labelledby=settings-button data-gallery-element=settings-panel></div><div id=toolbar-tweet-panel role=region aria-labelledby=tweet-text-button data-gallery-element=tweet-panel>`), _tmpl$2$2 = /* @__PURE__ */ template(`<div>Loading...`);
TweetTextPanelLazy = lazy(() => Promise.resolve().then(() => (init_TweetTextPanel(), TweetTextPanel_exports)));
SCROLLABLE_SELECTOR = "[data-gallery-scrollable=\"true\"]";
SCROLL_LOCK_TOLERANCE = 1;
shouldAllowWheelDefault$1 = (event) => {
return shouldAllowWheelDefault(event, {
scrollableSelector: SCROLLABLE_SELECTOR,
tolerance: SCROLL_LOCK_TOLERANCE
});
};
delegateEvents([
"keydown",
"mousedown",
"click"
]);
}));
function useToolbarSettingsController(options) {
const { isSettingsExpanded, setSettingsExpanded, toggleSettingsExpanded, documentRef = typeof document !== "undefined" ? document : void 0, themeService: providedThemeService, languageService: providedLanguageService, focusDelayMs = DEFAULT_FOCUS_DELAY_MS, selectChangeGuardMs = DEFAULT_SELECT_GUARD_MS } = options;
const themeManager = providedThemeService ?? getThemeService();
const languageService = providedLanguageService ?? getLanguageService();
const scheduleTimeout = (callback, delay$1) => {
return globalTimerManager.setTimeout(callback, delay$1);
};
const clearScheduledTimeout = (handle) => {
if (handle == null) return;
globalTimerManager.clearTimeout(handle);
};
const [toolbarRef, setToolbarRef] = createSignal(void 0);
const [settingsPanelRef, setSettingsPanelRef] = createSignal(void 0);
const [settingsButtonRef, setSettingsButtonRef] = createSignal(void 0);
const toThemeOption = (value) => {
return value === "light" || value === "dark" ? value : "auto";
};
const getInitialTheme = () => {
try {
return toThemeOption(themeManager.getCurrentTheme());
} catch (error$1) {}
return "auto";
};
const [currentTheme, setCurrentTheme] = createSignal(getInitialTheme());
const [currentLanguage, setCurrentLanguage] = createSignal(languageService.getCurrentLanguage());
const syncThemeFromService = () => {
try {
setCurrentTheme(toThemeOption(themeManager.getCurrentTheme()));
} catch (error$1) {
;
}
};
syncThemeFromService();
if (typeof themeManager.isInitialized === "function" && !themeManager.isInitialized()) themeManager.initialize().then(syncThemeFromService).catch((error$1) => {
;
});
createEffect(() => {
const unsubscribe = themeManager.onThemeChange((_, setting$1) => {
setCurrentTheme(toThemeOption(setting$1));
});
onCleanup(() => {
unsubscribe?.();
});
});
createEffect(() => {
const unsubscribe = languageService.onLanguageChange((next) => {
setCurrentLanguage(next);
});
onCleanup(() => {
unsubscribe();
});
});
createEffect(() => {
if (!documentRef) return;
const expanded = isSettingsExpanded();
const panel = settingsPanelRef();
if (!expanded || !panel) return;
const eventManager = EventManager.getInstance();
const listenerContext = `toolbar-settings-controller:${toolbarSettingsControllerListenerSeq++}`;
let isSelectActive = false;
let selectGuardTimeout = null;
const handleSelectFocus = () => {
isSelectActive = true;
};
const handleSelectBlur = () => {
scheduleTimeout(() => {
isSelectActive = false;
}, 100);
};
const handleSelectChange = () => {
isSelectActive = true;
clearScheduledTimeout(selectGuardTimeout);
selectGuardTimeout = scheduleTimeout(() => {
isSelectActive = false;
selectGuardTimeout = null;
}, selectChangeGuardMs);
};
Array.from(panel.querySelectorAll("select")).forEach((select$1) => {
eventManager.addListener(select$1, "focus", handleSelectFocus, void 0, listenerContext);
eventManager.addListener(select$1, "blur", handleSelectBlur, void 0, listenerContext);
eventManager.addListener(select$1, "change", handleSelectChange, void 0, listenerContext);
});
const handleOutsideClick = (event) => {
const target = event.target;
const settingsButton = settingsButtonRef();
const toolbarElement = toolbarRef();
if (!target) return;
if (isSelectActive) return;
const targetElement = target;
if (toolbarElement?.contains(targetElement)) return;
if (settingsButton?.contains(targetElement)) return;
if (panel.contains(targetElement)) return;
let currentNode = targetElement;
while (currentNode) {
if (currentNode.tagName === "SELECT" || currentNode.tagName === "OPTION") return;
currentNode = currentNode.parentElement;
}
setSettingsExpanded(false);
};
eventManager.addListener(documentRef, "mousedown", handleOutsideClick, { capture: false }, listenerContext);
onCleanup(() => {
clearScheduledTimeout(selectGuardTimeout);
eventManager.removeByContext(listenerContext);
});
});
const handleSettingsClick = (event) => {
event.stopImmediatePropagation?.();
const wasExpanded = isSettingsExpanded();
toggleSettingsExpanded();
if (!wasExpanded) scheduleTimeout(() => {
const firstControl = settingsPanelRef()?.querySelector("select");
if (firstControl) firstControl.focus({ preventScroll: true });
}, focusDelayMs);
};
const handleSettingsMouseDown = (event) => {
event.stopPropagation();
};
const handleToolbarKeyDown = (event) => {
if (event.key === "Escape" && isSettingsExpanded()) {
event.preventDefault();
event.stopPropagation();
setSettingsExpanded(false);
scheduleTimeout(() => {
const settingsButton = settingsButtonRef();
if (settingsButton) settingsButton.focus({ preventScroll: true });
}, focusDelayMs);
}
};
const handlePanelMouseDown = (event) => {
event.stopPropagation();
};
const handlePanelClick = (event) => {
event.stopPropagation();
};
const handleThemeChange = (event) => {
const select$1 = event.target;
if (!select$1) return;
const theme = toThemeOption(select$1.value);
setCurrentTheme(theme);
themeManager.setTheme(theme);
try {
const settingsService = tryGetSettingsManager();
if (settingsService) settingsService.set("gallery.theme", theme).catch((error$1) => {
;
});
} catch (error$1) {
;
}
};
const handleLanguageChange = (event) => {
const select$1 = event.target;
if (!select$1) return;
const language = select$1.value || "auto";
setCurrentLanguage(language);
languageService.setLanguage(language);
};
return {
assignToolbarRef: (element) => {
setToolbarRef(element ?? void 0);
},
assignSettingsPanelRef: (element) => {
setSettingsPanelRef(element ?? void 0);
},
assignSettingsButtonRef: (element) => {
setSettingsButtonRef(element ?? void 0);
},
isSettingsExpanded,
currentTheme,
currentLanguage,
handleSettingsClick,
handleSettingsMouseDown,
handleToolbarKeyDown,
handlePanelMouseDown,
handlePanelClick,
handleThemeChange,
handleLanguageChange
};
}
var toolbarSettingsControllerListenerSeq, DEFAULT_FOCUS_DELAY_MS, DEFAULT_SELECT_GUARD_MS;
var init_use_toolbar_settings_controller = __esmMin((() => {
init_service_accessors();
init_logging();
init_event_manager();
init_timer_management();
init_solid();
toolbarSettingsControllerListenerSeq = 0;
DEFAULT_FOCUS_DELAY_MS = 50;
DEFAULT_SELECT_GUARD_MS = 300;
}));
function useToolbarState() {
const [isDownloading, setIsDownloading] = createSignal(INITIAL_STATE$2.isDownloading);
const [isLoading, setIsLoading] = createSignal(INITIAL_STATE$2.isLoading);
const [hasError, setHasError] = createSignal(INITIAL_STATE$2.hasError);
let lastDownloadToggle = 0;
let downloadTimeoutRef = null;
const clearDownloadTimeout = () => {
if (downloadTimeoutRef !== null) {
globalTimerManager.clearTimeout(downloadTimeoutRef);
downloadTimeoutRef = null;
}
};
const setDownloading = (downloading) => {
const now = Date.now();
if (downloading) {
lastDownloadToggle = now;
clearDownloadTimeout();
setIsDownloading(true);
setHasError(false);
return;
}
const timeSinceStart = now - lastDownloadToggle;
const minDisplayTime = 300;
if (timeSinceStart < minDisplayTime) {
clearDownloadTimeout();
downloadTimeoutRef = globalTimerManager.setTimeout(() => {
setIsDownloading(false);
downloadTimeoutRef = null;
}, minDisplayTime - timeSinceStart);
return;
}
setIsDownloading(false);
};
const setLoading = (loading$1) => {
setIsLoading(loading$1);
if (loading$1) setHasError(false);
};
const setError$1 = (errorState) => {
setHasError(errorState);
if (errorState) {
setIsLoading(false);
setIsDownloading(false);
}
};
const resetState = () => {
clearDownloadTimeout();
lastDownloadToggle = 0;
setIsDownloading(INITIAL_STATE$2.isDownloading);
setIsLoading(INITIAL_STATE$2.isLoading);
setHasError(INITIAL_STATE$2.hasError);
};
onCleanup(() => {
clearDownloadTimeout();
});
return [{
get isDownloading() {
return isDownloading();
},
get isLoading() {
return isLoading();
},
get hasError() {
return hasError();
}
}, {
setDownloading,
setLoading,
setError: setError$1,
resetState
}];
}
var INITIAL_STATE$2;
var init_use_toolbar_state = __esmMin((() => {
init_timer_management();
init_solid();
INITIAL_STATE$2 = {
isDownloading: false,
isLoading: false,
hasError: false
};
}));
var init_hooks = __esmMin((() => {
init_use_toolbar_settings_controller();
init_use_toolbar_state();
}));
function getToolbarDataState(state) {
if (state.hasError) return "error";
if (state.isDownloading) return "downloading";
if (state.isLoading) return "loading";
return "idle";
}
function ToolbarContainer(rawProps) {
const props = mergeProps(DEFAULT_PROPS, rawProps);
const currentIndex$1 = toRequiredAccessor(() => props.currentIndex, 0);
const totalCount$1 = toRequiredAccessor(() => props.totalCount, 0);
const focusedIndex = toRequiredAccessor(() => props.focusedIndex, null);
const isDownloadingProp = toRequiredAccessor(() => props.isDownloading, false);
const isDisabled = toRequiredAccessor(() => props.disabled, false);
const currentFitMode = toOptionalAccessor(() => props.currentFitMode);
const tweetText = toOptionalAccessor(() => props.tweetText);
const tweetTextHTML = toOptionalAccessor(() => props.tweetTextHTML);
const [toolbarState, toolbarActions] = useToolbarState();
const [settingsExpandedSignal, setSettingsExpandedSignal] = createSignal(false);
const [tweetExpanded, setTweetExpanded] = createSignal(false);
const setSettingsExpanded = (expanded) => {
setSettingsExpandedSignal(expanded);
if (expanded) setTweetExpanded(false);
};
const toggleSettings = () => {
setSettingsExpanded(!settingsExpandedSignal());
};
const toggleTweet = () => {
setTweetExpanded((prev) => {
const next = !prev;
if (next) setSettingsExpanded(false);
return next;
});
};
createEffect(on(isDownloadingProp, (value) => {
toolbarActions.setDownloading(Boolean(value));
}));
const baseSettingsController = useToolbarSettingsController({
isSettingsExpanded: settingsExpandedSignal,
setSettingsExpanded,
toggleSettingsExpanded: toggleSettings
});
const settingsController = {
...baseSettingsController,
handleSettingsClick: (event) => {
const wasOpen = settingsExpandedSignal();
baseSettingsController.handleSettingsClick(event);
if (!wasOpen && settingsExpandedSignal()) props.handlers.lifecycle.onOpenSettings?.();
}
};
const toolbarClass = createMemo(() => cx(Toolbar_module_default.toolbar, Toolbar_module_default.galleryToolbar, props.className));
const totalItems = createMemo(() => Math.max(0, totalCount$1()));
const currentIndexForNav = createMemo(() => clampIndex(currentIndex$1(), totalItems()));
const displayedIndex = createMemo(() => resolveDisplayedIndex({
total: totalItems(),
currentIndex: currentIndexForNav(),
focusedIndex: focusedIndex()
}));
const progressWidth = createMemo(() => calculateProgressWidth(displayedIndex(), totalItems()));
const toolbarDataState = createMemo(() => getToolbarDataState(toolbarState));
const navState = createMemo(() => computeNavigationState({
total: totalItems(),
toolbarDisabled: Boolean(isDisabled()),
downloadBusy: Boolean(isDownloadingProp() || toolbarState.isDownloading)
}));
const fitModeHandlers = createMemo(() => ({
original: props.handlers.fitMode?.onFitOriginal,
fitWidth: props.handlers.fitMode?.onFitWidth,
fitHeight: props.handlers.fitMode?.onFitHeight,
fitContainer: props.handlers.fitMode?.onFitContainer
}));
const activeFitMode = createMemo(() => currentFitMode() ?? FIT_MODE_ORDER[0]?.mode ?? "original");
const isToolbarDisabled = () => Boolean(isDisabled());
const handleFitModeClick = (mode) => (event) => {
safeEventPreventAll(event);
if (isToolbarDisabled()) return;
fitModeHandlers()[mode]?.(event);
};
const isFitDisabled = (mode) => {
if (isToolbarDisabled()) return true;
if (!fitModeHandlers()[mode]) return true;
return activeFitMode() === mode;
};
const handlePrevious = createGuardedHandler(() => navState().prevDisabled, props.handlers.navigation.onPrevious);
const handleNext = createGuardedHandler(() => navState().nextDisabled, props.handlers.navigation.onNext);
const handleDownloadCurrent = createGuardedHandler(() => navState().downloadDisabled, props.handlers.download.onDownloadCurrent);
const handleDownloadAll = createGuardedHandler(() => navState().downloadDisabled, props.handlers.download.onDownloadAll);
const handleClose = (event) => {
safeEventPrevent(event);
props.handlers.lifecycle.onClose();
};
return createComponent(ToolbarView, {
currentIndex: currentIndex$1,
focusedIndex,
totalCount: totalCount$1,
isDownloading: isDownloadingProp,
disabled: isDisabled,
get ["aria-label"]() {
return props["aria-label"];
},
get ["aria-describedby"]() {
return props["aria-describedby"];
},
get role() {
return props.role;
},
get tabIndex() {
return props.tabIndex;
},
get ["data-testid"]() {
return props["data-testid"];
},
get onFocus() {
return props.handlers.focus?.onFocus;
},
get onBlur() {
return props.handlers.focus?.onBlur;
},
tweetText,
tweetTextHTML,
toolbarClass,
toolbarState,
toolbarDataState,
navState,
displayedIndex,
progressWidth,
fitModeOrder: FIT_MODE_ORDER,
fitModeLabels: FIT_MODE_LABELS,
activeFitMode,
handleFitModeClick,
isFitDisabled,
onPreviousClick: handlePrevious,
onNextClick: handleNext,
onDownloadCurrent: handleDownloadCurrent,
onDownloadAll: handleDownloadAll,
onCloseClick: handleClose,
settingsController,
get showSettingsButton() {
return typeof props.handlers.lifecycle.onOpenSettings === "function";
},
isTweetPanelExpanded: tweetExpanded,
toggleTweetPanelExpanded: toggleTweet
});
}
var DEFAULT_PROPS, FIT_MODE_LABELS, FIT_MODE_ORDER, resolveDisplayedIndex, calculateProgressWidth, computeNavigationState, createGuardedHandler, Toolbar;
var init_Toolbar = __esmMin((() => {
init_web();
init_Icon();
init_ToolbarView();
init_hooks();
init_formatting();
init_solid();
init_Toolbar_module();
DEFAULT_PROPS = {
isDownloading: false,
disabled: false,
className: ""
};
FIT_MODE_LABELS = {
original: {
label: "Original",
title: "Original Size (1:1)"
},
fitWidth: {
label: "Fit Width",
title: "Fit to Width"
},
fitHeight: {
label: "Fit Height",
title: "Fit to Height"
},
fitContainer: {
label: "Fit Window",
title: "Fit to Window"
}
};
FIT_MODE_ORDER = [
{
mode: "original",
Icon: HeroArrowsPointingOut
},
{
mode: "fitWidth",
Icon: HeroArrowsRightLeft
},
{
mode: "fitHeight",
Icon: HeroArrowsUpDown
},
{
mode: "fitContainer",
Icon: HeroArrowsPointingIn
}
];
resolveDisplayedIndex = ({ total, currentIndex: currentIndex$1, focusedIndex }) => {
if (total <= 0) return 0;
if (typeof focusedIndex === "number" && focusedIndex >= 0 && focusedIndex < total) return focusedIndex;
return clampIndex(currentIndex$1, total);
};
calculateProgressWidth = (index, total) => {
if (total <= 0) return "0%";
return `${(index + 1) / total * 100}%`;
};
computeNavigationState = ({ total, toolbarDisabled, downloadBusy }) => {
const hasItems = total > 0;
const canNavigate = hasItems && total > 1;
return {
prevDisabled: toolbarDisabled || !canNavigate,
nextDisabled: toolbarDisabled || !canNavigate,
canDownloadAll: total > 1,
downloadDisabled: toolbarDisabled || downloadBusy || !hasItems,
anyActionDisabled: toolbarDisabled
};
};
createGuardedHandler = (guard, action) => {
return (event) => {
safeEventPrevent(event);
if (guard()) return;
action?.();
};
};
Toolbar = ToolbarContainer;
}));
function requireSettingsService() {
const service = tryGetSettingsManager();
if (!service) throw new Error("SettingsService is not registered. Ensure bootstrap registers it before usage.");
return service;
}
function getTypedSettingOr(path, fallback) {
const value = requireSettingsService().get(path);
return value === void 0 ? fallback : value;
}
function setTypedSetting(path, value) {
return requireSettingsService().set(path, value);
}
function asUnknownRecord(value) {
return value;
}
function isPlainObject(value) {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function collectDotPaths(obj, prefix = "") {
const paths = [];
for (const [key, value] of Object.entries(obj)) {
const current = prefix ? `${prefix}.${key}` : key;
paths.push(current);
if (isPlainObject(value)) paths.push(...collectDotPaths(value, current));
}
return paths;
}
var SETTINGS_PATH_SCHEMA;
var init_typed_settings = __esmMin((() => {
init_constants$1();
init_service_accessors();
SETTINGS_PATH_SCHEMA = {
...asUnknownRecord(DEFAULT_SETTINGS),
download: {
...asUnknownRecord(asUnknownRecord(DEFAULT_SETTINGS).download),
customTemplate: void 0
},
tokens: {
...asUnknownRecord(asUnknownRecord(DEFAULT_SETTINGS).tokens),
bearerToken: void 0,
lastRefresh: void 0
}
};
new Set(collectDotPaths(SETTINGS_PATH_SCHEMA));
}));
var init_settings_access = __esmMin((() => {
init_typed_settings();
}));
function createSignalSafe(initial) {
const [read, write] = createSignal(initial, { equals: false });
const setSignal = write;
const subscribers = /* @__PURE__ */ new Set();
const notify = (val) => {
for (const subscriber of subscribers) subscriber(val);
};
const signalObject = {
set(value) {
if (typeof value === "function") setSignal(() => value);
else setSignal(value);
notify(value);
},
update(updater) {
const nextValue = updater(read());
setSignal(updater);
notify(nextValue);
},
subscribe(callback) {
subscribers.add(callback);
notify(read());
return () => {
subscribers.delete(callback);
};
}
};
Object.defineProperty(signalObject, "value", {
get: () => {
return read();
},
set: (v) => {
signalObject.set(v);
},
enumerable: true
});
return signalObject;
}
function effectSafe(fn) {
return createRoot((dispose$1) => {
createComputed(() => {
fn();
});
return dispose$1;
});
}
var init_signal_factory = __esmMin((() => {
init_logging();
init_solid();
createScopedLogger?.("SignalFactory") ?? createLogger({ prefix: "[SignalFactory]" });
}));
function getDownloadState() {
if (!downloadStateSignal) downloadStateSignal = createSignalSafe(INITIAL_STATE$1);
return downloadStateSignal;
}
function setProcessingFlag(isProcessing) {
const currentState = downloadState.value;
if (currentState.isProcessing === isProcessing) return;
downloadState.value = {
...currentState,
isProcessing
};
}
function acquireDownloadLock() {
setProcessingFlag(true);
return () => {
const { queue, activeTasks } = downloadState.value;
if (queue.length === 0 && activeTasks.size === 0) setProcessingFlag(false);
};
}
function isDownloadLocked() {
return downloadState.value.isProcessing;
}
var INITIAL_STATE$1, downloadStateSignal, downloadState;
var init_download_signals = __esmMin((() => {
init_signal_factory();
INITIAL_STATE$1 = {
activeTasks: /* @__PURE__ */ new Map(),
queue: [],
isProcessing: false
};
downloadStateSignal = null;
downloadState = {
get value() {
return getDownloadState().value;
},
set value(newState) {
getDownloadState().value = newState;
},
subscribe(callback) {
return getDownloadState().subscribe(callback);
}
};
}));
function resolveNowMs(nowMs$1) {
return nowMs$1 ?? Date.now();
}
function isValidNavigationSource(value) {
return typeof value === "string" && VALID_NAVIGATION_SOURCES.includes(value);
}
function isValidNavigationTrigger(value) {
return typeof value === "string" && VALID_NAVIGATION_TRIGGERS.includes(value);
}
function isManualSource(source) {
return source === "button" || source === "keyboard";
}
function createNavigationActionError(context, reason) {
return /* @__PURE__ */ new Error(`[Gallery] Invalid navigation action (${context}): ${reason}`);
}
function validateNavigationParams(targetIndex, source, trigger, context) {
if (typeof targetIndex !== "number" || Number.isNaN(targetIndex)) throw createNavigationActionError(context, "Navigate payload targetIndex invalid");
if (!isValidNavigationSource(source)) throw createNavigationActionError(context, `Navigate payload source invalid: ${String(source)}`);
if (!isValidNavigationTrigger(trigger)) throw createNavigationActionError(context, `Navigate payload trigger invalid: ${String(trigger)}`);
}
function recordNavigation(targetIndex, source, nowMs$1) {
const timestamp = resolveNowMs(nowMs$1);
const currentIndex$1 = navigationSignals.lastNavigatedIndex.value;
const currentSource = navigationSignals.lastSource.value;
if (targetIndex === currentIndex$1 && isManualSource(source) && isManualSource(currentSource)) {
navigationSignals.lastTimestamp.value = timestamp;
return { isDuplicate: true };
}
navigationSignals.lastSource.value = source;
navigationSignals.lastTimestamp.value = timestamp;
navigationSignals.lastNavigatedIndex.value = targetIndex;
return { isDuplicate: false };
}
function resetNavigation(nowMs$1) {
navigationSignals.lastSource.value = INITIAL_NAVIGATION_STATE.lastSource;
navigationSignals.lastTimestamp.value = resolveNowMs(nowMs$1);
navigationSignals.lastNavigatedIndex.value = INITIAL_NAVIGATION_STATE.lastNavigatedIndex;
}
function resolveNavigationSource(trigger) {
if (trigger === "scroll") return "scroll";
if (trigger === "keyboard") return "keyboard";
return "button";
}
var INITIAL_NAVIGATION_STATE, VALID_NAVIGATION_SOURCES, VALID_NAVIGATION_TRIGGERS, navigationSignals;
var init_navigation_state = __esmMin((() => {
init_signal_factory();
INITIAL_NAVIGATION_STATE = {
lastSource: "auto-focus",
lastTimestamp: 0,
lastNavigatedIndex: null
};
VALID_NAVIGATION_SOURCES = [
"button",
"keyboard",
"scroll",
"auto-focus"
];
VALID_NAVIGATION_TRIGGERS = [
"button",
"click",
"keyboard",
"scroll"
];
navigationSignals = {
lastSource: createSignalSafe(INITIAL_NAVIGATION_STATE.lastSource),
lastTimestamp: createSignalSafe(INITIAL_NAVIGATION_STATE.lastTimestamp),
lastNavigatedIndex: createSignalSafe(INITIAL_NAVIGATION_STATE.lastNavigatedIndex)
};
}));
function setError(error$1) {
uiSignals.error.value = error$1;
if (error$1) {
uiSignals.isLoading.value = false;
logger.error(`[Gallery UI] Error: ${error$1}`);
}
}
var INITIAL_UI_STATE, uiSignals;
var init_ui_state = __esmMin((() => {
init_logging();
init_signal_factory();
INITIAL_UI_STATE = {
viewMode: "vertical",
isLoading: false,
error: null
};
uiSignals = {
viewMode: createSignalSafe(INITIAL_UI_STATE.viewMode),
isLoading: createSignalSafe(INITIAL_UI_STATE.isLoading),
error: createSignalSafe(INITIAL_UI_STATE.error)
};
}));
function createEventEmitter() {
const listeners$1 = /* @__PURE__ */ new Map();
return {
on(event, callback) {
if (!listeners$1.has(event)) listeners$1.set(event, /* @__PURE__ */ new Set());
listeners$1.get(event).add(callback);
return () => {
listeners$1.get(event)?.delete(callback);
};
},
emit(event, data) {
const eventListeners = listeners$1.get(event);
if (!eventListeners) return;
eventListeners.forEach((callback) => {
try {
callback(data);
} catch (error$1) {
logger.error(`[EventEmitter] Listener error for event "${String(event)}":`, error$1);
}
});
},
dispose() {
listeners$1.clear();
}
};
}
var init_emitter = __esmMin((() => {
init_logging();
}));
function applyGalleryStateUpdate(state) {
batch$1(() => {
gallerySignals.mediaItems.value = state.mediaItems;
gallerySignals.currentIndex.value = state.currentIndex;
gallerySignals.isLoading.value = state.isLoading;
gallerySignals.error.value = state.error;
gallerySignals.viewMode.value = state.viewMode;
gallerySignals.isOpen.value = state.isOpen;
});
}
function openGallery(items, startIndex = 0) {
const validIndex = clampIndex(startIndex, items.length);
galleryState.value = {
...galleryState.value,
isOpen: true,
mediaItems: items,
currentIndex: validIndex,
error: null
};
gallerySignals.focusedIndex.value = validIndex;
resetNavigation();
;
}
function closeGallery() {
galleryState.value = {
...galleryState.value,
isOpen: false,
currentIndex: 0,
mediaItems: [],
error: null
};
gallerySignals.focusedIndex.value = null;
gallerySignals.currentVideoElement.value = null;
resetNavigation();
;
}
function navigateToItem(index, trigger = "button", source) {
const state = galleryState.value;
const validIndex = clampIndex(index, state.mediaItems.length);
const navigationSource = source ?? resolveNavigationSource(trigger);
validateNavigationParams(validIndex, navigationSource, trigger, "navigateToItem");
if (recordNavigation(validIndex, navigationSource).isDuplicate) {
;
gallerySignals.focusedIndex.value = validIndex;
return;
}
galleryIndexEvents.emit("navigate:start", {
from: state.currentIndex,
to: validIndex,
trigger
});
batch$1(() => {
galleryState.value = {
...state,
currentIndex: validIndex
};
gallerySignals.focusedIndex.value = validIndex;
});
galleryIndexEvents.emit("navigate:complete", {
index: validIndex,
trigger
});
;
}
function navigatePrevious(trigger = "button") {
const state = galleryState.value;
const baseIndex = getCurrentActiveIndex();
navigateToItem(baseIndex > 0 ? baseIndex - 1 : state.mediaItems.length - 1, trigger, resolveNavigationSource(trigger));
}
function navigateNext(trigger = "button") {
const state = galleryState.value;
const baseIndex = getCurrentActiveIndex();
navigateToItem(baseIndex < state.mediaItems.length - 1 ? baseIndex + 1 : 0, trigger, resolveNavigationSource(trigger));
}
function getCurrentActiveIndex() {
return gallerySignals.focusedIndex.value ?? galleryState.value.currentIndex;
}
var batch$1, INITIAL_STATE, galleryIndexEvents, gallerySignals, galleryState;
var init_gallery_signals = __esmMin((() => {
init_logging();
init_navigation_state();
init_signal_factory();
init_ui_state();
init_emitter();
init_solid();
batch$1 = batch;
INITIAL_STATE = {
isOpen: false,
mediaItems: [],
currentIndex: 0,
isLoading: false,
error: null,
viewMode: "vertical"
};
galleryIndexEvents = createEventEmitter();
gallerySignals = {
isOpen: createSignalSafe(INITIAL_STATE.isOpen),
mediaItems: createSignalSafe(INITIAL_STATE.mediaItems),
currentIndex: createSignalSafe(INITIAL_STATE.currentIndex),
isLoading: uiSignals.isLoading,
error: uiSignals.error,
viewMode: uiSignals.viewMode,
focusedIndex: createSignalSafe(null),
currentVideoElement: createSignalSafe(null)
};
galleryState = {
get value() {
return {
isOpen: gallerySignals.isOpen.value,
mediaItems: gallerySignals.mediaItems.value,
currentIndex: gallerySignals.currentIndex.value,
isLoading: gallerySignals.isLoading.value,
error: gallerySignals.error.value,
viewMode: gallerySignals.viewMode.value
};
},
set value(state) {
applyGalleryStateUpdate(state);
},
subscribe(callback) {
return effectSafe(() => {
callback(galleryState.value);
});
}
};
}));
function isDownloadUiBusy(context) {
return Boolean(context.downloadProcessing);
}
var DEFAULTS, FocusCoordinator;
var init_focus_coordinator = __esmMin((() => {
init_performance();
DEFAULTS = {
THRESHOLD: [
0,
.1,
.2,
.3,
.4,
.5,
.6,
.7,
.8,
.9,
1
],
ROOT_MARGIN: "0px"
};
FocusCoordinator = class {
items = /* @__PURE__ */ new Map();
observerOptions;
constructor(options) {
this.options = options;
this.observerOptions = {
threshold: options.threshold ?? [...DEFAULTS.THRESHOLD],
rootMargin: options.rootMargin ?? DEFAULTS.ROOT_MARGIN
};
}
registerItem(index, element) {
this.items.get(index)?.unsubscribe?.();
if (!element) {
this.items.delete(index);
return;
}
const trackedItem = {
element,
isVisible: false
};
trackedItem.unsubscribe = SharedObserver.observe(element, (entry) => {
const item = this.items.get(index);
if (item) {
item.entry = entry;
item.isVisible = entry.isIntersecting;
}
}, this.observerOptions);
this.items.set(index, trackedItem);
}
updateFocus(force = false) {
if (!force && !this.options.isEnabled()) return;
const container$2 = this.options.container();
if (!container$2) return;
const containerRect = container$2.getBoundingClientRect();
const selection = this.selectBestCandidate(containerRect);
if (!selection) return;
if (this.options.activeIndex() !== selection.index) this.options.onFocusChange(selection.index, "auto");
}
cleanup() {
for (const item of this.items.values()) item.unsubscribe?.();
this.items.clear();
}
selectBestCandidate(containerRect) {
const viewportHeight = Math.max(containerRect.height, 1);
const viewportTop = containerRect.top;
const viewportBottom = viewportTop + viewportHeight;
const viewportCenter = viewportTop + viewportHeight / 2;
const topProximityThreshold = 50;
let bestCandidate = null;
let topAlignedCandidate = null;
let highestVisibilityCandidate = null;
for (const [index, item] of this.items) {
if (!item.isVisible || !item.element.isConnected) continue;
const rect = item.element.getBoundingClientRect();
const itemTop = rect.top;
const itemHeight = rect.height;
const itemBottom = itemTop + itemHeight;
const itemCenter = itemTop + itemHeight / 2;
const visibleHeight = Math.max(0, Math.min(itemBottom, viewportBottom) - Math.max(itemTop, viewportTop));
const visibilityRatio = itemHeight > 0 ? visibleHeight / itemHeight : 0;
const centerDistance = Math.abs(itemCenter - viewportCenter);
const topDistance = Math.abs(itemTop - viewportTop);
if (topDistance <= topProximityThreshold && visibilityRatio > .1) {
if (!topAlignedCandidate || topDistance < topAlignedCandidate.distance) topAlignedCandidate = {
index,
distance: topDistance
};
}
if (visibilityRatio > .1) {
if (!highestVisibilityCandidate || visibilityRatio > highestVisibilityCandidate.ratio || visibilityRatio === highestVisibilityCandidate.ratio && centerDistance < highestVisibilityCandidate.centerDistance) highestVisibilityCandidate = {
index,
ratio: visibilityRatio,
centerDistance
};
}
if (!bestCandidate || centerDistance < bestCandidate.distance) bestCandidate = {
index,
distance: centerDistance
};
}
if (topAlignedCandidate) return topAlignedCandidate;
if (highestVisibilityCandidate) return {
index: highestVisibilityCandidate.index,
distance: 0
};
return bestCandidate;
}
};
}));
function useGalleryFocusTracker(options) {
const isEnabled = toAccessor(options.isEnabled);
const container$2 = toAccessor(options.container);
const isScrolling$1 = options.isScrolling;
const lastNavigationTrigger = options.lastNavigationTrigger;
const shouldTrack = () => {
return isEnabled() && (isScrolling$1() || lastNavigationTrigger() === "scroll");
};
const coordinator = new FocusCoordinator({
isEnabled: shouldTrack,
container: container$2,
activeIndex: () => galleryState.value.currentIndex,
...options.threshold !== void 0 && { threshold: options.threshold },
rootMargin: options.rootMargin ?? "0px",
onFocusChange: (index, source) => {
if (source === "auto" && index !== null) navigateToItem(index, "scroll", "auto-focus");
}
});
onCleanup(() => coordinator.cleanup());
const handleItemFocus = (index) => {
navigateToItem(index, "keyboard");
};
return {
focusedIndex: () => gallerySignals.focusedIndex.value,
registerItem: (index, element) => coordinator.registerItem(index, element),
handleItemFocus,
forceSync: () => coordinator.updateFocus(true)
};
}
var init_useGalleryFocusTracker = __esmMin((() => {
init_focus_coordinator();
init_gallery_signals();
init_solid();
}));
function useGalleryItemScroll(containerRef, currentIndex$1, totalItems, options = {}) {
const containerAccessor = typeof containerRef === "function" ? containerRef : () => containerRef.current;
const enabled = toAccessor(options.enabled ?? true);
const behavior = toAccessor(options.behavior ?? "auto");
const block = toAccessor(options.block ?? "start");
const alignToCenter = toAccessor(options.alignToCenter ?? false);
const isScrolling$1 = toAccessor(options.isScrolling ?? false);
const currentIndexAccessor = toAccessor(currentIndex$1);
const totalItemsAccessor = toAccessor(totalItems);
const scrollToItem = (index) => {
const container$2 = containerAccessor();
if (!enabled() || !container$2 || index < 0 || index >= totalItemsAccessor()) return;
const itemsRoot = container$2.querySelector("[data-xeg-role=\"items-list\"], [data-xeg-role=\"items-container\"]");
if (!itemsRoot) return;
const target = itemsRoot.querySelectorAll("[data-xeg-role=\"gallery-item\"]")[index];
if (target) {
options.onScrollStart?.();
target.scrollIntoView({
behavior: behavior(),
block: alignToCenter() ? "center" : block(),
inline: "nearest"
});
} else requestAnimationFrame(() => {
const retryTarget = itemsRoot.querySelectorAll("[data-xeg-role=\"gallery-item\"]")[index];
if (retryTarget) {
options.onScrollStart?.();
retryTarget.scrollIntoView({
behavior: behavior(),
block: alignToCenter() ? "center" : block(),
inline: "nearest"
});
}
});
};
createEffect(() => {
const index = currentIndexAccessor();
const container$2 = containerAccessor();
const total = totalItemsAccessor();
if (!container$2 || total <= 0) return;
if (untrack(enabled) && !untrack(isScrolling$1)) scrollToItem(index);
});
return {
scrollToItem,
scrollToCurrentItem: () => scrollToItem(currentIndexAccessor())
};
}
var init_useGalleryItemScroll = __esmMin((() => {
init_solid();
}));
function createEventListener(handler) {
return (event) => {
handler(event);
};
}
function isHTMLElement(element) {
return element instanceof HTMLElement;
}
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function safeClosest(element, selector) {
try {
return element.closest(selector);
} catch (error$1) {
return null;
}
}
function safeMatches(element, selector) {
try {
return element.matches(selector);
} catch (error$1) {
return false;
}
}
function isWithinVideoPlayer(element) {
return VIDEO_PLAYER_CONTEXT_SELECTORS.some((selector) => safeClosest(element, selector) !== null);
}
function matchesVideoControlSelectors(element) {
return VIDEO_CONTROL_SELECTORS.some((selector) => safeMatches(element, selector) || safeClosest(element, selector) !== null);
}
function hasInputRangeSignature(element) {
if (typeof element.matches !== "function") return false;
return safeMatches(element, "input[type=\"range\"]");
}
function getNearestAttributeValue(element, attribute) {
if (element.hasAttribute(attribute)) return element.getAttribute(attribute);
return safeClosest(element, `[${attribute}]`)?.getAttribute(attribute) ?? null;
}
function containsControlToken(value, tokens) {
if (!value) return false;
const normalized = value.toLowerCase();
return tokens.some((token) => normalized.includes(token));
}
function collectControlAttributeSnapshot(element) {
return {
role: element.getAttribute("role"),
dataTestId: getNearestAttributeValue(element, "data-testid"),
ariaLabel: getNearestAttributeValue(element, "aria-label")
};
}
function gatherVideoControlEvidence(element) {
if (matchesVideoControlSelectors(element)) return {
selectorMatch: true,
datasetToken: false,
ariaToken: false,
playerScoped: true,
roleMatch: false,
rangeSignature: hasInputRangeSignature(element)
};
const attributes = collectControlAttributeSnapshot(element);
const datasetToken = containsControlToken(attributes.dataTestId, VIDEO_CONTROL_DATASET_PREFIXES);
const ariaToken = containsControlToken(attributes.ariaLabel, VIDEO_CONTROL_ARIA_TOKENS);
const roleMatch = attributes.role ? VIDEO_CONTROL_ROLE_SET.has(attributes.role.toLowerCase()) : false;
return {
selectorMatch: false,
datasetToken,
ariaToken,
playerScoped: isWithinVideoPlayer(element),
roleMatch,
rangeSignature: hasInputRangeSignature(element)
};
}
function isVideoControlElement(element) {
if (!isHTMLElement(element)) return false;
if (element.tagName.toLowerCase() === "video") return true;
if (typeof element.matches !== "function") return false;
const evidence = gatherVideoControlEvidence(element);
if (evidence.selectorMatch) return true;
if (evidence.datasetToken || evidence.ariaToken) return true;
if (!evidence.playerScoped) return false;
if (evidence.roleMatch || evidence.rangeSignature) return true;
return false;
}
function isGalleryInternalElement(element) {
if (!element) return false;
if (!(element instanceof HTMLElement)) return false;
if (typeof element.matches !== "function") {
;
return false;
}
return GALLERY_SELECTORS.some((selector) => {
try {
return element.matches(selector) || element.closest(selector) !== null;
} catch (error$1) {
;
return false;
}
});
}
function isGalleryInternalEvent(event) {
const target = event.target;
if (!isHTMLElement(target)) return false;
return isGalleryInternalElement(target);
}
var GALLERY_SELECTORS, VIDEO_PLAYER_CONTEXT_SELECTORS, VIDEO_CONTROL_ROLE_SET;
var init_utils = __esmMin((() => {
init_constants$1();
init_selectors();
init_logging();
GALLERY_SELECTORS = CSS.INTERNAL_SELECTORS;
VIDEO_PLAYER_CONTEXT_SELECTORS = [
VIDEO_PLAYER_SELECTOR,
"[data-testid=\"videoComponent\"]",
"[data-testid=\"videoPlayerControls\"]",
"[data-testid=\"videoPlayerOverlay\"]",
"[role=\"application\"]",
"[aria-label*=\"Video\"]"
];
VIDEO_CONTROL_ROLE_SET = new Set(VIDEO_CONTROL_ROLES.map((role) => role.toLowerCase()));
}));
function useGalleryScroll({ container: container$2, scrollTarget, onScroll, onScrollEnd, enabled = true, programmaticScrollTimestamp }) {
const containerAccessor = toAccessor(container$2);
const scrollTargetAccessor = toAccessor(scrollTarget ?? containerAccessor);
const enabledAccessor = toAccessor(enabled);
const programmaticTimestampAccessor = toAccessor(programmaticScrollTimestamp ?? 0);
const isGalleryOpen = createMemo(() => galleryState.value.isOpen);
const [isScrolling$1, setIsScrolling] = createSignal(false);
const [lastScrollTime, setLastScrollTime] = createSignal(0);
let scrollIdleTimerId = null;
const clearScrollIdleTimer = () => {
if (scrollIdleTimerId !== null) {
globalTimerManager.clearTimeout(scrollIdleTimerId);
scrollIdleTimerId = null;
}
};
const markScrolling = () => {
setIsScrolling(true);
setLastScrollTime(Date.now());
};
const scheduleScrollEnd = () => {
clearScrollIdleTimer();
scrollIdleTimerId = globalTimerManager.setTimeout(() => {
setIsScrolling(false);
;
onScrollEnd?.();
}, 250);
};
const shouldIgnoreScroll = () => Date.now() - programmaticTimestampAccessor() < PROGRAMMATIC_SCROLL_WINDOW;
const isToolbarScroll = (event) => {
const target = event.target;
if (!(target instanceof HTMLElement)) return false;
return Boolean(target.closest("[data-gallery-element=\"toolbar\"]") || target.closest("[data-gallery-element=\"settings-panel\"]") || target.closest("[data-gallery-element=\"tweet-panel\"]") || target.closest("[data-role=\"toolbar\"]"));
};
const handleWheel = (event) => {
if (!isGalleryOpen() || !isGalleryInternalEvent(event)) return;
if (isToolbarScroll(event)) return;
markScrolling();
onScroll?.();
scheduleScrollEnd();
};
const handleScroll = () => {
if (!isGalleryOpen() || shouldIgnoreScroll()) return;
markScrolling();
onScroll?.();
scheduleScrollEnd();
};
createEffect(() => {
const isEnabled = enabledAccessor();
const containerElement = containerAccessor();
const eventTarget = scrollTargetAccessor() ?? containerElement;
if (!isEnabled || !eventTarget) {
setIsScrolling(false);
clearScrollIdleTimer();
return;
}
const eventManager = EventManager.getInstance();
const listenerContext = createContextId(LISTENER_CONTEXT_PREFIX);
const listenerIds = [];
const registerListener = (type, handler) => {
const id = eventManager.addListener(eventTarget, type, handler, { passive: true }, listenerContext);
if (id) {
listenerIds.push(id);
;
}
};
registerListener("wheel", handleWheel);
registerListener("scroll", handleScroll);
;
onCleanup(() => {
for (const id of listenerIds) {
eventManager.removeListener(id);
;
}
clearScrollIdleTimer();
setIsScrolling(false);
});
});
onCleanup(clearScrollIdleTimer);
return {
isScrolling: isScrolling$1,
lastScrollTime
};
}
var PROGRAMMATIC_SCROLL_WINDOW, LISTENER_CONTEXT_PREFIX;
var init_useGalleryScroll = __esmMin((() => {
init_utils();
init_logging();
init_event_manager();
init_gallery_signals();
init_timer_management();
init_solid();
PROGRAMMATIC_SCROLL_WINDOW = 100;
LISTENER_CONTEXT_PREFIX = "useGalleryScroll";
}));
function useGalleryKeyboard({ onClose }) {
createEffect(() => {
if (typeof document === "undefined") return;
const isEditableTarget = (target) => {
const element = target;
if (!element) return false;
const tag = element.tagName?.toUpperCase();
if (tag === "INPUT" || tag === "TEXTAREA") return true;
return Boolean(element.isContentEditable);
};
const handleKeyDown = (event) => {
const keyboardEvent = event;
if (isEditableTarget(keyboardEvent.target)) return;
let handled = false;
if (keyboardEvent.key === "Escape") {
onClose();
handled = true;
}
if (handled) {
keyboardEvent.preventDefault();
keyboardEvent.stopPropagation();
}
};
const eventManager = EventManager.getInstance();
const listenerId = eventManager.addListener(document, "keydown", handleKeyDown, { capture: true }, "gallery-keyboard-navigation");
onCleanup(() => {
if (listenerId) eventManager.removeListener(listenerId);
});
});
}
var init_useGalleryKeyboard = __esmMin((() => {
init_event_manager();
init_solid();
}));
function ensureGalleryScrollAvailable(element) {
if (!element) return;
element.querySelectorAll("[data-xeg-role=\"items-list\"], .itemsList, .content").forEach((el) => {
if (el.style.overflowY !== "auto" && el.style.overflowY !== "scroll") el.style.overflowY = "auto";
});
}
function computeViewportConstraints(rect, chrome = {}) {
const vw = Math.max(0, Math.floor(rect.width));
const vh = Math.max(0, Math.floor(rect.height));
const top = Math.max(0, Math.floor(chrome.paddingTop ?? 0));
const bottom = Math.max(0, Math.floor(chrome.paddingBottom ?? 0));
const toolbar = Math.max(0, Math.floor(chrome.toolbarHeight ?? 0));
return {
viewportW: vw,
viewportH: vh,
constrainedH: Math.max(0, vh - top - bottom - toolbar)
};
}
function applyViewportCssVars(el, v) {
el.style.setProperty("--xeg-viewport-w", `${v.viewportW}px`);
el.style.setProperty("--xeg-viewport-h", `${v.viewportH}px`);
el.style.setProperty("--xeg-viewport-height-constrained", `${v.constrainedH}px`);
}
function observeViewportCssVars(el, getChrome) {
let disposed = false;
const calcAndApply = () => {
if (disposed) return;
const rect = el.getBoundingClientRect();
applyViewportCssVars(el, computeViewportConstraints({
width: rect.width,
height: rect.height
}, getChrome()));
};
let pending = false;
const schedule = () => {
if (pending) return;
pending = true;
if (typeof requestAnimationFrame === "function") requestAnimationFrame(() => {
pending = false;
calcAndApply();
});
else globalTimerManager.setTimeout(() => {
pending = false;
calcAndApply();
}, 0);
};
calcAndApply();
let ro = null;
if (typeof ResizeObserver !== "undefined") {
ro = new ResizeObserver(() => schedule());
try {
ro.observe(el);
} catch {}
}
const onResize = () => schedule();
let resizeListenerId = null;
if (typeof window !== "undefined") resizeListenerId = EventManager.getInstance().addListener(window, "resize", createEventListener(onResize), { passive: true }, "viewport:resize");
return () => {
disposed = true;
if (ro) try {
ro.disconnect();
} catch {}
if (resizeListenerId) {
EventManager.getInstance().removeListener(resizeListenerId);
resizeListenerId = null;
}
};
}
var init_viewport = __esmMin((() => {
init_event_manager();
init_timer_management();
}));
function runCssAnimation(element, className$1, options) {
return new Promise((resolve$1) => {
try {
const handleAnimationEnd = () => {
element.classList.remove(className$1);
options.onComplete?.();
resolve$1();
};
element.addEventListener("animationend", handleAnimationEnd, { once: true });
element.classList.add(className$1);
} catch (error$1) {
safeLogAnimationFailure("CSS animation failed", error$1);
resolve$1();
}
});
}
async function animateGalleryEnter(element, options = {}) {
return runCssAnimation(element, ANIMATION_CLASSES.FADE_IN, options);
}
async function animateGalleryExit(element, options = {}) {
return runCssAnimation(element, ANIMATION_CLASSES.FADE_OUT, options);
}
var ANIMATION_CLASSES, safeLogAnimationFailure;
var init_css_animations = __esmMin((() => {
ANIMATION_CLASSES = {
FADE_IN: "xeg-fade-in",
FADE_OUT: "xeg-fade-out"
};
safeLogAnimationFailure = (message, error$1) => {};
}));
function useGalleryLifecycle(options) {
const { containerEl, toolbarWrapperEl, isVisible } = options;
createEffect(on(containerEl, (element) => {
if (element) ensureGalleryScrollAvailable(element);
}));
createEffect(on([containerEl, isVisible], ([container$2, visible]) => {
if (!container$2) return;
if (visible) animateGalleryEnter(container$2);
else {
animateGalleryExit(container$2);
const logCleanupFailure = (error$1) => {
;
};
container$2.querySelectorAll("video").forEach((video$1) => {
try {
video$1.pause();
} catch (error$1) {
logCleanupFailure(error$1);
}
try {
if (video$1.currentTime !== 0) video$1.currentTime = 0;
} catch (error$1) {
logCleanupFailure(error$1);
}
});
}
}, { defer: true }));
createEffect(() => {
const container$2 = containerEl();
const wrapper = toolbarWrapperEl();
if (!container$2 || !wrapper) return;
const cleanup$1 = observeViewportCssVars(container$2, () => {
return {
toolbarHeight: wrapper ? Math.floor(wrapper.getBoundingClientRect().height) : 0,
paddingTop: 0,
paddingBottom: 0
};
});
onCleanup(() => {
cleanup$1?.();
});
});
}
var init_useGalleryLifecycle = __esmMin((() => {
init_viewport();
init_logging();
init_css_animations();
init_solid();
}));
function useGalleryNavigation(options) {
const { isVisible, scrollToItem } = options;
const [lastNavigationTrigger, setLastNavigationTrigger] = createSignal(null);
const [programmaticScrollTimestamp, setProgrammaticScrollTimestamp] = createSignal(0);
createEffect(on(isVisible, (visible) => {
if (!visible) return;
onCleanup(registerNavigationEvents({
onTriggerChange: setLastNavigationTrigger,
onNavigateComplete: ({ index, trigger }) => {
if (trigger === "scroll") return;
scrollToItem(index);
}
}));
}));
return {
lastNavigationTrigger,
setLastNavigationTrigger,
programmaticScrollTimestamp,
setProgrammaticScrollTimestamp
};
}
function registerNavigationEvents({ onTriggerChange, onNavigateComplete }) {
const stopStart = galleryIndexEvents.on("navigate:start", (payload) => onTriggerChange(payload.trigger));
const stopComplete = galleryIndexEvents.on("navigate:complete", (payload) => {
onTriggerChange(payload.trigger);
onNavigateComplete(payload);
});
return () => {
stopStart();
stopComplete();
};
}
var init_useGalleryNavigation = __esmMin((() => {
init_gallery_signals();
init_solid();
}));
function useToolbarAutoHide(options) {
const { isVisible, hasItems } = options;
const computeInitialVisibility = () => Boolean(isVisible() && hasItems());
const [isInitialToolbarVisible, setIsInitialToolbarVisible] = createSignal(computeInitialVisibility());
let activeTimer = null;
const clearActiveTimer = () => {
if (activeTimer === null) return;
globalTimerManager.clearTimeout(activeTimer);
activeTimer = null;
};
createEffect(() => {
onCleanup(clearActiveTimer);
if (!computeInitialVisibility()) {
setIsInitialToolbarVisible(false);
return;
}
setIsInitialToolbarVisible(true);
const rawAutoHideDelay = getTypedSettingOr("toolbar.autoHideDelay", 3e3);
const autoHideDelay = Math.max(0, typeof rawAutoHideDelay === "number" ? rawAutoHideDelay : 0);
if (autoHideDelay === 0) {
setIsInitialToolbarVisible(false);
return;
}
activeTimer = globalTimerManager.setTimeout(() => {
setIsInitialToolbarVisible(false);
activeTimer = null;
}, autoHideDelay);
});
return {
isInitialToolbarVisible,
setIsInitialToolbarVisible
};
}
var init_useToolbarAutoHide = __esmMin((() => {
init_settings_access();
init_timer_management();
init_solid();
}));
function useVerticalGallery(options) {
const { isVisible, currentIndex: currentIndex$1, mediaItemsCount, containerEl, toolbarWrapperEl, itemsContainerEl, onClose } = options;
let focusSyncCallback = null;
const { isInitialToolbarVisible, setIsInitialToolbarVisible } = useToolbarAutoHide({
isVisible,
hasItems: () => mediaItemsCount() > 0
});
let scrollToItemRef = null;
const navigationState = useGalleryNavigation({
isVisible,
scrollToItem: (index) => scrollToItemRef?.(index)
});
const { isScrolling: isScrolling$1 } = useGalleryScroll({
container: containerEl,
scrollTarget: itemsContainerEl,
enabled: isVisible,
programmaticScrollTimestamp: () => navigationState.programmaticScrollTimestamp(),
onScrollEnd: () => focusSyncCallback?.()
});
const { scrollToItem, scrollToCurrentItem } = useGalleryItemScroll(containerEl, currentIndex$1, mediaItemsCount, {
enabled: () => !isScrolling$1() && navigationState.lastNavigationTrigger() !== "scroll",
block: "start",
isScrolling: isScrolling$1,
onScrollStart: () => navigationState.setProgrammaticScrollTimestamp(Date.now())
});
scrollToItemRef = scrollToItem;
const { focusedIndex, registerItem: registerFocusItem, handleItemFocus, forceSync: focusTrackerForceSync } = useGalleryFocusTracker({
container: containerEl,
isEnabled: isVisible,
isScrolling: isScrolling$1,
lastNavigationTrigger: navigationState.lastNavigationTrigger
});
focusSyncCallback = focusTrackerForceSync;
useGalleryLifecycle({
containerEl,
toolbarWrapperEl,
isVisible
});
createEffect(() => {
if (isScrolling$1()) setIsInitialToolbarVisible(false);
});
useGalleryKeyboard({ onClose: onClose ?? (() => {}) });
return {
scroll: {
isScrolling: isScrolling$1,
scrollToItem,
scrollToCurrentItem
},
navigation: {
lastNavigationTrigger: navigationState.lastNavigationTrigger,
setLastNavigationTrigger: navigationState.setLastNavigationTrigger,
programmaticScrollTimestamp: navigationState.programmaticScrollTimestamp,
setProgrammaticScrollTimestamp: navigationState.setProgrammaticScrollTimestamp
},
focus: {
focusedIndex,
registerItem: registerFocusItem,
handleItemFocus,
forceSync: focusTrackerForceSync
},
toolbar: {
isInitialToolbarVisible,
setIsInitialToolbarVisible
}
};
}
var init_useVerticalGallery = __esmMin((() => {
init_useGalleryFocusTracker();
init_useGalleryItemScroll();
init_useGalleryScroll();
init_solid();
init_useGalleryKeyboard();
init_useGalleryLifecycle();
init_useGalleryNavigation();
init_useToolbarAutoHide();
}));
var xegVerticalGalleryContainer, container$1, toolbarWrapper, uiHidden, isScrolling, itemsContainer, empty, galleryItem, itemActive, scrollSpacer, toolbarHoverZone, initialToolbarVisible, toolbarButton, emptyMessage, VerticalGalleryView_module_default;
var init_VerticalGalleryView_module = __esmMin((() => {
xegVerticalGalleryContainer = "xeg_LSA44p";
container$1 = "xeg_X9gZRg";
toolbarWrapper = "xeg_meO3Up";
uiHidden = "xeg_9abgzR";
isScrolling = "xeg_sOsSyv";
itemsContainer = "xeg_gmRWyH";
empty = "xeg_yhK-Ds";
galleryItem = "xeg_EfVayF";
itemActive = "xeg_LxHLC8";
scrollSpacer = "xeg_sfF005";
toolbarHoverZone = "xeg_gC-mQz";
initialToolbarVisible = "xeg_Canm64";
toolbarButton = "xeg_e06XPV";
emptyMessage = "xeg_fwsrVX";
VerticalGalleryView_module_default = {
xegVerticalGalleryContainer,
container: container$1,
toolbarWrapper,
uiHidden,
isScrolling,
itemsContainer,
empty,
galleryItem,
itemActive,
scrollSpacer,
toolbarHoverZone,
initialToolbarVisible,
toolbarButton,
emptyMessage
};
}));
function createDebounced(fn, delayMs = 300) {
let timeoutId = null;
let pendingArgs = null;
const cancel = () => {
if (timeoutId !== null) {
globalTimerManager.clearTimeout(timeoutId);
timeoutId = null;
}
pendingArgs = null;
};
const flush = () => {
if (timeoutId !== null && pendingArgs !== null) {
globalTimerManager.clearTimeout(timeoutId);
timeoutId = null;
const args = pendingArgs;
pendingArgs = null;
fn(...args);
}
};
const debounced = ((...args) => {
cancel();
pendingArgs = args;
timeoutId = globalTimerManager.setTimeout(() => {
timeoutId = null;
const savedArgs = pendingArgs;
pendingArgs = null;
if (savedArgs !== null) fn(...savedArgs);
}, delayMs);
});
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
}
var init_debounce = __esmMin((() => {
init_timer_management();
}));
var init_async = __esmMin((() => {
init_debounce();
}));
function createVideoVisibilityController(options) {
const { video: video$1, setMuted } = options;
let wasPlayingBeforeHidden = false;
let wasMutedBeforeHidden = null;
let didAutoMute = false;
const pauseVideo = () => {
if (typeof video$1.pause === "function") video$1.pause();
};
const playVideo = () => {
if (typeof video$1.play !== "function") return;
try {
const result = video$1.play();
if (result && typeof result.catch === "function") result.catch((err) => {
;
});
} catch (err) {
;
}
};
const applyMuted = (nextMuted) => {
if (typeof setMuted === "function") {
setMuted(video$1, nextMuted);
return;
}
video$1.muted = nextMuted;
};
return { handleEntry(entry) {
if (!entry.isIntersecting) try {
if (wasMutedBeforeHidden === null) {
wasPlayingBeforeHidden = !video$1.paused;
wasMutedBeforeHidden = video$1.muted;
didAutoMute = false;
}
if (!video$1.muted) {
applyMuted(true);
didAutoMute = true;
}
if (!video$1.paused) pauseVideo();
} catch (err) {
;
}
else try {
if (wasMutedBeforeHidden !== null) {
if (didAutoMute && video$1.muted === true && wasMutedBeforeHidden === false) applyMuted(false);
}
if (wasPlayingBeforeHidden) playVideo();
} catch (err) {
;
} finally {
wasPlayingBeforeHidden = false;
wasMutedBeforeHidden = null;
didAutoMute = false;
}
} };
}
function useVideoVisibility(options) {
const { container: container$2, video: video$1, isVideo, setMuted } = options;
createEffect(() => {
if (!isVideo()) return;
const containerEl = container$2();
const videoEl = video$1();
if (!containerEl || !videoEl) return;
const controller = createVideoVisibilityController(typeof setMuted === "function" ? {
video: videoEl,
setMuted
} : { video: videoEl });
const unsubscribeObserver = SharedObserver.observe(containerEl, controller.handleEntry, {
threshold: 0,
rootMargin: "0px"
});
onCleanup(() => {
unsubscribeObserver();
});
});
}
var init_useVideoVisibility = __esmMin((() => {
init_logging();
init_performance();
init_solid();
}));
function areVolumesEquivalent(a, b) {
if (!Number.isFinite(a) || !Number.isFinite(b)) return a === b;
return Math.abs(a - b) <= DEFAULT_VOLUME_EPSILON;
}
function nowMs() {
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
}
function createVideoVolumeChangeGuard(options = {}) {
const windowMs = options.windowMs ?? 500;
const MAX_EXPECTED_MARKS = 4;
let expectedMarks = [];
const pruneExpiredMarks = (now) => {
if (expectedMarks.length === 0) return;
expectedMarks = expectedMarks.filter((mark) => {
const age = now - mark.markedAt;
if (age < 0) return false;
return age <= windowMs;
});
};
return {
markProgrammaticChange(expected) {
const now = nowMs();
pruneExpiredMarks(now);
expectedMarks = [...expectedMarks, {
snapshot: expected,
markedAt: now
}];
if (expectedMarks.length > MAX_EXPECTED_MARKS) expectedMarks = expectedMarks.slice(-MAX_EXPECTED_MARKS);
},
shouldIgnoreChange(current) {
if (expectedMarks.length === 0) return false;
pruneExpiredMarks(nowMs());
if (expectedMarks.length === 0) return false;
return expectedMarks.some((mark) => areVolumesEquivalent(current.volume, mark.snapshot.volume) && current.muted === mark.snapshot.muted);
}
};
}
var DEFAULT_VOLUME_EPSILON;
var init_video_volume_change_guard = __esmMin((() => {
DEFAULT_VOLUME_EPSILON = .001;
}));
function cleanFilename(filename) {
if (!filename) return "Untitled";
const MAX_LENGTH = 40;
const TRUNCATION_MARKER = "...";
const truncateMiddlePreservingExtension = (value) => {
if (value.length <= MAX_LENGTH) return value;
const extension = value.match(/(\.[^./\\]{1,10})$/)?.[1] ?? "";
const base = extension ? value.slice(0, -extension.length) : value;
const available = MAX_LENGTH - extension.length - 3;
if (available <= 1) return value.slice(0, MAX_LENGTH);
const headLen = Math.max(1, Math.floor(available / 2));
const tailLen = Math.max(1, available - headLen);
return `${base.slice(0, headLen)}${TRUNCATION_MARKER}${base.slice(Math.max(0, base.length - tailLen))}${extension}`;
};
let cleaned = filename.replace(/^twitter_media_\d{8}T\d{6}_/, "").replace(/^\/media\//, "").replace(/^\.\//, "");
const lastSegment = cleaned.split(/[\\/]/).pop();
if (lastSegment) cleaned = lastSegment;
if (/^[\\/]+$/.test(cleaned)) cleaned = "";
cleaned = cleaned.trim();
if (!cleaned) return "Image";
return truncateMiddlePreservingExtension(cleaned);
}
function normalizeVideoVolumeSetting(value, fallback = 1) {
const candidate = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
if (!Number.isFinite(candidate)) return fallback;
return Math.min(1, Math.max(0, candidate));
}
function normalizeVideoMutedSetting(value, fallback = false) {
if (typeof value === "boolean") return value;
if (typeof value === "number") return value !== 0;
if (typeof value === "string") {
const normalized = value.trim().toLowerCase();
if (normalized === "true" || normalized === "1") return true;
if (normalized === "false" || normalized === "0") return false;
}
return fallback;
}
function isVideoMedia(media) {
const urlLowerCase = media.url.toLowerCase();
let parsedUrl = null;
try {
parsedUrl = new URL(media.url);
} catch {
parsedUrl = null;
}
const pathToCheck = parsedUrl ? parsedUrl.pathname.toLowerCase() : urlLowerCase.split(/[?#]/)[0] ?? "";
if (VIDEO_EXTENSIONS.some((ext) => pathToCheck.endsWith(ext))) return true;
if (media.filename) {
const filenameLowerCase = media.filename.toLowerCase();
if (VIDEO_EXTENSIONS.some((ext) => filenameLowerCase.endsWith(ext))) return true;
}
if (parsedUrl) return parsedUrl.hostname === "video.twimg.com";
return false;
}
var VIDEO_EXTENSIONS;
var init_VerticalImageItem_helpers = __esmMin((() => {
VIDEO_EXTENSIONS = [
".mp4",
".webm",
".mov",
".avi"
];
}));
var container, active, focused, imageWrapper, placeholder, loadingSpinner, image, video, loading, loaded, fitOriginal, fitWidth, fitHeight, fitContainer, errorIcon, errorText, error, VerticalImageItem_module_default;
var init_VerticalImageItem_module = __esmMin((() => {
container = "xeg_huYoSL";
active = "xeg_xm-1cY";
focused = "xeg_luqi-C";
imageWrapper = "xeg_8-c8dL";
placeholder = "xeg_lhkEW2";
loadingSpinner = "xeg_6YYDYR";
image = "xeg_FWlk5q";
video = "xeg_GUevPQ";
loading = "xeg_8Z3Su4";
loaded = "xeg_y9iPua";
fitOriginal = "xeg_yYtGJp";
fitWidth = "xeg_Uc0oUi";
fitHeight = "xeg_M9Z6MG";
fitContainer = "xeg_-Mlrhi";
errorIcon = "xeg_Wno7Ud";
errorText = "xeg_8-wisg";
error = "xeg_GswePL";
VerticalImageItem_module_default = {
container,
active,
focused,
imageWrapper,
placeholder,
loadingSpinner,
image,
video,
loading,
loaded,
fitOriginal,
fitWidth,
fitHeight,
fitContainer,
errorIcon,
errorText,
error
};
}));
function VerticalImageItem(props) {
const [local, rest] = splitProps(props, [
"media",
"index",
"isActive",
"isFocused",
"forceVisible",
"isVisible",
"onClick",
"onImageContextMenu",
"className",
"onMediaLoad",
"fitMode",
"style",
"data-testid",
"aria-label",
"aria-describedby",
"registerContainer",
"role",
"tabIndex",
"onFocus",
"onBlur",
"onKeyDown"
]);
const isFocused = createMemo(() => local.isFocused ?? false);
const forceVisible = createMemo(() => local.forceVisible ?? false);
const className$1 = createMemo(() => local.className ?? "");
const lang = getLanguageService();
const isVideo = createMemo(() => {
switch (local.media.type) {
case "video":
case "gif": return true;
case "image": return false;
default: return isVideoMedia(local.media);
}
});
const [isLoaded, setIsLoaded] = createSignal(false);
const [isError, setIsError] = createSignal(false);
const [isVisible, setIsVisible] = createSignal(forceVisible());
createEffect(() => {
setIsLoaded(false);
setIsError(false);
});
const [containerRef, setContainerRef] = createSignal(null);
const [imageRef, setImageRef] = createSignal(null);
const [videoRef, setVideoRef] = createSignal(null);
const resolvedDimensions = createMemo(() => resolveMediaDimensionsWithIntrinsicFlag(local.media));
const dimensions = createMemo(() => resolvedDimensions().dimensions);
const hasIntrinsicSize = createMemo(() => resolvedDimensions().hasIntrinsicSize);
const intrinsicSizingStyle = createMemo(() => {
return createIntrinsicSizingStyle(dimensions());
});
const mergedStyle = createMemo(() => {
const base = intrinsicSizingStyle();
const extra = local.style ?? {};
return {
...base,
...extra
};
});
const volumeChangeGuard = createVideoVolumeChangeGuard();
const applyMutedProgrammatically = (videoEl, muted) => {
volumeChangeGuard.markProgrammaticChange({
volume: videoEl.volume,
muted
});
videoEl.muted = muted;
};
const applyVolumeProgrammatically = (videoEl, volume) => {
volumeChangeGuard.markProgrammaticChange({
volume,
muted: videoEl.muted
});
videoEl.volume = volume;
};
useVideoVisibility({
container: containerRef,
video: videoRef,
isVideo,
setMuted: applyMutedProgrammatically
});
const [videoVolume, setVideoVolume] = createSignal(normalizeVideoVolumeSetting(getTypedSettingOr("gallery.videoVolume", 1), 1));
const [videoMuted, setVideoMuted] = createSignal(normalizeVideoMutedSetting(getTypedSettingOr("gallery.videoMuted", false), false));
let isApplyingVideoSettings = false;
createEffect(() => {
const video$1 = videoRef();
if (video$1 && isVideo()) {
isApplyingVideoSettings = true;
try {
untrack(() => {
const nextMuted = normalizeVideoMutedSetting(videoMuted(), false);
const nextVolume = normalizeVideoVolumeSetting(videoVolume(), 1);
if (nextMuted !== videoMuted()) setVideoMuted(nextMuted);
if (nextVolume !== videoVolume()) setVideoVolume(nextVolume);
applyMutedProgrammatically(video$1, nextMuted);
applyVolumeProgrammatically(video$1, nextVolume);
});
} finally {
isApplyingVideoSettings = false;
}
}
});
const debouncedSaveVolume = createDebounced((volume, muted) => {
setTypedSetting("gallery.videoVolume", volume);
setTypedSetting("gallery.videoMuted", muted);
}, 300);
onCleanup(() => {
debouncedSaveVolume.flush();
});
const handleVolumeChange = (event) => {
const video$1 = event.currentTarget;
const snapshot = {
volume: video$1.volume,
muted: video$1.muted
};
if (isApplyingVideoSettings || volumeChangeGuard.shouldIgnoreChange(snapshot)) return;
const newVolume = normalizeVideoVolumeSetting(snapshot.volume, 1);
const newMuted = normalizeVideoMutedSetting(snapshot.muted, false);
setVideoVolume(newVolume);
setVideoMuted(newMuted);
debouncedSaveVolume(newVolume, newMuted);
};
const preventDragStart = (event) => {
event.preventDefault();
};
const handleContainerClick = (event) => {
event.stopPropagation();
if (isVideo()) {
const video$1 = videoRef();
if (video$1) {
const target = event.target;
const targetInVideo = target instanceof Node && video$1.contains(target);
const path = typeof event.composedPath === "function" ? event.composedPath() : [];
const pathIncludesVideo = Array.isArray(path) && path.includes(video$1);
if (targetInVideo || pathIncludesVideo) return;
}
containerRef()?.focus?.({ preventScroll: true });
local.onClick();
return;
}
containerRef()?.focus?.({ preventScroll: true });
local.onClick();
};
const handleContainerKeyDown = (event) => {
if (typeof local.onKeyDown === "function") {
local.onKeyDown(event);
return;
}
if ((local.role ?? (isVideo() ? "group" : "button")) !== "button") return;
const key = event.key;
if (key === "Enter" || key === " " || key === "Spacebar") {
event.preventDefault();
event.stopPropagation();
local.onClick();
}
};
const handleMediaLoad = () => {
if (!isLoaded()) {
setIsLoaded(true);
setIsError(false);
local.onMediaLoad?.(local.media.id, local.index);
}
};
const handleMediaError = () => {
setIsError(true);
setIsLoaded(false);
};
const handleContextMenu = (event) => {
local.onImageContextMenu?.(event, local.media);
};
createEffect(() => {
if (forceVisible() && !isVisible()) setIsVisible(true);
});
createEffect(() => {
const container$2 = containerRef();
if (!container$2 || isVisible() || forceVisible()) return;
let unsubscribe = null;
const handleEntry = (entry) => {
if (entry.isIntersecting) {
setIsVisible(true);
unsubscribe?.();
unsubscribe = null;
}
};
unsubscribe = SharedObserver.observe(container$2, handleEntry, {
threshold: .1,
rootMargin: "100px"
});
onCleanup(() => {
unsubscribe?.();
unsubscribe = null;
});
});
createEffect(() => {
if (!isVisible() || isLoaded()) return;
if (isVideo()) {
const video$1 = videoRef();
if (video$1 && video$1.readyState >= 1) handleMediaLoad();
} else {
const image$1 = imageRef();
if (image$1?.complete) if (image$1.naturalWidth > 0) handleMediaLoad();
else handleMediaError();
}
});
const resolvedFitMode = createMemo(() => {
const value = local.fitMode;
if (typeof value === "function") return value() ?? "fitWidth";
return value ?? "fitWidth";
});
const fitModeClass = createMemo(() => FIT_MODE_CLASSES[resolvedFitMode()] ?? "");
const containerClasses = createMemo(() => cx("xeg-gallery", "xeg-gallery-item", "vertical-item", VerticalImageItem_module_default.container, local.isActive ? VerticalImageItem_module_default.active : void 0, isFocused() ? VerticalImageItem_module_default.focused : void 0, className$1()));
const assignContainerRef = (element) => {
setContainerRef(element);
local.registerContainer?.(element);
};
const defaultContainerRole = createMemo(() => isVideo() ? "group" : "button");
const resolvedContainerRole = createMemo(() => local.role ?? defaultContainerRole());
const defaultAriaLabel = createMemo(() => lang.translate("messages.gallery.mediaItemLabel", {
index: local.index + 1,
filename: cleanFilename(local.media.filename)
}));
return (() => {
var _el$ = _tmpl$$3();
use(assignContainerRef, _el$);
spread(_el$, mergeProps(rest, {
get ["class"]() {
return containerClasses();
},
"data-xeg-role": "gallery-item",
get ["data-index"]() {
return local.index;
},
get ["data-fit-mode"]() {
return resolvedFitMode();
},
get ["data-media-loaded"]() {
return isLoaded() ? "true" : "false";
},
get ["data-has-intrinsic-size"]() {
return hasIntrinsicSize() ? "true" : void 0;
},
"data-xeg-gallery": "true",
"data-xeg-gallery-type": "item",
"data-xeg-gallery-version": "2.0",
"data-xeg-component": "vertical-image-item",
"data-xeg-block-twitter": "true",
get style() {
return mergedStyle();
},
"onClick": handleContainerClick,
get onFocus() {
return local.onFocus;
},
get onBlur() {
return local.onBlur;
},
"onKeyDown": handleContainerKeyDown,
get ["aria-label"]() {
return local["aria-label"] || defaultAriaLabel();
},
get ["aria-describedby"]() {
return local["aria-describedby"];
},
get role() {
return resolvedContainerRole();
},
get tabIndex() {
return local.tabIndex ?? 0;
},
get ["data-testid"]() {
return local["data-testid"];
}
}), false, true);
insert(_el$, (() => {
var _c$ = memo(() => !!isVisible());
return () => _c$() && (() => {
var _el$2 = _tmpl$2$1();
insert(_el$2, (() => {
var _c$2 = memo(() => !!(!isLoaded() && !isError() && !isVideo()));
return () => _c$2() && (() => {
var _el$3 = _tmpl$3(), _el$4 = _el$3.firstChild;
createRenderEffect((_p$) => {
var _v$ = VerticalImageItem_module_default.placeholder, _v$2 = cx("xeg-spinner", VerticalImageItem_module_default.loadingSpinner);
_v$ !== _p$.e && className(_el$3, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$4, _p$.t = _v$2);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$3;
})();
})(), null);
insert(_el$2, (() => {
var _c$3 = memo(() => !!isVideo());
return () => _c$3() ? (() => {
var _el$5 = _tmpl$4();
_el$5.addEventListener("volumechange", handleVolumeChange);
_el$5.addEventListener("dragstart", preventDragStart);
_el$5.$$contextmenu = handleContextMenu;
_el$5.addEventListener("error", handleMediaError);
_el$5.addEventListener("canplay", handleMediaLoad);
_el$5.addEventListener("loadeddata", handleMediaLoad);
_el$5.addEventListener("loadedmetadata", handleMediaLoad);
use(setVideoRef, _el$5);
createRenderEffect((_p$) => {
var _v$3 = local.media.url, _v$4 = cx(VerticalImageItem_module_default.video, fitModeClass(), isLoaded() ? VerticalImageItem_module_default.loaded : VerticalImageItem_module_default.loading);
_v$3 !== _p$.e && setAttribute(_el$5, "src", _p$.e = _v$3);
_v$4 !== _p$.t && className(_el$5, _p$.t = _v$4);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$5;
})() : (() => {
var _el$6 = _tmpl$5();
_el$6.addEventListener("dragstart", preventDragStart);
_el$6.$$contextmenu = handleContextMenu;
_el$6.addEventListener("error", handleMediaError);
_el$6.addEventListener("load", handleMediaLoad);
use(setImageRef, _el$6);
createRenderEffect((_p$) => {
var _v$5 = local.media.url, _v$6 = cleanFilename(local.media.filename), _v$7 = cx(VerticalImageItem_module_default.image, fitModeClass(), isLoaded() ? VerticalImageItem_module_default.loaded : VerticalImageItem_module_default.loading);
_v$5 !== _p$.e && setAttribute(_el$6, "src", _p$.e = _v$5);
_v$6 !== _p$.t && setAttribute(_el$6, "alt", _p$.t = _v$6);
_v$7 !== _p$.a && className(_el$6, _p$.a = _v$7);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$6;
})();
})(), null);
insert(_el$2, (() => {
var _c$4 = memo(() => !!isError());
return () => _c$4() && (() => {
var _el$7 = _tmpl$6(), _el$8 = _el$7.firstChild, _el$9 = _el$8.nextSibling;
insert(_el$9, () => lang.translate("messages.gallery.failedToLoadImage", { type: isVideo() ? "video" : "image" }));
createRenderEffect((_p$) => {
var _v$8 = VerticalImageItem_module_default.error, _v$9 = VerticalImageItem_module_default.errorIcon, _v$0 = VerticalImageItem_module_default.errorText;
_v$8 !== _p$.e && className(_el$7, _p$.e = _v$8);
_v$9 !== _p$.t && className(_el$8, _p$.t = _v$9);
_v$0 !== _p$.a && className(_el$9, _p$.a = _v$0);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0
});
return _el$7;
})();
})(), null);
createRenderEffect(() => className(_el$2, VerticalImageItem_module_default.imageWrapper));
return _el$2;
})();
})());
return _el$;
})();
}
var _tmpl$$3, _tmpl$2$1, _tmpl$3, _tmpl$4, _tmpl$5, _tmpl$6, FIT_MODE_CLASSES;
var init_VerticalImageItem = __esmMin((() => {
init_web();
init_async();
init_service_accessors();
init_settings_access();
init_media_dimensions();
init_performance();
init_formatting();
init_solid();
init_useVideoVisibility();
init_video_volume_change_guard();
init_VerticalImageItem_helpers();
init_VerticalImageItem_module();
_tmpl$$3 = /* @__PURE__ */ template(`<div>`), _tmpl$2$1 = /* @__PURE__ */ template(`<div data-xeg-role=media-wrapper>`), _tmpl$3 = /* @__PURE__ */ template(`<div><div>`), _tmpl$4 = /* @__PURE__ */ template(`<video controls>`), _tmpl$5 = /* @__PURE__ */ template(`<img loading=lazy decoding=async>`, true, false, false), _tmpl$6 = /* @__PURE__ */ template(`<div><span>⚠️</span><span>`);
FIT_MODE_CLASSES = {
original: VerticalImageItem_module_default.fitOriginal,
fitHeight: VerticalImageItem_module_default.fitHeight,
fitWidth: VerticalImageItem_module_default.fitWidth,
fitContainer: VerticalImageItem_module_default.fitContainer
};
delegateEvents(["contextmenu"]);
}));
function VerticalGalleryViewCore({ onClose, className: className$1 = "", onPrevious, onNext, onDownloadCurrent, onDownloadAll }) {
const mediaItems = createMemo(() => galleryState.value.mediaItems);
const currentIndex$1 = createMemo(() => galleryState.value.currentIndex);
const isDownloading = createMemo(() => isDownloadUiBusy({ downloadProcessing: downloadState.value.isProcessing }));
const [containerEl, setContainerEl] = createSignal(null);
const [toolbarWrapperEl, setToolbarWrapperEl] = createSignal(null);
const [itemsContainerEl, setItemsContainerEl] = createSignal(null);
const isVisible = createMemo(() => mediaItems().length > 0);
const activeMedia = createMemo(() => {
return mediaItems()[currentIndex$1()] ?? null;
});
const preloadIndices = createMemo(() => {
const count = getTypedSettingOr("gallery.preloadCount", 0);
return computePreloadIndices(currentIndex$1(), mediaItems().length, count);
});
const { scroll, navigation, focus, toolbar } = useVerticalGallery({
isVisible,
currentIndex: currentIndex$1,
mediaItemsCount: () => mediaItems().length,
containerEl,
toolbarWrapperEl,
itemsContainerEl,
onClose
});
createEffect(() => {
if (!isVisible() || navigation.lastNavigationTrigger()) return;
navigateToItem(currentIndex$1(), "click");
});
const getInitialFitMode = () => {
return getTypedSettingOr("gallery.imageFitMode", "fitWidth");
};
const [imageFitMode, setImageFitMode] = createSignal(getInitialFitMode());
const persistFitMode = (mode) => setTypedSetting("gallery.imageFitMode", mode).catch((error$1) => {
;
});
const applyFitMode = (mode, event) => {
safeEventPrevent(event);
setImageFitMode(mode);
persistFitMode(mode);
scroll.scrollToCurrentItem();
navigateToItem(currentIndex$1(), "click");
};
const handleDownloadCurrent = () => onDownloadCurrent?.();
const handleDownloadAll = () => onDownloadAll?.();
const handleFitOriginal = (event) => applyFitMode("original", event);
const handleFitWidth = (event) => applyFitMode("fitWidth", event);
const handleFitHeight = (event) => applyFitMode("fitHeight", event);
const handleFitContainer = (event) => applyFitMode("fitContainer", event);
const handlePrevious = () => {
const current = currentIndex$1();
if (current > 0) navigateToItem(current - 1, "click");
};
const handleNext = () => {
const current = currentIndex$1();
if (current < mediaItems().length - 1) navigateToItem(current + 1, "click");
};
const handleBackgroundClick = (event) => {
const target = event.target;
if (!(target instanceof Element)) return;
if (target.closest("[data-role=\"toolbar\"], [data-role=\"toolbar-hover-zone\"], [data-gallery-element], [data-xeg-role=\"gallery-item\"], [data-xeg-role=\"scroll-spacer\"]")) return;
onClose?.();
};
const handleMediaItemClick = (index) => {
const items = mediaItems();
const current = currentIndex$1();
if (index >= 0 && index < items.length && index !== current) navigateToItem(index, "click");
};
createEffect(() => {
const container$2 = containerEl();
if (!container$2) return;
const handleContainerWheel = (event) => {
const itemsContainer$1 = itemsContainerEl();
if (!itemsContainer$1) return;
const target = event.target;
if (!(target instanceof Element)) return;
if (itemsContainer$1.contains(target)) return;
event.preventDefault();
event.stopPropagation();
itemsContainer$1.scrollTop += event.deltaY;
};
const bus = getEventBus();
const listener = (event) => {
handleContainerWheel(event);
};
const id = bus.addDOMListener(container$2, "wheel", listener, {
passive: false,
context: "gallery:wheel:container-redirect"
});
onCleanup(() => bus.remove(id));
});
if (!isVisible()) {
const languageService = getLanguageService();
const emptyTitle = languageService.translate("messages.gallery.emptyTitle");
const emptyDesc = languageService.translate("messages.gallery.emptyDescription");
return (() => {
var _el$ = _tmpl$$2(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.nextSibling;
insert(_el$3, emptyTitle);
insert(_el$4, emptyDesc);
createRenderEffect((_p$) => {
var _v$ = cx(VerticalGalleryView_module_default.container, VerticalGalleryView_module_default.empty, className$1), _v$2 = VerticalGalleryView_module_default.emptyMessage;
_v$ !== _p$.e && className(_el$, _p$.e = _v$);
_v$2 !== _p$.t && className(_el$2, _p$.t = _v$2);
return _p$;
}, {
e: void 0,
t: void 0
});
return _el$;
})();
}
return (() => {
var _el$5 = _tmpl$2(), _el$6 = _el$5.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.firstChild;
_el$5.$$click = handleBackgroundClick;
use((el) => setContainerEl(el ?? null), _el$5);
use((el) => setToolbarWrapperEl(el ?? null), _el$7);
insert(_el$7, createComponent(Toolbar, {
currentIndex: currentIndex$1,
get focusedIndex() {
return focus.focusedIndex;
},
totalCount: () => mediaItems().length,
isDownloading,
get currentFitMode() {
return imageFitMode();
},
tweetText: () => activeMedia()?.tweetText,
tweetTextHTML: () => activeMedia()?.tweetTextHTML,
get className() {
return VerticalGalleryView_module_default.toolbar || "";
},
handlers: {
navigation: {
onPrevious: onPrevious || handlePrevious,
onNext: onNext || handleNext
},
download: {
onDownloadCurrent: handleDownloadCurrent,
onDownloadAll: handleDownloadAll
},
fitMode: {
onFitOriginal: handleFitOriginal,
onFitWidth: handleFitWidth,
onFitHeight: handleFitHeight,
onFitContainer: handleFitContainer
},
lifecycle: {
onClose: onClose || (() => {}),
onOpenSettings: () => void 0
}
}
}));
use((el) => setItemsContainerEl(el ?? null), _el$8);
insert(_el$8, createComponent(For, {
get each() {
return mediaItems();
},
children: (item, index) => {
const actualIndex = index();
return createComponent(VerticalImageItem, {
media: item,
index: actualIndex,
get isActive() {
return actualIndex === currentIndex$1();
},
get isFocused() {
return actualIndex === focus.focusedIndex();
},
forceVisible: preloadIndices().includes(actualIndex),
fitMode: imageFitMode,
onClick: () => handleMediaItemClick(actualIndex),
get className() {
return cx(VerticalGalleryView_module_default.galleryItem, actualIndex === currentIndex$1() && VerticalGalleryView_module_default.itemActive);
},
registerContainer: (element) => focus.registerItem(actualIndex, element),
onFocus: () => focus.handleItemFocus(actualIndex)
});
}
}), _el$9);
createRenderEffect((_p$) => {
var _v$3 = cx(VerticalGalleryView_module_default.container, toolbar.isInitialToolbarVisible() && VerticalGalleryView_module_default.initialToolbarVisible, scroll.isScrolling() && VerticalGalleryView_module_default.isScrolling, className$1), _v$4 = VerticalGalleryView_module_default.toolbarHoverZone, _v$5 = VerticalGalleryView_module_default.toolbarWrapper, _v$6 = VerticalGalleryView_module_default.itemsContainer, _v$7 = VerticalGalleryView_module_default.scrollSpacer;
_v$3 !== _p$.e && className(_el$5, _p$.e = _v$3);
_v$4 !== _p$.t && className(_el$6, _p$.t = _v$4);
_v$5 !== _p$.a && className(_el$7, _p$.a = _v$5);
_v$6 !== _p$.o && className(_el$8, _p$.o = _v$6);
_v$7 !== _p$.i && className(_el$9, _p$.i = _v$7);
return _p$;
}, {
e: void 0,
t: void 0,
a: void 0,
o: void 0,
i: void 0
});
return _el$5;
})();
}
var _tmpl$$2, _tmpl$2, VerticalGalleryView;
var init_VerticalGalleryView = __esmMin((() => {
init_web();
init_Toolbar();
init_service_accessors();
init_settings_access();
init_events();
init_logging();
init_download_signals();
init_gallery_signals();
init_performance();
init_formatting();
init_solid();
init_useVerticalGallery();
init_VerticalGalleryView_module();
init_VerticalImageItem();
_tmpl$$2 = /* @__PURE__ */ template(`<div><div><h3></h3><p>`), _tmpl$2 = /* @__PURE__ */ template(`<div data-xeg-gallery=true data-xeg-role=gallery><div data-role=toolbar-hover-zone></div><div data-role=toolbar></div><div data-xeg-role=items-container data-xeg-role-compat=items-list><div aria-hidden=true data-xeg-role=scroll-spacer>`);
VerticalGalleryView = VerticalGalleryViewCore;
delegateEvents(["click"]);
}));
function mountGallery(container$2, element) {
const host = container$2;
host[DISPOSE_SYMBOL]?.();
host[DISPOSE_SYMBOL] = render(typeof element === "function" ? element : () => element ?? null, host);
return container$2;
}
function unmountGallery(container$2) {
const host = container$2;
host[DISPOSE_SYMBOL]?.();
delete host[DISPOSE_SYMBOL];
container$2.replaceChildren();
}
function GalleryContainer({ children: children$1, onClose, className: className$1, registerEscapeListener }) {
const classes = cx("xeg-gallery-overlay", "xeg-gallery-container", className$1);
const hasCloseHandler = typeof onClose === "function";
const escapeListener = (event) => {
if (!hasCloseHandler) return;
const keyboardEvent = event;
if (keyboardEvent.key === "Escape") {
keyboardEvent.preventDefault();
keyboardEvent.stopPropagation();
onClose?.();
}
};
if (hasCloseHandler && registerEscapeListener && typeof window !== "undefined") {
const captureWindow = window;
captureWindow[ESCAPE_LISTENER_STORAGE_KEY] = escapeListener;
registerEscapeListener(escapeListener);
}
createEffect(() => {
if (!hasCloseHandler) return;
const eventManager = EventManager.getInstance();
const listenerId = eventManager.addListener(document, "keydown", escapeListener);
onCleanup(() => {
if (listenerId) eventManager.removeListener(listenerId);
});
});
return (() => {
var _el$ = _tmpl$$1();
className(_el$, classes);
insert(_el$, children$1);
return _el$;
})();
}
var _tmpl$$1, DISPOSE_SYMBOL, ESCAPE_LISTENER_STORAGE_KEY;
var init_GalleryContainer = __esmMin((() => {
init_web();
init_event_manager();
init_formatting();
init_solid();
_tmpl$$1 = /* @__PURE__ */ template(`<div data-xeg-gallery-container>`);
DISPOSE_SYMBOL = Symbol("xeg-gallery-container-dispose");
ESCAPE_LISTENER_STORAGE_KEY = "__xegCapturedEscapeListener";
}));
var init_isolation = __esmMin((() => {
init_GalleryContainer();
}));
var NotificationService;
var init_notification_service = __esmMin((() => {
init_userscript();
init_logging();
NotificationService = class NotificationService {
static singleton = createSingleton(() => new NotificationService());
get userscript() {
return getUserscriptSafe();
}
constructor() {}
static getInstance() {
return NotificationService.singleton.get();
}
async show(options) {
this.userscript.notification({
title: options.title,
text: options.text,
image: options.image,
timeout: options.timeout,
onclick: options.onclick
});
;
}
async error(title, text, timeout = 5e3) {
await this.show({
title,
text: text ?? "An error occurred.",
timeout
});
}
};
}));
function stringifyError(error$1) {
if (error$1 instanceof Error && error$1.message) return error$1.message;
try {
return String(error$1);
} catch {
return "Unknown error";
}
}
function translateError(error$1) {
try {
const languageService = getLanguageService();
return {
title: languageService.translate("messages.errorBoundary.title"),
body: languageService.translate("messages.errorBoundary.body", { error: stringifyError(error$1) })
};
} catch {
return {
title: "Unexpected error",
body: stringifyError(error$1)
};
}
}
function ErrorBoundary(props) {
let lastReportedError;
const [caughtError, setCaughtError] = createSignal(void 0);
const [boundaryMounted, setBoundaryMounted] = createSignal(true);
const notifyError = (error$1) => {
if (lastReportedError === error$1) return;
lastReportedError = error$1;
try {
const copy = translateError(error$1);
NotificationService.getInstance().error(copy.title, copy.body);
} catch {}
};
const handleRetry = () => {
lastReportedError = void 0;
setCaughtError(void 0);
setBoundaryMounted(false);
queueMicrotask(() => setBoundaryMounted(true));
};
const renderFallback = (error$1) => {
let title = "Unexpected error";
let body$1 = stringifyError(error$1);
try {
const copy = translateError(error$1);
title = copy.title;
body$1 = copy.body;
} catch {}
return (() => {
var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.nextSibling;
insert(_el$2, title);
insert(_el$3, body$1);
_el$4.$$click = handleRetry;
return _el$;
})();
};
return [createComponent(Show, {
get when() {
return boundaryMounted();
},
get children() {
return createComponent(ErrorBoundary$1, {
fallback: (boundaryError) => {
notifyError(boundaryError);
setCaughtError(boundaryError);
return null;
},
get children() {
return props.children;
}
});
}
}), createComponent(Show, {
get when() {
return caughtError();
},
children: (error$1) => renderFallback(error$1())
})];
}
var _tmpl$;
var init_ErrorBoundary = __esmMin((() => {
init_web();
init_service_accessors();
init_notification_service();
init_solid();
_tmpl$ = /* @__PURE__ */ template(`<div role=alert data-xeg-error-boundary aria-live=polite><p class=xeg-error-boundary__title></p><p class=xeg-error-boundary__body></p><button type=button class=xeg-error-boundary__action>Retry`);
delegateEvents(["click"]);
}));
function resolveRoot(root) {
if (root && typeof root.querySelectorAll === "function") return root;
return typeof document !== "undefined" && typeof document.querySelectorAll === "function" ? document : null;
}
function isVideoPlaying(video$1) {
try {
return !video$1.paused && !video$1.ended;
} catch {
return false;
}
}
function shouldPauseVideo(video$1, force = false) {
return video$1 instanceof HTMLVideoElement && !isGalleryInternalElement(video$1) && video$1.isConnected && (force || isVideoPlaying(video$1));
}
function tryPauseVideo(video$1) {
try {
video$1.pause?.();
return true;
} catch (error$1) {
;
return false;
}
}
function pauseActiveTwitterVideos(options = {}) {
const root = resolveRoot(options.root ?? null);
if (!root) return ZERO_RESULT;
const videos = Array.from(root.querySelectorAll("video"));
const inspectedCount = videos.length;
if (inspectedCount === 0) return ZERO_RESULT;
let pausedCount = 0;
let totalCandidates = 0;
for (const video$1 of videos) {
if (!shouldPauseVideo(video$1, options.force)) continue;
totalCandidates += 1;
if (tryPauseVideo(video$1)) pausedCount += 1;
}
const result = {
pausedCount,
totalCandidates,
skippedCount: inspectedCount - pausedCount
};
if (result.pausedCount > 0) ;
return result;
}
var ZERO_RESULT;
var init_twitter_video_pauser = __esmMin((() => {
init_utils();
init_logging();
ZERO_RESULT = Object.freeze({
pausedCount: 0,
totalCandidates: 0,
skippedCount: 0
});
}));
function findTweetContainer(element) {
if (!element) return null;
for (const selector of STABLE_TWEET_CONTAINERS_SELECTORS) try {
const container$2 = element.closest(selector);
if (container$2 instanceof HTMLElement) return container$2;
} catch {}
return null;
}
function resolvePauseContext(request) {
if (request.root !== void 0) return {
root: request.root ?? null,
scope: "custom"
};
if (findTweetContainer(request.sourceElement)) return {
root: null,
scope: "tweet"
};
return {
root: null,
scope: "document"
};
}
function isVideoTriggerElement(element) {
if (!element) return false;
if (element.tagName === "VIDEO") return true;
for (const selector of VIDEO_TRIGGER_SCOPES) try {
if (element.matches(selector) || element.closest(selector)) return true;
} catch {}
return false;
}
function isImageTriggerElement(element) {
if (!element) return false;
if (element.tagName === "IMG") return true;
for (const selector of IMAGE_TRIGGER_SCOPES) try {
if (element.matches(selector) || element.closest(selector)) return true;
} catch {}
return false;
}
function inferAmbientVideoTrigger(element) {
if (isVideoTriggerElement(element)) return "video-click";
if (isImageTriggerElement(element)) return "image-click";
return "unknown";
}
function pauseAmbientVideosForGallery(request = {}) {
const trigger = request.trigger ?? inferAmbientVideoTrigger(request.sourceElement);
const force = request.force ?? true;
const reason = request.reason ?? trigger;
const { root, scope } = resolvePauseContext(request);
let result;
try {
result = pauseActiveTwitterVideos({
root,
force
});
} catch (error$1) {
;
return {
...PAUSE_RESULT_DEFAULT,
trigger,
forced: force,
reason,
scope
};
}
if (result.totalCandidates > 0 || result.pausedCount > 0) ;
return {
...result,
trigger,
forced: force,
reason,
scope
};
}
var VIDEO_TRIGGER_SCOPES, IMAGE_TRIGGER_SCOPES, PAUSE_RESULT_DEFAULT;
var init_ambient_video_coordinator = __esmMin((() => {
init_selectors();
init_logging();
init_twitter_video_pauser();
VIDEO_TRIGGER_SCOPES = new Set([VIDEO_PLAYER_SELECTOR, ...STABLE_VIDEO_CONTAINERS_SELECTORS]);
IMAGE_TRIGGER_SCOPES = new Set([TWEET_PHOTO_SELECTOR, ...STABLE_IMAGE_CONTAINERS_SELECTORS]);
PAUSE_RESULT_DEFAULT = Object.freeze({
pausedCount: 0,
totalCandidates: 0,
skippedCount: 0
});
}));
var GalleryRenderer_exports = /* @__PURE__ */ __export({ GalleryRenderer: () => GalleryRenderer }, 1);
var GalleryRenderer;
var init_GalleryRenderer = __esmMin((() => {
init_web();
init_constants$1();
init_VerticalGalleryView();
init_isolation();
init_ErrorBoundary();
init_service_accessors();
init_logging();
init_download_signals();
init_gallery_signals();
init_ui_state();
init_ambient_video_coordinator();
init_solid();
GalleryRenderer = class {
container = null;
isMounting = false;
stateUnsubscribe = null;
onCloseCallback;
constructor() {
this.setupStateSubscription();
}
setOnCloseCallback(onClose) {
this.onCloseCallback = onClose;
}
setupStateSubscription() {
this.stateUnsubscribe = gallerySignals.isOpen.subscribe((isOpen) => {
if (isOpen && !this.container) this.renderGallery();
else if (!isOpen && this.container) this.cleanupGallery();
});
}
renderGallery() {
if (this.isMounting || this.container) return;
const { isOpen, mediaItems } = gallerySignals;
if (!isOpen.value || mediaItems.value.length === 0) return;
this.isMounting = true;
;
try {
this.createContainer();
this.renderComponent();
;
} catch (error$1) {
logger.error("[GalleryRenderer] Rendering failed:", error$1);
this.cleanupContainer();
setError(getErrorMessage(error$1) || "Gallery rendering failed");
} finally {
this.isMounting = false;
}
}
createContainer() {
this.cleanupContainer();
this.container = document.createElement("div");
this.container.className = CSS.CLASSES.RENDERER;
this.container.setAttribute("data-renderer", "gallery");
document.body.appendChild(this.container);
}
renderComponent() {
if (!this.container) return;
const themeService = getThemeService();
const languageService = getLanguageService();
const handleClose = () => {
closeGallery();
this.onCloseCallback?.();
};
const handleDownload = (type) => this.handleDownload(type);
const Root = () => {
const [currentTheme, setCurrentTheme] = createSignal(themeService.getCurrentTheme());
const [currentLanguage, setCurrentLanguage] = createSignal(languageService.getCurrentLanguage());
const unbindTheme = themeService.onThemeChange((_, setting$1) => setCurrentTheme(setting$1));
const unbindLanguage = languageService.onLanguageChange((lang) => setCurrentLanguage(lang));
onCleanup(() => {
unbindTheme();
unbindLanguage();
});
return createComponent(GalleryContainer, {
onClose: handleClose,
get className() {
return `${CSS.CLASSES.RENDERER} ${CSS.CLASSES.ROOT} xeg-theme-scope`;
},
get ["data-theme"]() {
return currentTheme();
},
get ["data-language"]() {
return currentLanguage();
},
get children() {
return createComponent(ErrorBoundary, { get children() {
return createComponent(VerticalGalleryView, {
onClose: handleClose,
onPrevious: () => navigatePrevious("button"),
onNext: () => navigateNext("button"),
onDownloadCurrent: () => handleDownload("current"),
onDownloadAll: () => handleDownload("all"),
get className() {
return CSS.CLASSES.VERTICAL_VIEW;
}
});
} });
}
});
};
mountGallery(this.container, () => createComponent(Root, {}));
;
}
async handleDownload(type) {
;
if (isDownloadLocked()) return;
const releaseLock = acquireDownloadLock();
try {
const mediaItems = gallerySignals.mediaItems.value;
const downloadService = await this.getDownloadService();
if (type === "current") {
const currentMedia = mediaItems[gallerySignals.currentIndex.value];
if (currentMedia) {
const result = await downloadService.downloadSingle(currentMedia);
if (!result.success) setError(result.error || "Download failed.");
}
} else {
const result = await downloadService.downloadBulk([...mediaItems]);
if (!result.success) setError(result.error || "Download failed.");
}
} catch (error$1) {
logger.error(`[GalleryRenderer] ${type} download failed:`, error$1);
setError(getErrorMessage(error$1) || "Download failed.");
} finally {
releaseLock();
}
}
async getDownloadService() {
return getDownloadOrchestrator();
}
cleanupGallery() {
;
this.isMounting = false;
this.cleanupContainer();
}
cleanupContainer() {
if (this.container) {
const container$2 = this.container;
try {
unmountGallery(container$2);
} catch (error$1) {
;
}
try {
container$2.remove();
} catch (error$1) {
;
} finally {
this.container = null;
}
}
}
async render(mediaItems, renderOptions) {
const pauseContext = renderOptions?.pauseContext ?? { reason: "programmatic" };
try {
pauseAmbientVideosForGallery(pauseContext);
} catch (error$1) {
;
}
openGallery(mediaItems, renderOptions?.startIndex ?? 0);
}
close() {
if (!gallerySignals.isOpen.value) return;
closeGallery();
this.onCloseCallback?.();
}
isRendering() {
return Boolean(this.container && gallerySignals.isOpen.value);
}
destroy() {
;
this.stateUnsubscribe?.();
this.cleanupGallery();
}
};
}));
function pruneWithTemplate(input, template$1) {
if (!isRecord(input)) return {};
const out = {};
for (const key of Object.keys(template$1)) {
const tplVal = template$1[key];
const inVal = input[key];
if (inVal === void 0) continue;
if (isRecord(tplVal) && !Array.isArray(tplVal)) out[key] = pruneWithTemplate(inVal, tplVal);
else out[key] = inVal;
}
return out;
}
function fillWithDefaults(settings, nowMs$1) {
const pruned = pruneWithTemplate(settings, DEFAULT_SETTINGS);
const categories = {
gallery: DEFAULT_SETTINGS.gallery,
toolbar: DEFAULT_SETTINGS.toolbar,
download: DEFAULT_SETTINGS.download,
tokens: DEFAULT_SETTINGS.tokens,
accessibility: DEFAULT_SETTINGS.accessibility,
features: DEFAULT_SETTINGS.features
};
const merged = {
...DEFAULT_SETTINGS,
...pruned
};
for (const [key, defaults] of Object.entries(categories)) merged[key] = {
...defaults,
...pruned[key] ?? {}
};
return {
...merged,
version: DEFAULT_SETTINGS.version,
lastModified: nowMs$1
};
}
function migrateSettings(input, nowMs$1) {
let working = { ...input };
const mig = migrations[input.version];
if (typeof mig === "function") try {
working = mig(working);
} catch {}
return fillWithDefaults(working, nowMs$1);
}
var migrations;
var init_settings_migration = __esmMin((() => {
init_constants$1();
migrations = { "1.0.0": (input) => {
const next = { ...input };
next.gallery = {
...next.gallery,
enableKeyboardNav: true
};
return next;
} };
}));
function planSettingsPersist(input) {
return [{
type: "STORE_SET",
key: input.key,
value: {
...input.settings,
__schemaHash: input.schemaHash
}
}];
}
function stableNormalizeForHash(input, seen) {
if (input === null) return null;
const type = typeof input;
if (type === "string" || type === "number" || type === "boolean") return input;
if (type !== "object") return;
if (Array.isArray(input)) return input.map((value) => stableNormalizeForHash(value, seen));
const obj = input;
if (seen.has(obj)) return "[Circular]";
seen.add(obj);
const out = {};
for (const key of Object.keys(obj).sort()) {
if (key === "__schemaHash") continue;
out[key] = stableNormalizeForHash(obj[key], seen);
}
return out;
}
function stableStringifyForHash(input) {
const normalized = stableNormalizeForHash(input, /* @__PURE__ */ new WeakSet());
return JSON.stringify(normalized);
}
function computeHashString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash |= 0;
}
return Math.abs(hash).toString(16);
}
function computeSettingsSchemaHashFrom(obj) {
return computeHashString(stableStringifyForHash(obj && typeof obj === "object" ? obj : {}));
}
function computeCurrentSettingsSchemaHash() {
return computeSettingsSchemaHashFrom(DEFAULT_SETTINGS);
}
var init_settings_schema = __esmMin((() => {
init_constants$1();
}));
var PersistentSettingsRepository;
var init_settings_repository = __esmMin((() => {
init_constants$1();
init_logging();
init_persistent_storage();
init_settings_migration();
init_settings_schema();
PersistentSettingsRepository = class {
storage = getPersistentStorage();
schemaHash = computeCurrentSettingsSchemaHash();
async load() {
try {
const stored = await this.storage.getJson(APP_SETTINGS_STORAGE_KEY);
if (!stored) {
const defaults = createDefaultSettings();
try {
await this.persist(defaults);
} catch (persistError) {
;
}
return cloneDeep(defaults);
}
const migrated = migrateSettings(stored, Date.now());
if (stored.__schemaHash !== this.schemaHash) try {
await this.persist(migrated);
} catch (persistError) {
;
}
return cloneDeep(migrated);
} catch (error$1) {
;
const defaults = createDefaultSettings();
try {
await this.persist(defaults);
} catch (persistError) {
;
}
return cloneDeep(defaults);
}
}
async save(settings) {
try {
await this.persist(settings);
} catch (error$1) {
logger.error("[SettingsRepository] save failed", error$1);
throw error$1;
}
}
async persist(settings) {
const cmds = planSettingsPersist({
key: APP_SETTINGS_STORAGE_KEY,
settings,
schemaHash: this.schemaHash
});
await this.executePersistCommands(cmds);
}
async executePersistCommands(cmds) {
for (const cmd of cmds) switch (cmd.type) {
case "STORE_SET":
await this.storage.setJson(cmd.key, cmd.value);
break;
case "LOG":
switch (cmd.level) {
case "debug":
;
break;
case "info":
;
break;
case "warn":
;
break;
case "error":
logger.error(cmd.message, cmd.context);
break;
default:
;
break;
}
break;
default: break;
}
}
};
}));
function isSafeKey(key) {
return !FORBIDDEN_KEYS.has(key);
}
function resolveNestedPath(source, path) {
if (!path) return void 0;
const keys = path.split(".");
let current = source;
for (const key of keys) {
if (!isSafeKey(key)) return;
if (current === null || typeof current !== "object") return;
current = current[key];
}
return current;
}
function assignNestedPath(target, path, value, options) {
if (!target || typeof target !== "object" || !path) return false;
const keys = path.split(".");
for (const key of keys) if (!isSafeKey(key)) return false;
const createIntermediate = options?.createIntermediate !== false;
let current = target;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!key || !isSafeKey(key)) continue;
const descriptor = Object.getOwnPropertyDescriptor(current, key);
const hasOwnKey = descriptor !== void 0;
const existingValue = hasOwnKey ? descriptor.value : void 0;
if (!hasOwnKey || typeof existingValue !== "object" || existingValue === null) {
if (!createIntermediate) return false;
const newObj = Object.create(null);
Object.defineProperty(current, key, {
value: newObj,
writable: true,
enumerable: true,
configurable: true
});
current = newObj;
} else current = existingValue;
}
const lastKey = keys[keys.length - 1];
if (lastKey && isSafeKey(lastKey)) {
Object.defineProperty(current, lastKey, {
value,
writable: true,
enumerable: true,
configurable: true
});
return true;
}
return false;
}
var FORBIDDEN_KEYS;
var init_object_path = __esmMin((() => {
FORBIDDEN_KEYS = new Set([
"__proto__",
"constructor",
"prototype"
]);
}));
var settings_service_exports = /* @__PURE__ */ __export({ SettingsService: () => SettingsService }, 1);
function normalizeFeatureFlags(features) {
return Object.keys(FEATURE_DEFAULTS).reduce((acc, key) => {
const candidate = features?.[key];
acc[key] = typeof candidate === "boolean" ? candidate : FEATURE_DEFAULTS[key];
return acc;
}, {});
}
var FEATURE_DEFAULTS, SettingsService;
var init_settings_service = __esmMin((() => {
init_constants$1();
init_settings_migration();
init_settings_repository();
init_logging();
init_object_path();
FEATURE_DEFAULTS = Object.freeze({ ...DEFAULT_SETTINGS.features });
SettingsService = class SettingsService {
lifecycle;
static singleton = createSingleton(() => new SettingsService());
static getInstance() {
return SettingsService.singleton.get();
}
settings = createDefaultSettings();
featureMap = normalizeFeatureFlags(this.settings.features);
listeners = /* @__PURE__ */ new Set();
constructor(repository = new PersistentSettingsRepository()) {
this.repository = repository;
this.lifecycle = createLifecycle("SettingsService", {
onInitialize: () => this.onInitialize(),
onDestroy: () => this.onDestroy()
});
}
async initialize() {
return this.lifecycle.initialize();
}
destroy() {
this.lifecycle.destroy();
}
isInitialized() {
return this.lifecycle.isInitialized();
}
async onInitialize() {
this.settings = await this.repository.load();
this.refreshFeatureMap();
}
onDestroy() {
this.listeners.clear();
this.repository.save(this.settings).catch((error$1) => {
;
});
}
getAllSettings() {
this.assertInitialized();
return cloneDeep(this.settings);
}
get(key) {
this.assertInitialized();
const value = resolveNestedPath(this.settings, key);
return value === void 0 ? this.getDefaultValue(key) : value;
}
async set(key, value) {
this.assertInitialized();
if (!this.isValid(key, value)) throw new Error(`Invalid setting value for ${key}`);
const oldValue = this.get(key);
assignNestedPath(this.settings, key, value);
this.settings.lastModified = Date.now();
this.refreshFeatureMap();
this.notifyListeners({
key,
oldValue,
newValue: value,
timestamp: Date.now(),
status: "success"
});
await this.persist();
}
async updateBatch(updates) {
this.assertInitialized();
const entries = Object.entries(updates);
for (const [key, value] of entries) if (!this.isValid(key, value)) throw new Error(`Invalid setting value for ${key}`);
const previous = cloneDeep(this.settings);
const timestamp = Date.now();
for (const [key, value] of entries) assignNestedPath(this.settings, key, value);
this.settings.lastModified = timestamp;
this.refreshFeatureMap();
for (const [key, value] of entries) {
const oldValue = resolveNestedPath(previous, key);
this.notifyListeners({
key,
oldValue,
newValue: value,
timestamp,
status: "success"
});
}
await this.persist();
}
async resetToDefaults(category) {
this.assertInitialized();
const previous = this.getAllSettings();
if (!category) this.settings = createDefaultSettings();
else if (category in DEFAULT_SETTINGS) {
const defaultValue = DEFAULT_SETTINGS[category];
if (defaultValue !== void 0) Object.assign(this.settings, { [category]: cloneDeep(defaultValue) });
}
this.settings.lastModified = Date.now();
this.refreshFeatureMap();
this.notifyListeners({
key: category ?? "all",
oldValue: previous,
newValue: this.getAllSettings(),
timestamp: Date.now(),
status: "success"
});
await this.persist();
}
subscribe(listener) {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
exportSettings() {
this.assertInitialized();
return JSON.stringify(this.settings, null, 2);
}
async importSettings(jsonString) {
this.assertInitialized();
try {
const imported = JSON.parse(jsonString);
if (!imported || typeof imported !== "object") throw new Error("Invalid settings");
const previous = this.getAllSettings();
const nowMs$1 = Date.now();
this.settings = migrateSettings(imported, nowMs$1);
this.settings.lastModified = nowMs$1;
this.refreshFeatureMap();
this.notifyListeners({
key: "all",
oldValue: previous,
newValue: this.getAllSettings(),
timestamp: nowMs$1,
status: "success"
});
await this.persist();
} catch (error$1) {
logger.error("Settings import failed:", error$1);
throw error$1;
}
}
getFeatureMap() {
this.assertInitialized();
return Object.freeze({ ...this.featureMap });
}
refreshFeatureMap() {
this.featureMap = normalizeFeatureFlags(this.settings.features);
}
async persist() {
await this.repository.save(this.settings);
}
isValid(key, value) {
const def = this.getDefaultValue(key);
if (def === void 0) return true;
const type = Array.isArray(def) ? "array" : typeof def;
if (type === "array") return Array.isArray(value);
if (type === "object") return typeof value === "object" && value !== null;
return typeof value === type;
}
getDefaultValue(key) {
return resolveNestedPath(DEFAULT_SETTINGS, key);
}
notifyListeners(event) {
this.listeners.forEach((listener) => {
try {
listener(event);
} catch (error$1) {
logger.error("Settings listener error:", error$1);
}
});
}
assertInitialized() {
if (!this.isInitialized()) throw new Error("SettingsService must be initialized before use");
}
};
}));
function ensureGuardEffect() {
if (guardDispose) return;
guardDispose = gallerySignals.isOpen.subscribe((isOpen) => {
if (!isOpen) return;
const result = pauseAmbientVideosForGallery({
trigger: "guard",
reason: "guard"
});
if (result.pausedCount <= 0) return;
;
});
}
function startAmbientVideoGuard() {
guardSubscribers += 1;
ensureGuardEffect();
return () => {
stopAmbientVideoGuard();
};
}
function stopAmbientVideoGuard() {
if (guardSubscribers === 0) return;
guardSubscribers -= 1;
if (guardSubscribers > 0) return;
guardDispose?.();
guardDispose = null;
}
function withAmbientVideoGuard() {
return { dispose: startAmbientVideoGuard() };
}
var guardDispose, guardSubscribers;
var init_ambient_video_guard = __esmMin((() => {
init_logging();
init_gallery_signals();
init_ambient_video_coordinator();
guardDispose = null;
guardSubscribers = 0;
}));
function getCurrentGalleryVideo(video$1) {
if (video$1) return video$1;
const signaled = gallerySignals.currentVideoElement.value;
if (signaled instanceof HTMLVideoElement) return signaled;
try {
const doc = typeof document !== "undefined" ? document : globalThis.document;
if (!(doc instanceof Document)) return null;
const hostSelectors = [
CSS.SELECTORS.DATA_GALLERY,
CSS.SELECTORS.ROOT,
CSS.SELECTORS.CONTAINER,
CSS.SELECTORS.DATA_CONTAINER
];
let container$2 = null;
for (const selector of hostSelectors) {
const match = doc.querySelector(selector);
if (match) {
container$2 = match;
break;
}
}
if (!container$2) return null;
const items = container$2.querySelector("[data-xeg-role=\"items-container\"]");
if (!items) return null;
const target = items.children?.[gallerySignals.currentIndex.value];
if (!target) return null;
const fallbackVideo = target.querySelector("video");
return fallbackVideo instanceof HTMLVideoElement ? fallbackVideo : null;
} catch (error$1) {
;
return null;
}
}
function executeVideoControl(action, options = {}) {
const { video: video$1, context } = options;
try {
const videoElement = getCurrentGalleryVideo(video$1);
if (!videoElement) {
;
return;
}
switch (action) {
case "play": {
const maybePromise = videoElement.play?.();
if (maybePromise && typeof maybePromise.then === "function") maybePromise.then(() => videoPlaybackStateMap.set(videoElement, { playing: true })).catch(() => {
videoPlaybackStateMap.set(videoElement, { playing: false });
;
});
else videoPlaybackStateMap.set(videoElement, { playing: true });
break;
}
case "pause":
videoElement.pause?.();
videoPlaybackStateMap.set(videoElement, { playing: false });
break;
case "togglePlayPause":
if (!(videoPlaybackStateMap.get(videoElement)?.playing ?? !videoElement.paused)) {
const maybePromise = videoElement.play?.();
if (maybePromise && typeof maybePromise.then === "function") maybePromise.then(() => videoPlaybackStateMap.set(videoElement, { playing: true })).catch(() => {
videoPlaybackStateMap.set(videoElement, { playing: false });
;
});
else videoPlaybackStateMap.set(videoElement, { playing: true });
} else {
videoElement.pause?.();
videoPlaybackStateMap.set(videoElement, { playing: false });
}
break;
case "volumeUp": {
const newVolume = Math.min(1, Math.round((videoElement.volume + .1) * 100) / 100);
videoElement.volume = newVolume;
if (newVolume > 0 && videoElement.muted) videoElement.muted = false;
break;
}
case "volumeDown": {
const newVolume = Math.max(0, Math.round((videoElement.volume - .1) * 100) / 100);
videoElement.volume = newVolume;
if (newVolume === 0 && !videoElement.muted) videoElement.muted = true;
break;
}
case "mute":
videoElement.muted = true;
break;
case "toggleMute":
videoElement.muted = !videoElement.muted;
break;
}
;
} catch (error$1) {
logger.error("[VideoControl] Unexpected error", {
error: error$1,
action,
context
});
}
}
var videoPlaybackStateMap;
var init_video_control_helper = __esmMin((() => {
init_constants$1();
init_logging();
init_gallery_signals();
videoPlaybackStateMap = /* @__PURE__ */ new WeakMap();
}));
function shouldExecuteKeyboardAction(key, minInterval = 100) {
const now = Date.now();
const timeSinceLastExecution = now - debounceState.lastExecutionTime;
if (key === debounceState.lastKey && timeSinceLastExecution < minInterval) {
;
return false;
}
debounceState.lastExecutionTime = now;
debounceState.lastKey = key;
return true;
}
function shouldExecuteVideoControlKey(key) {
if (![
"ArrowUp",
"ArrowDown",
"m",
"M"
].includes(key)) return true;
return shouldExecuteKeyboardAction(key, 100);
}
function shouldExecutePlayPauseKey(key) {
if (key !== " " && key !== "Space") return true;
return shouldExecuteKeyboardAction(key, 150);
}
function resetKeyboardDebounceState() {
debounceState.lastExecutionTime = 0;
debounceState.lastKey = "";
;
}
var debounceState;
var init_keyboard_debounce = __esmMin((() => {
init_logging();
debounceState = {
lastExecutionTime: 0,
lastKey: ""
};
}));
function checkGalleryOpen() {
return gallerySignals.isOpen.value;
}
function handleKeyboardEvent(event, handlers, options) {
if (!options.enableKeyboard) return;
try {
if (checkGalleryOpen()) {
const key = event.key;
if (key === "Home" || key === "End" || key === "PageDown" || key === "PageUp" || key === "ArrowLeft" || key === "ArrowRight" || key === " " || key === "Space" || key === " " || key === "Space" || key === "ArrowUp" || key === "ArrowDown" || key === "m" || key === "M") {
event.preventDefault();
event.stopPropagation();
switch (key) {
case " ":
case "Space":
if (shouldExecutePlayPauseKey(event.key)) executeVideoControl("togglePlayPause");
break;
case "ArrowLeft":
navigatePrevious("keyboard");
break;
case "ArrowRight":
navigateNext("keyboard");
break;
case "Home":
navigateToItem(0, "keyboard");
break;
case "End":
navigateToItem(Math.max(0, gallerySignals.mediaItems.value.length - 1), "keyboard");
break;
case "PageDown":
navigateToItem(Math.min(gallerySignals.mediaItems.value.length - 1, gallerySignals.currentIndex.value + 5), "keyboard");
break;
case "PageUp":
navigateToItem(Math.max(0, gallerySignals.currentIndex.value - 5), "keyboard");
break;
case "ArrowUp":
if (shouldExecuteVideoControlKey(event.key)) executeVideoControl("volumeUp");
break;
case "ArrowDown":
if (shouldExecuteVideoControlKey(event.key)) executeVideoControl("volumeDown");
break;
case "m":
case "M":
if (shouldExecuteVideoControlKey(event.key)) executeVideoControl("toggleMute");
break;
}
if (handlers.onKeyboardEvent) handlers.onKeyboardEvent(event);
return;
}
}
if (event.key === "Escape" && checkGalleryOpen()) {
handlers.onGalleryClose();
event.preventDefault();
event.stopPropagation();
return;
}
if (handlers.onKeyboardEvent) handlers.onKeyboardEvent(event);
} catch (error$1) {
logger.error("Error handling keyboard event:", error$1);
}
}
var init_keyboard = __esmMin((() => {
init_logging();
init_gallery_signals();
init_video_control_helper();
init_keyboard_debounce();
}));
function isValidMediaSource(url) {
if (!url) return false;
if (url.startsWith("blob:")) return true;
return isSafeAndValidMediaUrl(url);
}
function shouldBlockMediaTrigger(target) {
if (!target) return false;
if (isVideoControlElement(target)) return true;
if (target.closest(CSS.SELECTORS.ROOT) || target.closest(CSS.SELECTORS.OVERLAY)) return true;
const interactive = target.closest(INTERACTIVE_SELECTOR);
if (interactive) return !(interactive.matches(MEDIA_LINK_SELECTOR) || interactive.matches(MEDIA_CONTAINER_SELECTOR) || interactive.querySelector(MEDIA_CONTAINER_SELECTOR) !== null);
return false;
}
function isProcessableMedia(target) {
if (!target) return false;
if (gallerySignals.isOpen.value) return false;
if (shouldBlockMediaTrigger(target)) return false;
const mediaElement = findMediaElementInDOM(target);
if (mediaElement) {
if (isValidMediaSource(extractMediaUrlFromElement(mediaElement))) return true;
}
return Boolean(target.closest(MEDIA_CONTAINER_SELECTOR));
}
var MEDIA_LINK_SELECTOR, MEDIA_CONTAINER_SELECTOR, INTERACTIVE_SELECTOR;
var init_media_click_detector = __esmMin((() => {
init_css();
init_selectors();
init_utils();
init_gallery_signals();
init_media_element_utils();
init_url();
MEDIA_LINK_SELECTOR = [
STATUS_LINK_SELECTOR,
"a[href*=\"/photo/\"]",
"a[href*=\"/video/\"]"
].join(", ");
MEDIA_CONTAINER_SELECTOR = STABLE_MEDIA_CONTAINERS_SELECTORS.join(", ");
INTERACTIVE_SELECTOR = [
"button",
"a",
"[role=\"button\"]",
"[data-testid=\"like\"]",
"[data-testid=\"retweet\"]",
"[data-testid=\"reply\"]",
"[data-testid=\"share\"]",
"[data-testid=\"bookmark\"]"
].join(", ");
}));
async function handleMediaClick(event, handlers, options) {
if (!options.enableMediaDetection) return {
handled: false,
reason: "Media detection disabled"
};
const target = event.target;
if (!isHTMLElement(target)) return {
handled: false,
reason: "Invalid target (not HTMLElement)"
};
if (gallerySignals.isOpen.value && isGalleryInternalElement(target)) return {
handled: false,
reason: "Gallery internal event"
};
if (isVideoControlElement(target)) return {
handled: false,
reason: "Video control element"
};
if (!isProcessableMedia(target)) return {
handled: false,
reason: "Non-processable media target"
};
event.stopImmediatePropagation();
event.preventDefault();
await handlers.onMediaClick(target, event);
return {
handled: true,
reason: "Media click handled"
};
}
var init_media_click = __esmMin((() => {
init_utils();
init_gallery_signals();
init_media_click_detector();
}));
var gallery_lifecycle_exports = /* @__PURE__ */ __export({
cleanupGalleryEvents: () => cleanupGalleryEvents,
getGalleryEventSnapshot: () => getGalleryEventSnapshot,
initializeGalleryEvents: () => initializeGalleryEvents,
updateGalleryEventOptions: () => updateGalleryEventOptions
}, 1);
function sanitizeContext(context) {
const trimmed = context?.trim();
return trimmed && trimmed.length > 0 ? trimmed : DEFAULT_GALLERY_EVENT_OPTIONS.context;
}
function resolveInitializationInput(optionsOrRoot) {
if (optionsOrRoot instanceof HTMLElement) return {
options: { ...DEFAULT_GALLERY_EVENT_OPTIONS },
root: optionsOrRoot
};
const partial = optionsOrRoot ?? {};
const merged = {
...DEFAULT_GALLERY_EVENT_OPTIONS,
...partial
};
merged.context = sanitizeContext(merged.context);
return {
options: merged,
root: null
};
}
function resolveEventTarget(explicitRoot) {
return explicitRoot || document.body || document.documentElement || document;
}
async function initializeGalleryEvents(handlers, optionsOrRoot) {
if (lifecycleState$1.initialized) {
;
cleanupGalleryEvents();
}
if (!handlers) {
logger.error("[GalleryLifecycle] Missing handlers");
return () => {};
}
const { options: finalOptions, root: explicitGalleryRoot } = resolveInitializationInput(optionsOrRoot);
const listenerContext = sanitizeContext(finalOptions.context);
const keyHandler = (evt) => {
handleKeyboardEvent(evt, handlers, finalOptions);
};
const clickHandler = async (evt) => {
await handleMediaClick(evt, handlers, finalOptions);
};
const target = resolveEventTarget(explicitGalleryRoot);
const listenerOptions = {
capture: true,
passive: false
};
const eventManager = EventManager.getInstance();
if (finalOptions.enableKeyboard) eventManager.addListener(target, "keydown", keyHandler, listenerOptions, listenerContext);
if (finalOptions.enableMediaDetection) eventManager.addListener(target, "click", clickHandler, listenerOptions, listenerContext);
resetKeyboardDebounceState();
lifecycleState$1 = {
initialized: true,
options: finalOptions,
handlers,
keyListener: keyHandler,
clickListener: clickHandler,
listenerContext,
eventTarget: target
};
if (finalOptions.debugMode) ;
return () => {
cleanupGalleryEvents();
};
}
function cleanupGalleryEvents() {
if (!lifecycleState$1.initialized) return;
if (lifecycleState$1.listenerContext) EventManager.getInstance().removeByContext(lifecycleState$1.listenerContext);
resetKeyboardDebounceState();
lifecycleState$1 = { ...initialLifecycleState };
}
function updateGalleryEventOptions(newOptions) {
if (lifecycleState$1.options) lifecycleState$1.options = {
...lifecycleState$1.options,
...newOptions
};
}
function getGalleryEventSnapshot() {
return {
initialized: lifecycleState$1.initialized,
options: lifecycleState$1.options,
isConnected: lifecycleState$1.initialized
};
}
var DEFAULT_GALLERY_EVENT_OPTIONS, initialLifecycleState, lifecycleState$1;
var init_gallery_lifecycle = __esmMin((() => {
init_logging();
init_event_manager();
init_keyboard();
init_media_click();
init_keyboard_debounce();
DEFAULT_GALLERY_EVENT_OPTIONS = {
enableKeyboard: true,
enableMediaDetection: true,
debugMode: false,
preventBubbling: true,
context: "gallery"
};
initialLifecycleState = {
initialized: false,
options: null,
handlers: null,
keyListener: null,
clickListener: null,
listenerContext: null,
eventTarget: null
};
lifecycleState$1 = { ...initialLifecycleState };
}));
var GalleryApp_exports = /* @__PURE__ */ __export({ GalleryApp: () => GalleryApp }, 1);
var GalleryApp;
var init_GalleryApp = __esmMin((() => {
init_service_accessors();
init_app_error_reporter();
init_logging();
init_notification_service();
init_gallery_signals();
init_ambient_video_coordinator();
init_ambient_video_guard();
GalleryApp = class {
galleryRenderer = null;
isInitialized = false;
notificationService = NotificationService.getInstance();
ambientVideoGuardHandle = null;
constructor() {
;
}
async initialize() {
try {
;
this.galleryRenderer = getGalleryRenderer();
this.galleryRenderer?.setOnCloseCallback(() => this.closeGallery());
await this.setupEventHandlers();
this.ambientVideoGuardHandle = this.ambientVideoGuardHandle ?? withAmbientVideoGuard();
this.isInitialized = true;
;
} catch (error$1) {
galleryErrorReporter.critical(error$1, { code: "GALLERY_APP_INIT_FAILED" });
}
}
async setupEventHandlers() {
try {
const { initializeGalleryEvents: initializeGalleryEvents$1 } = await Promise.resolve().then(() => (init_gallery_lifecycle(), gallery_lifecycle_exports));
await initializeGalleryEvents$1({
onMediaClick: (element, event) => this.handleMediaClick(element, event),
onGalleryClose: () => this.closeGallery(),
onKeyboardEvent: (event) => {
if (event.key === "Escape" && gallerySignals.isOpen.value) this.closeGallery();
}
}, {
enableKeyboard: tryGetSettingsManager()?.get("gallery.enableKeyboardNav") ?? true,
enableMediaDetection: true,
debugMode: false,
preventBubbling: true,
context: "gallery"
});
;
} catch (error$1) {
galleryErrorReporter.critical(error$1, { code: "EVENT_HANDLERS_SETUP_FAILED" });
}
}
async handleMediaClick(element, _event) {
try {
const result = await getMediaService().extractFromClickedElement(element);
if (result.success && result.mediaItems.length > 0) await this.openGallery(result.mediaItems, result.clickedIndex, { pauseContext: {
sourceElement: element,
reason: "media-click"
} });
else {
mediaErrorReporter.warn(/* @__PURE__ */ new Error("Media extraction returned no items"), {
code: "MEDIA_EXTRACTION_EMPTY",
metadata: { success: result.success }
});
this.notificationService.error("Failed to load media", "Could not find images or videos.");
}
} catch (error$1) {
mediaErrorReporter.error(error$1, {
code: "MEDIA_EXTRACTION_ERROR",
notify: true
});
this.notificationService.error("Error occurred", getErrorMessage(error$1) || "Unknown error");
}
}
async openGallery(mediaItems, startIndex = 0, options = {}) {
if (!this.isInitialized) {
;
this.notificationService.error("Gallery unavailable", "Tampermonkey or similar userscript manager is required.");
return;
}
if (!mediaItems?.length) return;
try {
const validIndex = clampIndex(startIndex, mediaItems.length);
const providedContext = options.pauseContext ?? null;
const pauseContext = {
...providedContext,
reason: providedContext?.reason ?? (providedContext ? "media-click" : "programmatic")
};
try {
pauseAmbientVideosForGallery(pauseContext);
} catch (error$1) {
;
}
openGallery(mediaItems, validIndex);
} catch (error$1) {
galleryErrorReporter.error(error$1, {
code: "GALLERY_OPEN_FAILED",
metadata: {
itemCount: mediaItems.length,
startIndex
},
notify: true
});
this.notificationService.error("Failed to load gallery", getErrorMessage(error$1) || "Unknown error");
throw error$1;
}
}
closeGallery() {
try {
if (gallerySignals.isOpen.value) closeGallery();
} catch (error$1) {
galleryErrorReporter.error(error$1, { code: "GALLERY_CLOSE_FAILED" });
}
}
async cleanup() {
try {
;
if (gallerySignals.isOpen.value) this.closeGallery();
this.ambientVideoGuardHandle?.dispose();
this.ambientVideoGuardHandle = null;
try {
const { cleanupGalleryEvents: cleanupGalleryEvents$1 } = await Promise.resolve().then(() => (init_gallery_lifecycle(), gallery_lifecycle_exports));
cleanupGalleryEvents$1();
} catch (error$1) {
;
}
this.galleryRenderer = null;
this.isInitialized = false;
delete globalThis.xegGalleryDebug;
;
} catch (error$1) {
galleryErrorReporter.error(error$1, { code: "GALLERY_CLEANUP_FAILED" });
}
}
};
}));
init_service_accessors();
init_app_error_reporter();
init_userscript();
init_logging();
var rendererRegistrationTask = null;
async function registerRenderer() {
if (!rendererRegistrationTask) rendererRegistrationTask = (async () => {
const { GalleryRenderer: GalleryRenderer$1 } = await Promise.resolve().then(() => (init_GalleryRenderer(), GalleryRenderer_exports));
registerGalleryRenderer(new GalleryRenderer$1());
})().finally(() => {
rendererRegistrationTask = null;
});
await rendererRegistrationTask;
}
async function initializeServices() {
if (!(isGMAPIAvailable("download") || isGMAPIAvailable("setValue"))) bootstrapErrorReporter.warn(/* @__PURE__ */ new Error("Tampermonkey APIs limited"), { code: "GM_API_LIMITED" });
let settingsService = null;
try {
const { SettingsService: SettingsService$1 } = await Promise.resolve().then(() => (init_settings_service(), settings_service_exports));
const service = new SettingsService$1();
await service.initialize();
registerSettingsManager(service);
settingsService = service;
;
} catch (error$1) {
settingsErrorReporter.warn(error$1, { code: "SETTINGS_SERVICE_INIT_FAILED" });
}
try {
const themeService = getThemeService();
if (!themeService.isInitialized()) await themeService.initialize();
if (settingsService) themeService.bindSettingsService(settingsService);
;
} catch (error$1) {
bootstrapErrorReporter.warn(error$1, { code: "THEME_SYNC_FAILED" });
}
}
async function initializeGalleryApp() {
try {
;
await Promise.all([registerRenderer(), initializeServices()]);
const { GalleryApp: GalleryApp$1 } = await Promise.resolve().then(() => (init_GalleryApp(), GalleryApp_exports));
const galleryApp = new GalleryApp$1();
await galleryApp.initialize();
;
return galleryApp;
} catch (error$1) {
galleryErrorReporter.error(error$1, { code: "GALLERY_APP_INIT_FAILED" });
throw error$1;
}
}
init_app_error_reporter();
init_logging();
async function executeStage(stage) {
const startTime = performance.now();
if (stage.shouldRun && !stage.shouldRun()) return {
label: stage.label,
success: true,
optional: Boolean(stage.optional),
durationMs: 0
};
try {
await Promise.resolve(stage.run());
const durationMs = performance.now() - startTime;
return {
label: stage.label,
success: true,
optional: Boolean(stage.optional),
durationMs
};
} catch (error$1) {
const durationMs = performance.now() - startTime;
if (stage.optional) bootstrapErrorReporter.warn(error$1, {
code: "STAGE_OPTIONAL_FAILED",
metadata: {
stage: stage.label,
durationMs
}
});
else bootstrapErrorReporter.error(error$1, {
code: "STAGE_FAILED",
metadata: {
stage: stage.label,
durationMs
}
});
return {
label: stage.label,
success: false,
optional: Boolean(stage.optional),
error: error$1,
durationMs
};
}
}
async function executeStages(stages, options) {
const results = [];
const stopOnFailure = options?.stopOnFailure ?? true;
for (const stage of stages) {
const result = await executeStage(stage);
results.push(result);
if (!result.success && !result.optional && stopOnFailure) {
logger.error(`[bootstrap] ❌ Critical stage failed: ${stage.label}`);
break;
}
}
return results;
}
var base_services_exports = /* @__PURE__ */ __export({ initializeCoreBaseServices: () => initializeCoreBaseServices }, 1);
function registerMissingBaseServices(coreService) {
let registeredCount = 0;
for (const [key, getService] of BASE_SERVICE_REGISTRATIONS) if (!coreService.has(key)) {
coreService.register(key, getService());
registeredCount += 1;
}
return registeredCount;
}
async function initializeCoreBaseServices() {
const coreService = CoreService.getInstance();
try {
registerMissingBaseServices(coreService);
for (const identifier of CORE_BASE_SERVICE_IDENTIFIERS) {
const service = coreService.get(identifier);
if (service && typeof service.initialize === "function") await service.initialize();
}
} catch (error$1) {
throw new Error("[base-services] initialization failed", { cause: error$1 instanceof Error ? error$1 : new Error(String(error$1)) });
}
}
var BASE_SERVICE_REGISTRATIONS;
var init_base_services = __esmMin((() => {
init_service_accessors();
init_service_manager();
init_singletons();
BASE_SERVICE_REGISTRATIONS = [
[THEME_SERVICE_IDENTIFIER, getThemeServiceInstance],
[LANGUAGE_SERVICE_IDENTIFIER, getLanguageServiceInstance],
[MEDIA_SERVICE_IDENTIFIER, getMediaServiceInstance]
];
}));
var globals_exports = /* @__PURE__ */ __export({}, 1);
var init_globals = __esmMin((() => {
}));
var error_handler_exports = /* @__PURE__ */ __export({
GlobalErrorHandler: () => GlobalErrorHandler,
globalErrorHandler: () => globalErrorHandler
}, 1);
var GlobalErrorHandler, globalErrorHandler;
var init_error_handler = __esmMin((() => {
init_events();
init_logging();
GlobalErrorHandler = class GlobalErrorHandler {
static singleton = createSingleton(() => new GlobalErrorHandler());
isInitialized = false;
controller = null;
errorListener = (event) => {
const message = event.message ?? "Unknown error occurred";
const location = event.filename ? `${event.filename}:${event.lineno ?? 0}:${event.colno ?? 0}` : void 0;
logger.error(`[UncaughtError] ${message}`, {
type: "uncaught-error",
location
});
};
rejectionListener = (event) => {
const { reason } = event;
const message = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : `Unhandled rejection: ${String(reason)}`;
logger.error(`[UnhandledRejection] ${message}`, {
type: "unhandled-rejection",
reason
});
};
static getInstance() {
return GlobalErrorHandler.singleton.get();
}
constructor() {}
initialize() {
if (this.isInitialized || typeof window === "undefined") return;
const bus = getEventBus();
this.controller = new AbortController();
bus.addDOMListener(window, "error", this.errorListener, {
signal: this.controller.signal,
context: "global-error-handler"
});
bus.addDOMListener(window, "unhandledrejection", this.rejectionListener, {
signal: this.controller.signal,
context: "global-error-handler"
});
this.isInitialized = true;
}
destroy() {
if (!this.isInitialized || typeof window === "undefined") return;
this.controller?.abort();
this.controller = null;
this.isInitialized = false;
}
};
globalErrorHandler = GlobalErrorHandler.getInstance();
}));
init_service_accessors();
init_app_error_reporter();
init_logging();
init_service_manager();
init_timer_management();
var isDevEnvironment = false;
var lifecycleState = {
started: false,
startPromise: null,
galleryApp: null,
lastError: null
};
var warnCleanupLog = (message, error$1) => {
;
};
var debugCleanupLog = (message, error$1) => {
;
};
var globalEventTeardown = null;
var commandRuntimeTeardown = null;
async function refreshDevNamespace(app) {}
var initializeCommandRuntimeIfNeeded = async () => {};
function tearDownGlobalEventHandlers() {
if (!globalEventTeardown) return;
const teardown = globalEventTeardown;
globalEventTeardown = null;
try {
teardown();
} catch (error$1) {}
}
function tearDownCommandRuntime() {
if (!commandRuntimeTeardown) return;
const teardown = commandRuntimeTeardown;
commandRuntimeTeardown = null;
try {
teardown();
} catch (error$1) {}
}
async function runOptionalCleanup(label$1, task, log = warnCleanupLog) {
try {
await task();
} catch (error$1) {
log(label$1, error$1);
}
}
var bootstrapStages = [
{
label: "1",
run: loadGlobalStyles
},
{
label: "2",
run: initializeDevToolsIfNeeded,
shouldRun: () => isDevEnvironment,
optional: true
},
{
label: "3",
run: initializeInfrastructure
},
{
label: "4",
run: initializeCriticalSystems
},
{
label: "5",
run: initializeBaseServicesStage,
optional: true
},
{
label: "6",
run: applyInitialThemeSetting,
optional: true
},
{
label: "7",
run: () => setupGlobalEventHandlers()
},
{
label: "8",
run: initializeCommandRuntimeIfNeeded,
shouldRun: () => isDevEnvironment,
optional: true
},
{
label: "9",
run: initializeGalleryIfPermitted
},
{
label: "10",
run: () => initializeNonCriticalSystems(),
optional: true
}
];
async function runBootstrapStages() {
const failedStage = (await executeStages(bootstrapStages, { stopOnFailure: true })).find((r) => !r.success && !r.optional);
if (failedStage) throw failedStage.error ?? /* @__PURE__ */ new Error(`Bootstrap stage failed: ${failedStage.label}`);
}
async function initializeInfrastructure() {
try {
await initializeEnvironment();
;
} catch (error$1) {
bootstrapErrorReporter.critical(error$1, { code: "INFRASTRUCTURE_INIT_FAILED" });
throw error$1;
}
}
async function initializeBaseServicesStage() {
try {
const { initializeCoreBaseServices: initializeCoreBaseServices$1 } = await Promise.resolve().then(() => (init_base_services(), base_services_exports));
await initializeCoreBaseServices$1();
;
} catch (error$1) {
bootstrapErrorReporter.warn(error$1, { code: "BASE_SERVICES_INIT_FAILED" });
throw error$1;
}
}
async function applyInitialThemeSetting() {
try {
const themeService = getThemeService();
if (typeof themeService.isInitialized === "function" && !themeService.isInitialized()) await themeService.initialize();
const savedSetting = themeService.getCurrentTheme();
themeService.setTheme(savedSetting, {
force: true,
persist: false
});
} catch (error$1) {
;
}
}
function initializeNonCriticalSystems() {
try {
;
warmupNonCriticalServices();
;
} catch (error$1) {
;
}
}
function setupGlobalEventHandlers() {
tearDownGlobalEventHandlers();
globalEventTeardown = wireGlobalEvents(() => {
cleanup().catch((error$1) => logger.error("Error during page unload cleanup:", error$1));
});
}
async function loadGlobalStyles() {
await Promise.resolve().then(() => (init_globals(), globals_exports));
}
async function initializeDevToolsIfNeeded() {}
async function initializeGalleryIfPermitted() {
await initializeGallery();
}
async function cleanup() {
try {
;
tearDownGlobalEventHandlers();
tearDownCommandRuntime();
await runOptionalCleanup("Gallery cleanup", async () => {
if (!lifecycleState.galleryApp) return;
await lifecycleState.galleryApp.cleanup();
lifecycleState.galleryApp = null;
});
await runOptionalCleanup("CoreService cleanup", () => {
CoreService.getInstance().cleanup();
});
await runOptionalCleanup("Global timer cleanup", () => {
globalTimerManager.cleanup();
});
await runOptionalCleanup("Global error handler cleanup", async () => {
const { GlobalErrorHandler: GlobalErrorHandler$1 } = await Promise.resolve().then(() => (init_error_handler(), error_handler_exports));
GlobalErrorHandler$1.getInstance().destroy();
}, debugCleanupLog);
await runOptionalCleanup("Dev namespace refresh", async () => {
await refreshDevNamespace(lifecycleState.galleryApp);
}, debugCleanupLog);
lifecycleState.started = false;
;
} catch (error$1) {
bootstrapErrorReporter.error(error$1, { code: "CLEANUP_FAILED" });
throw error$1;
}
}
async function startApplication() {
if (lifecycleState.started) {
;
return;
}
if (lifecycleState.startPromise) {
;
return lifecycleState.startPromise;
}
lifecycleState.startPromise = (async () => {
;
await runBootstrapStages();
lifecycleState.started = true;
lifecycleState.lastError = null;
;
await refreshDevNamespace(lifecycleState.galleryApp);
})().catch((error$1) => {
lifecycleState.started = false;
lifecycleState.lastError = error$1;
bootstrapErrorReporter.error(error$1, {
code: "APP_INIT_FAILED",
metadata: { leanMode: true }
});
throw error$1;
}).finally(() => {
lifecycleState.startPromise = null;
});
return lifecycleState.startPromise;
}
async function initializeGallery() {
try {
;
lifecycleState.galleryApp = await initializeGalleryApp();
;
} catch (error$1) {
lifecycleState.galleryApp = null;
galleryErrorReporter.error(error$1, { code: "GALLERY_INIT_FAILED" });
throw error$1;
}
}
startApplication().catch(() => {});
})();