{"version":3,"mappings":"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","names":["getAccount","id","accounts","store","local","getJSON","find","a","info","getAccountByAccessToken","accessToken","getAccountByInstance","instance","instanceURL","getCurrentAccount","window","__IGNORE_GET_ACCOUNT_ERROR__","currentAccount","account","getCurrentAccountNS","saveAccount","acc","updateAccount","accountInfo","displayName","currentInstance","getCurrentInstance","instances","e","location","getInstanceConfiguration","configuration","maxMediaAttachments","maxTootChars","pleroma","pollLimits","statuses","polls","maxCharactersPerOption","maxExpiration","getCurrentInstanceConfiguration","get","key","localStorage","getItem","console","JSON","value","set","setJSON","warn","session","sessionStorage","setItem","data","del","DEFAULT_INSTANCE","apis","accountApis","__ACCOUNT_APIS__","currentAccountApi","initClient","test","replace","toLowerCase","url","client","masto","createRestAPIClient","timeout","initInstance","uri","streamingApi","urls","domain","streaming","streamClient","createStreamingAPIClient","implementation","initAccount","vapidKey","mastoAccount","initPreferences","preferences","accountID","authenticated","_b","_c","instanceAccount","_e","_showToast","showToast","props","text","onClick","delay","rest","toast","Toastify","className","destination","gravity","position","setTimeout","isMastodonLinkMaybe","pathname","hash","URL","_moize","moize","mem","fn","opts","maxSize","isDeepEqual","pmem","isPromise","rateLimit","interval","queue","isRunning","executeNext","length","shift","args","callFn","apply","push","throttle","pThrottle","limit","denylistDomains","failedUnfurls","_unfurlMastodonLink","snapStates","snapshot","states","RegExp","unfurledLinks","Promise","resolve","debug","remoteInstanceFetch","theURL","urlAfterHash","urlObj","hostname","statusRegex","path","statusMatch","masto2","status","mastoSearchFetch","q","type","results","then","handleFulfill","result","selfURL","instance2","saveStatus","reject","handleCatch","finalPromise","unfurlMastodonLink","proxy","appVersion","prevLocation","currentLocation","statusThreadNumber","home","homeNew","homeLast","homeLastFetchTime","notifications","notificationsLast","notificationsNew","notificationsShowNew","notificationsLastFetchTime","reloadStatusPage","reloadGenericAccounts","counter","spoilers","spoilersMedia","scrollPositions","statusQuotes","statusFollowedTags","routeNotification","showCompose","showSettings","showAccount","showAccounts","showDrafts","showMediaModal","showShortcutsSettings","showKeyboardShortcutsHelp","showGenericAccounts","showMediaAlt","shortcuts","settings","autoRefresh","shortcutsViewMode","shortcutsColumnsMode","boostsCarousel","contentTranslation","contentTranslationTargetLanguage","contentTranslationHideLanguages","contentTranslationAutoInline","mediaAltGenerator","cloakMode","states$1","initStates","subscribeKey","v","subscribe","changes","prevValue","statusKey","getStatus","statusID","skipThreading","skipUnfurling","override","oldStatus","_pinned","threadifyStatus","queueMicrotask","unfurlStatus","_threadifyStatus","propInstance","api","traverse","index","fetchIndex","inReplyToAccountId","inReplyToId","prevStatus","r","fetchStatus","fauxDiv","content","document","sKey","reblog","i","splice","useInterval","deps","immediate","savedCallback","useRef","useEffect","current","setInterval","tick","clearInterval","openCompose","width","screenWidth","height","screenHeight","screen","left","Math","max","top","min","winUID","uid","random","newWin","open","__COMPOSE__","alert","SIZES","s","m","l","xl","xxl","ICONS","x","__vitePreload","heart","bookmark","check-circle","x-circle","transfer","rocket","arrow-left","arrow-right","arrow-up","arrow-down","earth","lock","unlock","eye-close","eye-open","message","comment","comment2","notification","follow","follow-add","poll","pencil","quill","at","attachment","upload","gear","more","external","popout","popin","plus","chevron-left","chevron-right","chevron-down","reply","thread","group","bot","menu","list","search","hashtag","shortcut","user","following","pin","bus","link","history","share","sparkles","sparkles2","exit","translate","play","trash","mute","unmute","block","unblock","flag","time","refresh","emoji2","filter","chart","react","layout4","layout5","announce","round","arrow-up-circle","arrow-down-circle","clipboard","account-edit","account-warning","keyboard","cloud","month","media","speak","building","ICONDATA","Icon","icon","size","alt","title","class","style","iconSize","iconBlock","rotate","flip","Array","isArray","iconData","setIconData","useState","currentIcon","iconB","default","_jsx","children","viewBox","dangerouslySetInnerHTML","__html","body","transform","useCloseWatcher","watcher","CloseWatcher","addEventListener","destroy","useCloseWatcher$1","$modalContainer","getElementById","Modal","onClose","modalRef","timer","focusElement","querySelector","focus","clearTimeout","supportsCloseWatcher","escRef","useHotkeys","enabled","keydown","keyup","ref","node","target","currentTarget","tabIndex","onFocus","createPortal","Loader","abrupt","hidden","xxxl","alphaCache","canvas","OffscreenCanvas","createElement","ctx","getContext","willReadFrequently","Avatar","squircle","avatarRef","isMissing","src","loading","decoding","crossOrigin","undefined","onError","onLoad","dataset","loaded","drawImage","allPixels","getImageData","hasAlpha","pixel","classList","add","clearRect","Link","forwardRef","routerLocation","useLocation","trim","to","restProps","includes","parsedHash","origin","searchParams","searchParamsStr","from","entries","map","encodeURIComponent","join","isActive","decodeURIComponent","href","parentNode","closest","stopPropagation","root","documentElement","getComputedStyle","defaultBoundingBoxPadding","safeAreaInsets","getSafeAreaInsets","safeAreaInsetTop","getPropertyValue","safeAreaInsetRight","safeAreaInsetBottom","safeAreaInsetLeft","parseInt","requestAnimationFrame","safeBoundingBoxPadding","paddings","CSS","supports","passive","Menu2","containerProps","instanceRef","_instanceRef","useWindowSize","Menu","boundingBoxPadding","repositionFlag","unmountOnClose","closeMenu","MenuConfirm","subMenu","confirm","confirmLabel","menuItemClassName","menuFooter","MenuItem","cloneElement","Parent","SubMenu","menuRef","_jsxs","openTrigger","direction","overflow","gap","menuClassName","menuButton","label","itemProps","onPointerMove","pointerType","openMenu","onPointerLeave","EmojiText","emojis","indexOf","regex","shortcode","elements","split","word","emoji","staticUrl","srcset","nameCollator","Intl","Collator","sensitivity","NameText","showAvatar","showAcct","short","acct","avatar","avatarStatic","username","_","acct1","acct2","match","trimmedUsername","trimmedDisplayName","shortenedDisplayName","shortenedAlphaNumericDisplayName","compare","preventDefault","_Fragment","memo","dayjs","extend","dayjsTwitter","localizedFormat","relativeTime","dtf","DateTimeFormat","RelativeTime","datetime","format","date","dateStr","now","diff","year","twitter","toDate","fromNow","toISOString","_localeMatch","localeMatch","locales","resolvedOptions","locale","navigator","languages","localeTargetLanguages","translationTargetLanguages","code","getTranslateTargetLanguage","fromSettings","localeCode2Text","DisplayNames","of","emojifyText","forEach","whitelistLinkClasses","_enhanceContent","postEnhanceDOM","enhancedContent","dom","innerHTML","hasLink","hasCodeBlock","querySelectorAll","setAttribute","c","remove","textContent","links","usernames","innerText","hasChildren","some","u","d","slice","textNodes","extractTextNodes","html","nodeValue","replaceWith","childNodes","p","pre","br","nextParagraphs","currentBlock","nextElementSibling","next","tagName","codeText","replaceAll","rejectFilter","prevIndex","hashtagStuffedParagraphs","hashtagCount","nodeType","Node","TEXT_NODE","linkText","startsWith","enhanceContent","defaultRejectFilter","defaultRejectFilterMap","Object","fromEntries","nodeName","rejectFilterMap","assign","reduce","cur","walk","createTreeWalker","NodeFilter","SHOW_TEXT","acceptNode","FILTER_REJECT","FILTER_ACCEPT","nextNode","div","getHTMLText","handleContentLinks","mentions","previewMode","statusURL","metaKey","ctrlKey","shiftKey","altKey","which","prevText","previousSibling","textBeforeLinkIsAt","endsWith","textStartsWithAt","contains","targetText","getAttribute","mention","textBeforeLinkIsHash","tag","_DateTimeFormat","dateYear","hideTime","formatOpts","currentYear","Date","getFullYear","day","hour","minute","niceDateTime","dtfOpts","NumberFormat","shortenNumber","notation","AccountBlock","skeleton","avatarSize","useAvatarStatic","internal","showActivity","showStats","accountInstance","hideDisplayName","relationship","excludeRelationshipAttrs","statusesCount","lastStatusAt","fields","note","followersCount","verifiedField","f","verifiedAt","excludedRelationship","hasRelationship","followedBy","requested","supportsCache","feature","version","range","features","satisfies","includePrerelease","loose","define_import_meta_env_default","draftsStore","createStore","drafts","val","getMany","keys","delMany","clear","db","visibilityIconsMap","public","unlisted","private","direct","Poll","lang","readOnly","votePoll","uiState","setUIState","expired","expiresAt","multiple","options","ownVotes","voted","votersCount","votesCount","expiresAtDate","pollVotesCount","roundPrecision","showResults","setShowResults","optionsHaveVoteCounts","every","o","dir","option","optionVotesCount","percentage","toFixed","isLeading","disabled","onSubmit","form","formData","FormData","choices","name","marginLeft","FilterContext","createContext","_isFiltered","filtered","filterContext","appliedFilters","hasContext","context","isHidden","filterAction","action","filterTitles","titles","titlesStr","isFiltered","filteredItem","item","currentAccountID","isSelf","filterState","filteredItems","items","htmlContentLength","el","supportsTTS","speechSynthesis","speaking","cancel","utterance","SpeechSynthesisUtterance","statusPeek","spoilerText","mediaAttachments","image","gifv","video","audio","unknown","useTruncated","onResize","useThrottledCallback","scrollHeight","truncated","_height","maxHeight","computedHeight","toggle","useResizeObserver","box","isSafari","userAgent","dataAltLabel","AltBadge","MEDIA_CAPTION_LIMIT","isMediaCaptionLong","caption","Media","showOriginal","autoAnimate","showCaption","altIndex","blurhash","description","meta","previewRemoteUrl","previewUrl","remoteUrl","original","small","mediaURL","remoteMediaURL","orientation","rgbAverageColor","getBlurHashAverageColor","videoRef","focalPosition","y","mediaRef","onUpdate","useCallback","scale","make3dTransformValue","removeProperty","setProperty","touchAction","pinchZoomEnabled","setPinchZoomEnabled","quickPinchZoomProps","draggableUnZoomed","inertiaFriction","doubleTapZoomOutOnMaxScale","useMemo","isVideoMaybe","isImage","parentRef","imageSmallerThanParent","setImageSmallerThanParent","useLayoutEffect","offsetWidth","offsetHeight","smaller","maxAspectHeight","innerHeight","averageColorStyle","mediaStyles","aspectRatio","longDesc","showInlineDesc","Figure","Fragment","display","fetch","mode","backgroundImage","backgroundSize","QuickPinchZoom","objectPosition","shortDuration","duration","isGIF","loopable","formattedDuration","formatDuration","hoverAnimate","autoGIFAnimate","showProgress","videoHTML","pause","onMouseEnter","onMouseLeave","onBlur","poster","preload","playsinline","loop","muted","onTimeUpdate","container","currentTime","controls","autoplay","MenuLink","FocusableItem","detail","SHOW_COMMENT_COUNT_LIMIT","INLINE_TRANSLATE_LIMIT","fetchAccount","v1","$select","memFetchAccount","visibilityText","isIOS","ontouchstart","REACTIONS_LIMIT","getPollText","getPostText","Status","contentTextWeight","enableCommentHint","withinContext","enableTranslate","forceTranslate","_forceTranslate","onMediaClick","quoted","onStatusLinkClick","showFollowedTags","sameInstance","useSnapshot","accountId","accountURL","accountEmojis","repliesCount","reblogged","reblogsCount","favourited","favouritesCount","bookmarked","sensitive","visibility","language","editedAt","card","createdAt","tags","_deleted","useContext","filterInfo","debugHover","FilteredStatus","editedAtDate","inReplyToAccountRef","createdAtDate","setInReplyToAccount","inReplyToAccount","account2","catch","mentionSelf","readingExpandSpoilers","showSpoiler","readingExpandMedia","prefs","showSpoilerMedia","setForceTranslate","targetLanguage","contentLength","inlineTranslate","isSizeLarge","showEdited","setShowEdited","spoilerContentRef","statusRef","contentRef","mediaContainerRef","readMoreText","textWeight","editedDateText","_a","mediaNoDesc","replyToStatus","boostStatus","unauthInteractionErrorMessage","confirmText","yes","newStatus","favouriteStatus","differentLanguage","fetchBoostedLikedByAccounts","firstLoad","favouriteIterator","reblogResults","reblogIterator","favouriteResults","_types","done","MenuHeader","StatusMenuItems","createdDateText","MenuDivider","fetchAccounts","showReactions","replyStatus","postText","nicePostURL","editStatus","cachedStatus","setIsContextMenuOpen","contextMenuProps","setContextMenuProps","showContextMenu","bindLongPressContext","useLongPress","clientX","clientY","isContextMenuOpen","hotkeysEnabled","fRef","rRef","dRef","confirmBoostStatus","bRef","activeStatus","spoilerButton","spoilerMediaButton","canBoost","xRef","showMultipleMediaCaptions","displayedMediaAttachments","attachments","indices","showCommentHint","isThread","showCommentCount","nodeRef","containsQuestion","contextMenuRef","portal","QuoteStatuses","newPoll","pollResponse","TranslationBlock","mini","captionChildren","StatusButton","checked","bookmarkStatus","count","EditedAtModal","selfReferential","providerName","providerUrl","authorName","authorUrl","imageDescription","embedUrl","publishedAt","hasText","isLandscape","cardStatusURL","blurhashImage","setCardStatusURL","imageData","w","putImageData","videoID","fetchStatusHistory","editHistory","setEditHistory","editHistory2","setIconAlt","buttonTitle","iconAlt","host","minutes","restPath","seconds","toString","filterTitleStr","statusPeekText","bindLongPressPeek","isReblog","actualStatusID","isFollowedTags","setShowPeek","threshold","captureEvent","ssKey","showPeek","statusPeekRef","quotes","uniqueQuotes","arr","q2","level"],"sources":["../../src/utils/store-utils.js","../../src/utils/store.js","../../src/utils/api.js","../../src/utils/show-toast.js","../../src/utils/isMastodonLinkMaybe.jsx","../../src/utils/mem.js","../../src/utils/pmem.js","../../src/utils/ratelimit.js","../../src/utils/unfurl-link.jsx","../../src/utils/states.js","../../src/utils/useInterval.js","../../src/utils/open-compose.js","../../src/components/icon.jsx","../../src/utils/useCloseWatcher.js","../../src/components/modal.jsx","../../src/components/loader.jsx","../../src/components/avatar.jsx","../../src/components/link.jsx","../../src/utils/safe-bounding-box-padding.js","../../src/components/menu2.jsx","../../src/components/menu-confirm.jsx","../../src/components/emoji-text.jsx","../../src/components/name-text.jsx","../../src/components/relative-time.jsx","../../src/utils/locale-match.jsx","../../src/utils/get-translate-target-language.jsx","../../src/utils/localeCode2Text.jsx","../../src/utils/emojify-text.js","../../src/utils/enhance-content.js","../../src/utils/getHTMLText.jsx","../../src/utils/handle-content-links.js","../../src/utils/nice-date-time.js","../../src/utils/shorten-number.jsx","../../src/components/account-block.jsx","../../src/utils/supports.js","../../src/components/translation-block.jsx","../../src/utils/db.js","../../src/utils/visibility-icons-map.js","../../src/components/poll.jsx","../../src/utils/filter-context.js","../../src/utils/filters.jsx","../../src/utils/html-content-length.js","../../src/utils/speech.js","../../src/utils/status-peek.jsx","../../src/utils/useTruncated.js","../../src/components/media.jsx","../../src/components/menu-link.jsx","../../src/components/status.jsx","../../src/components/compose.jsx","../../src/utils/useTitle.js"],"sourcesContent":["import store from './store';\n\nexport function getAccount(id) {\n  const accounts = store.local.getJSON('accounts') || [];\n  return accounts.find((a) => a.info.id === id) || accounts[0];\n}\n\nexport function getAccountByAccessToken(accessToken) {\n  const accounts = store.local.getJSON('accounts') || [];\n  return accounts.find((a) => a.accessToken === accessToken);\n}\n\nexport function getAccountByInstance(instance) {\n  const accounts = store.local.getJSON('accounts') || [];\n  return accounts.find((a) => a.instanceURL === instance);\n}\n\nexport function getCurrentAccount() {\n  if (!window.__IGNORE_GET_ACCOUNT_ERROR__) {\n    // Track down getCurrentAccount() calls before account-based states are initialized\n    console.error('getCurrentAccount() called before states are initialized');\n    if (import.meta.env.DEV) console.trace();\n  }\n  const currentAccount = store.session.get('currentAccount');\n  const account = getAccount(currentAccount);\n  return account;\n}\n\nexport function getCurrentAccountNS() {\n  const account = getCurrentAccount();\n  const {\n    instanceURL,\n    info: { id },\n  } = account;\n  return `${id}@${instanceURL}`;\n}\n\nexport function saveAccount(account) {\n  const accounts = store.local.getJSON('accounts') || [];\n  const acc = accounts.find((a) => a.info.id === account.info.id);\n  if (acc) {\n    acc.info = account.info;\n    acc.instanceURL = account.instanceURL;\n    acc.accessToken = account.accessToken;\n    acc.vapidKey = account.vapidKey;\n  } else {\n    accounts.push(account);\n  }\n  store.local.setJSON('accounts', accounts);\n  store.session.set('currentAccount', account.info.id);\n}\n\nexport function updateAccount(accountInfo) {\n  // Only update if displayName or avatar or avatar_static is different\n  const accounts = store.local.getJSON('accounts') || [];\n  const acc = accounts.find((a) => a.info.id === accountInfo.id);\n  if (acc) {\n    if (\n      acc.info.displayName !== accountInfo.displayName ||\n      acc.info.avatar !== accountInfo.avatar ||\n      acc.info.avatar_static !== accountInfo.avatar_static\n    ) {\n      acc.info = {\n        ...acc.info,\n        ...accountInfo,\n      };\n      store.local.setJSON('accounts', accounts);\n    }\n  }\n}\n\nlet currentInstance = null;\nexport function getCurrentInstance() {\n  if (currentInstance) return currentInstance;\n  try {\n    const account = getCurrentAccount();\n    const instances = store.local.getJSON('instances');\n    const instance = account.instanceURL.toLowerCase();\n    return (currentInstance = instances[instance]);\n  } catch (e) {\n    console.error(e);\n    alert(`Failed to load instance configuration. Please try again.\\n\\n${e}`);\n    // Temporary fix for corrupted data\n    store.local.del('instances');\n    location.reload();\n    return {};\n  }\n}\n\n// Massage these instance configurations to match the Mastodon API\n// - Pleroma\nfunction getInstanceConfiguration(instance) {\n  const {\n    configuration,\n    maxMediaAttachments,\n    maxTootChars,\n    pleroma,\n    pollLimits,\n  } = instance;\n\n  const statuses = configuration?.statuses || {};\n  if (maxMediaAttachments) {\n    statuses.maxMediaAttachments ??= maxMediaAttachments;\n  }\n  if (maxTootChars) {\n    statuses.maxCharacters ??= maxTootChars;\n  }\n\n  const polls = configuration?.polls || {};\n  if (pollLimits) {\n    polls.maxCharactersPerOption ??= pollLimits.maxOptionChars;\n    polls.maxExpiration ??= pollLimits.maxExpiration;\n    polls.maxOptions ??= pollLimits.maxOptions;\n    polls.minExpiration ??= pollLimits.minExpiration;\n  }\n\n  return {\n    ...configuration,\n    statuses,\n    polls,\n  };\n}\n\nexport function getCurrentInstanceConfiguration() {\n  const instance = getCurrentInstance();\n  return getInstanceConfiguration(instance);\n}\n","import { getCurrentAccountNS } from './store-utils';\n\nconst local = {\n  get: (key) => {\n    try {\n      return localStorage.getItem(key);\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n  getJSON: (key) => {\n    try {\n      return JSON.parse(local.get(key));\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n  set: (key, value) => {\n    try {\n      return localStorage.setItem(key, value);\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n  setJSON: (key, value) => {\n    try {\n      return local.set(key, JSON.stringify(value));\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n  del: (key) => {\n    try {\n      return localStorage.removeItem(key);\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n};\n\nconst session = {\n  get: (key) => {\n    try {\n      return sessionStorage.getItem(key);\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n  getJSON: (key) => {\n    try {\n      return JSON.parse(session.get(key));\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n  set: (key, value) => {\n    try {\n      return sessionStorage.setItem(key, value);\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n  setJSON: (key, value) => {\n    try {\n      return session.set(key, JSON.stringify(value));\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n  del: (key) => {\n    try {\n      return sessionStorage.removeItem(key);\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n};\n\n// Store with account namespace (id@domain.tld) <- uses id, not username\nconst account = {\n  get: (key) => {\n    try {\n      return local.getJSON(key)[getCurrentAccountNS()];\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n  set: (key, value) => {\n    try {\n      const data = local.getJSON(key) || {};\n      data[getCurrentAccountNS()] = value;\n      return local.setJSON(key, data);\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n  del: (key) => {\n    try {\n      const data = local.getJSON(key) || {};\n      delete data[getCurrentAccountNS()];\n      return local.setJSON(key, data);\n    } catch (e) {\n      console.warn(e);\n      return null;\n    }\n  },\n};\n\nexport default { local, session, account };\n","import { createRestAPIClient, createStreamingAPIClient } from 'masto';\n\nimport store from './store';\nimport {\n  getAccount,\n  getAccountByAccessToken,\n  getAccountByInstance,\n  getCurrentAccount,\n  saveAccount,\n} from './store-utils';\n\n// Default *fallback* instance\nconst DEFAULT_INSTANCE = 'mastodon.social';\n\n// Per-instance masto instance\n// Useful when only one account is logged in\n// I'm not sure if I'll ever allow multiple logged-in accounts but oh well...\n// E.g. apis['mastodon.social']\nconst apis = {};\n\n// Per-account masto instance\n// Note: There can be many accounts per instance\n// Useful when multiple accounts are logged in or when certain actions require a specific account\n// Just in case if I need this one day.\n// E.g. accountApis['mastodon.social']['ACCESS_TOKEN']\nconst accountApis = {};\nwindow.__ACCOUNT_APIS__ = accountApis;\n\n// Current account masto instance\nlet currentAccountApi;\n\nexport function initClient({ instance, accessToken }) {\n  if (/^https?:\\/\\//.test(instance)) {\n    instance = instance\n      .replace(/^https?:\\/\\//, '')\n      .replace(/\\/+$/, '')\n      .toLowerCase();\n  }\n  const url = instance ? `https://${instance}` : `https://${DEFAULT_INSTANCE}`;\n\n  const masto = createRestAPIClient({\n    url,\n    accessToken, // Can be null\n    timeout: 30_000, // Unfortunatly this is global instead of per-request\n  });\n\n  const client = {\n    masto,\n    instance,\n    accessToken,\n  };\n  apis[instance] = client;\n  if (!accountApis[instance]) accountApis[instance] = {};\n  if (accessToken) accountApis[instance][accessToken] = client;\n\n  return client;\n}\n\n// Get the instance information\n// The config is needed for composing\nexport async function initInstance(client, instance) {\n  console.log('INIT INSTANCE', client, instance);\n  const { masto, accessToken } = client;\n  // Request v2, fallback to v1 if fail\n  let info;\n  try {\n    info = await masto.v2.instance.fetch();\n  } catch (e) {}\n  if (!info) {\n    try {\n      info = await masto.v1.instance.fetch();\n    } catch (e) {}\n  }\n  if (!info) return;\n  console.log(info);\n  const {\n    // v1\n    uri,\n    urls: { streamingApi } = {},\n    // v2\n    domain,\n    configuration: { urls: { streaming } = {} } = {},\n  } = info;\n  const instances = store.local.getJSON('instances') || {};\n  if (uri || domain) {\n    instances[\n      (domain || uri)\n        .replace(/^https?:\\/\\//, '')\n        .replace(/\\/+$/, '')\n        .toLowerCase()\n    ] = info;\n  }\n  if (instance) {\n    instances[instance.toLowerCase()] = info;\n  }\n  store.local.setJSON('instances', instances);\n  // This is a weird place to put this but here's updating the masto instance with the streaming API URL set in the configuration\n  // Reason: Streaming WebSocket URL may change, unlike the standard API REST URLs\n  const supportsWebSocket = 'WebSocket' in window;\n  if (supportsWebSocket && (streamingApi || streaming)) {\n    console.log('🎏 Streaming API URL:', streaming || streamingApi);\n    // masto.config.props.streamingApiUrl = streaming || streamingApi;\n    // Legacy masto.ws\n    const streamClient = createStreamingAPIClient({\n      streamingApiUrl: streaming || streamingApi,\n      accessToken,\n      implementation: WebSocket,\n    });\n    client.streaming = streamClient;\n    // masto.ws = streamClient;\n    console.log('🎏 Streaming API client:', client);\n  }\n}\n\n// Get the account information and store it\nexport async function initAccount(client, instance, accessToken, vapidKey) {\n  const { masto } = client;\n  const mastoAccount = await masto.v1.accounts.verifyCredentials();\n\n  console.log('CURRENTACCOUNT SET', mastoAccount.id);\n  store.session.set('currentAccount', mastoAccount.id);\n\n  saveAccount({\n    info: mastoAccount,\n    instanceURL: instance.toLowerCase(),\n    accessToken,\n    vapidKey,\n  });\n}\n\n// Get preferences\nexport async function initPreferences(client) {\n  try {\n    const { masto } = client;\n    const preferences = await masto.v1.preferences.fetch();\n    store.account.set('preferences', preferences);\n  } catch (e) {\n    // silently fail\n    console.error(e);\n  }\n}\n\n// Get the masto instance\n// If accountID is provided, get the masto instance for that account\nexport function api({ instance, accessToken, accountID, account } = {}) {\n  // Always lowercase and trim the instance\n  if (instance) {\n    instance = instance.toLowerCase().trim();\n  }\n\n  // If instance and accessToken are provided, get the masto instance for that account\n  if (instance && accessToken) {\n    const client =\n      accountApis[instance]?.[accessToken] ||\n      initClient({ instance, accessToken });\n    const { masto, streaming } = client;\n    return {\n      masto,\n      streaming,\n      client,\n      authenticated: true,\n      instance,\n    };\n  }\n\n  if (accessToken) {\n    // If only accessToken is provided, get the masto instance for that accessToken\n    console.log('X 1', accountApis);\n    for (const instance in accountApis) {\n      if (accountApis[instance][accessToken]) {\n        console.log('X 2', accountApis, instance, accessToken);\n        const client = accountApis[instance][accessToken];\n        const { masto, streaming } = client;\n        return {\n          masto,\n          streaming,\n          client,\n          authenticated: true,\n          instance,\n        };\n      } else {\n        console.log('X 3', accountApis, instance, accessToken);\n        const account = getAccountByAccessToken(accessToken);\n        if (account) {\n          const accessToken = account.accessToken;\n          const instance = account.instanceURL.toLowerCase().trim();\n          const client = initClient({ instance, accessToken });\n          const { masto, streaming } = client;\n          return {\n            masto,\n            streaming,\n            client,\n            authenticated: true,\n            instance,\n          };\n        } else {\n          throw new Error(`Access token not found`);\n        }\n      }\n    }\n  }\n\n  // If account is provided, get the masto instance for that account\n  if (account || accountID) {\n    account = account || getAccount(accountID);\n    if (account) {\n      const accessToken = account.accessToken;\n      const instance = account.instanceURL.toLowerCase().trim();\n      const client =\n        accountApis[instance]?.[accessToken] ||\n        initClient({ instance, accessToken });\n      const { masto, streaming } = client;\n      return {\n        masto,\n        streaming,\n        client,\n        authenticated: true,\n        instance,\n      };\n    } else {\n      throw new Error(`Account ${accountID} not found`);\n    }\n  }\n\n  const currentAccount = getCurrentAccount();\n\n  // If only instance is provided, get the masto instance for that instance\n  if (instance) {\n    if (currentAccountApi?.instance === instance) {\n      return {\n        masto: currentAccountApi.masto,\n        streaming: currentAccountApi.streaming,\n        client: currentAccountApi,\n        authenticated: true,\n        instance,\n      };\n    }\n\n    if (currentAccount?.instanceURL === instance) {\n      const { accessToken } = currentAccount;\n      currentAccountApi =\n        accountApis[instance]?.[accessToken] ||\n        initClient({ instance, accessToken });\n      return {\n        masto: currentAccountApi.masto,\n        streaming: currentAccountApi.streaming,\n        client: currentAccountApi,\n        authenticated: true,\n        instance,\n      };\n    }\n\n    const instanceAccount = getAccountByInstance(instance);\n    if (instanceAccount) {\n      const accessToken = instanceAccount.accessToken;\n      const client =\n        accountApis[instance]?.[accessToken] ||\n        initClient({ instance, accessToken });\n      const { masto, streaming } = client;\n      return {\n        masto,\n        streaming,\n        client,\n        authenticated: true,\n        instance,\n      };\n    }\n\n    const client = apis[instance] || initClient({ instance });\n    const { masto, streaming, accessToken } = client;\n    return {\n      masto,\n      streaming,\n      client,\n      authenticated: !!accessToken,\n      instance,\n    };\n  }\n\n  // If no instance is provided, get the masto instance for the current account\n  if (currentAccountApi) {\n    return {\n      masto: currentAccountApi.masto,\n      streaming: currentAccountApi.streaming,\n      client: currentAccountApi,\n      authenticated: true,\n      instance: currentAccountApi.instance,\n    };\n  }\n  if (currentAccount) {\n    const { accessToken, instanceURL: instance } = currentAccount;\n    currentAccountApi =\n      accountApis[instance]?.[accessToken] ||\n      initClient({ instance, accessToken });\n    return {\n      masto: currentAccountApi.masto,\n      streaming: currentAccountApi.streaming,\n      client: currentAccountApi,\n      authenticated: true,\n      instance,\n    };\n  }\n\n  // If no instance is provided and no account is logged in, get the masto instance for DEFAULT_INSTANCE\n  const client =\n    apis[DEFAULT_INSTANCE] || initClient({ instance: DEFAULT_INSTANCE });\n  const { masto, streaming } = client;\n  return {\n    masto,\n    streaming,\n    client,\n    authenticated: false,\n    instance: DEFAULT_INSTANCE,\n  };\n}\n\nwindow.__API__ = {\n  currentAccountApi,\n  apis,\n  accountApis,\n};\n","import Toastify from 'toastify-js';\n\nwindow._showToast = showToast;\n\nfunction showToast(props) {\n  if (typeof props === 'string') {\n    props = { text: props };\n  }\n  const { onClick, delay, ...rest } = props;\n  const toast = Toastify({\n    className: `${onClick || props.destination ? 'shiny-pill' : ''}`,\n    gravity: 'bottom',\n    position: 'center',\n    ...rest,\n    onClick: () => {\n      onClick?.(toast); // Pass in the object itself!\n    },\n  });\n  if (delay) {\n    setTimeout(() => {\n      toast.showToast();\n    }, delay);\n  } else {\n    toast.showToast();\n  }\n  return toast;\n}\n\nexport default showToast;\n","export default function isMastodonLinkMaybe(url) {\n  try {\n    const { pathname, hash } = new URL(url);\n    return (\n      /^\\/.*\\/\\d+$/i.test(pathname) ||\n      /^\\/(@[^/]+|users\\/[^/]+)\\/(statuses|posts)\\/\\w+\\/?$/i.test(pathname) || // GoToSocial, Takahe\n      /^\\/notes\\/[a-z0-9]+$/i.test(pathname) || // Misskey, Firefish\n      /^\\/(notice|objects)\\/[a-z0-9-]+$/i.test(pathname) || // Pleroma\n      /#\\/[^\\/]+\\.[^\\/]+\\/s\\/.+/i.test(hash) // Phanpy 🫣\n    );\n  } catch (e) {\n    return false;\n  }\n}\n","import moize from 'moize';\n\nwindow._moize = moize;\n\nexport default function mem(fn, opts = {}) {\n  return moize(fn, { ...opts, maxSize: 50, isDeepEqual: true });\n}\n","import mem from './mem';\n\nexport default function pmem(fn, opts = {}) {\n  return mem(fn, { isPromise: true, ...opts });\n}\n","// Rate limit repeated function calls and queue them to set interval\nexport default function rateLimit(fn, interval) {\n  let queue = [];\n  let isRunning = false;\n\n  function executeNext() {\n    if (queue.length === 0) {\n      isRunning = false;\n      return;\n    }\n\n    const nextFn = queue.shift();\n    nextFn();\n    setTimeout(executeNext, interval);\n  }\n\n  return function (...args) {\n    const callFn = () => fn.apply(this, args);\n    queue.push(callFn);\n\n    if (!isRunning) {\n      isRunning = true;\n      setTimeout(executeNext, interval);\n    }\n  };\n}\n","import pThrottle from 'p-throttle';\nimport { snapshot } from 'valtio/vanilla';\n\nimport { api } from './api';\nimport states, { saveStatus } from './states';\n\nexport const throttle = pThrottle({\n  limit: 1,\n  interval: 1000,\n});\n\nconst denylistDomains = /(twitter|github)\\.com/i;\nconst failedUnfurls = {};\nfunction _unfurlMastodonLink(instance, url) {\n  const snapStates = snapshot(states);\n  if (denylistDomains.test(url)) {\n    return;\n  }\n  if (failedUnfurls[url]) {\n    return;\n  }\n  const instanceRegex = new RegExp(instance + '/');\n  if (instanceRegex.test(snapStates.unfurledLinks[url]?.url)) {\n    return Promise.resolve(snapStates.unfurledLinks[url]);\n  }\n  console.debug('🦦 Unfurling URL', url);\n\n  let remoteInstanceFetch;\n  let theURL = url;\n\n  // https://elk.zone/domain.com/@stest/123 -> https://domain.com/@stest/123\n  if (/\\/\\/elk\\.[^\\/]+\\/[^\\/]+\\.[^\\/]+/i.test(theURL)) {\n    theURL = theURL.replace(/elk\\.[^\\/]+\\//i, '');\n  }\n\n  // https://trunks.social/status/domain.com/@stest/123 -> https://domain.com/@stest/123\n  if (/\\/\\/trunks\\.[^\\/]+\\/status\\/[^\\/]+\\.[^\\/]+/i.test(theURL)) {\n    theURL = theURL.replace(/trunks\\.[^\\/]+\\/status\\//i, '');\n  }\n\n  // https://phanpy.social/#/domain.com/s/123 -> https://domain.com/statuses/123\n  if (/\\/#\\/[^\\/]+\\.[^\\/]+\\/s\\/.+/i.test(theURL)) {\n    const urlAfterHash = theURL.split('/#/')[1];\n    const finalURL = urlAfterHash.replace(/\\/s\\//i, '/@fakeUsername/');\n    theURL = `https://${finalURL}`;\n  }\n\n  let urlObj;\n  try {\n    urlObj = new URL(theURL);\n  } catch (e) {\n    return;\n  }\n  const domain = urlObj.hostname;\n  const path = urlObj.pathname;\n  // Regex /:username/:id, where username = @username or @username@domain, id = number\n  const statusRegex = /\\/@([^@\\/]+)@?([^\\/]+)?\\/(\\d+)$/i;\n  const statusMatch = statusRegex.exec(path);\n  if (statusMatch) {\n    const id = statusMatch[3];\n    const { masto } = api({ instance: domain });\n    remoteInstanceFetch = masto.v1.statuses\n      .$select(id)\n      .fetch()\n      .then((status) => {\n        if (status?.id) {\n          return {\n            status,\n            instance: domain,\n          };\n        } else {\n          throw new Error('No results');\n        }\n      });\n  }\n\n  const { masto } = api({ instance });\n  const mastoSearchFetch = masto.v2.search\n    .fetch({\n      q: theURL,\n      type: 'statuses',\n      resolve: true,\n      limit: 1,\n    })\n    .then((results) => {\n      if (results.statuses.length > 0) {\n        const status = results.statuses[0];\n        return {\n          status,\n          instance,\n        };\n      } else {\n        throw new Error('No results');\n      }\n    });\n\n  function handleFulfill(result) {\n    const { status, instance } = result;\n    const { id } = status;\n    const selfURL = `/${instance}/s/${id}`;\n    console.debug('🦦 Unfurled URL', url, id, selfURL);\n    const data = {\n      id,\n      instance,\n      url: selfURL,\n    };\n    states.unfurledLinks[url] = data;\n    saveStatus(status, instance, {\n      skipThreading: true,\n    });\n    return data;\n  }\n  function handleCatch(e) {\n    failedUnfurls[url] = true;\n  }\n\n  if (remoteInstanceFetch) {\n    // return Promise.any([remoteInstanceFetch, mastoSearchFetch])\n    //   .then(handleFulfill)\n    //   .catch(handleCatch);\n    // If mastoSearchFetch is fulfilled within 3s, return it, else return remoteInstanceFetch\n    const finalPromise = Promise.race([\n      mastoSearchFetch,\n      new Promise((resolve, reject) => setTimeout(reject, 3000)),\n    ]).catch(() => {\n      // If remoteInstanceFetch is fullfilled, return it, else return mastoSearchFetch\n      return remoteInstanceFetch.catch(() => mastoSearchFetch);\n    });\n    return finalPromise.then(handleFulfill).catch(handleCatch);\n  } else {\n    return mastoSearchFetch.then(handleFulfill).catch(handleCatch);\n  }\n}\n\nconst unfurlMastodonLink = throttle(_unfurlMastodonLink);\nexport default unfurlMastodonLink;\n","import { proxy, subscribe } from 'valtio';\nimport { subscribeKey } from 'valtio/utils';\n\nimport { api } from './api';\nimport isMastodonLinkMaybe from './isMastodonLinkMaybe';\nimport pmem from './pmem';\nimport rateLimit from './ratelimit';\nimport store from './store';\nimport unfurlMastodonLink from './unfurl-link';\n\nconst states = proxy({\n  appVersion: {},\n  // history: [],\n  prevLocation: null,\n  currentLocation: null,\n  statuses: {},\n  statusThreadNumber: {},\n  home: [],\n  // specialHome: [],\n  homeNew: [],\n  homeLast: null, // Last item in 'home' list\n  homeLastFetchTime: null,\n  notifications: [],\n  notificationsLast: null, // Last read notification\n  notificationsNew: [],\n  notificationsShowNew: false,\n  notificationsLastFetchTime: null,\n  reloadStatusPage: 0,\n  reloadGenericAccounts: {\n    id: null,\n    counter: 0,\n  },\n  spoilers: {},\n  spoilersMedia: {},\n  scrollPositions: {},\n  unfurledLinks: {},\n  statusQuotes: {},\n  statusFollowedTags: {},\n  accounts: {},\n  routeNotification: null,\n  // Modals\n  showCompose: false,\n  showSettings: false,\n  showAccount: false,\n  showAccounts: false,\n  showDrafts: false,\n  showMediaModal: false,\n  showShortcutsSettings: false,\n  showKeyboardShortcutsHelp: false,\n  showGenericAccounts: false,\n  showMediaAlt: false,\n  // Shortcuts\n  shortcuts: [],\n  // Settings\n  settings: {\n    autoRefresh: false,\n    shortcutsViewMode: null,\n    shortcutsColumnsMode: false,\n    boostsCarousel: true,\n    contentTranslation: true,\n    contentTranslationTargetLanguage: null,\n    contentTranslationHideLanguages: [],\n    contentTranslationAutoInline: false,\n    mediaAltGenerator: false,\n    cloakMode: false,\n  },\n});\n\nexport default states;\n\nexport function initStates() {\n  // init all account based states\n  // all keys that uses store.account.get() should be initialized here\n  states.notificationsLast = store.account.get('notificationsLast') || null;\n  states.shortcuts = store.account.get('shortcuts') ?? [];\n  states.settings.autoRefresh =\n    store.account.get('settings-autoRefresh') ?? false;\n  states.settings.shortcutsViewMode =\n    store.account.get('settings-shortcutsViewMode') ?? null;\n  if (store.account.get('settings-shortcutsColumnsMode')) {\n    states.settings.shortcutsColumnsMode = true;\n  }\n  states.settings.boostsCarousel =\n    store.account.get('settings-boostsCarousel') ?? true;\n  states.settings.contentTranslation =\n    store.account.get('settings-contentTranslation') ?? true;\n  states.settings.contentTranslationTargetLanguage =\n    store.account.get('settings-contentTranslationTargetLanguage') || null;\n  states.settings.contentTranslationHideLanguages =\n    store.account.get('settings-contentTranslationHideLanguages') || [];\n  states.settings.contentTranslationAutoInline =\n    store.account.get('settings-contentTranslationAutoInline') ?? false;\n  states.settings.mediaAltGenerator =\n    store.account.get('settings-mediaAltGenerator') ?? false;\n  states.settings.cloakMode = store.account.get('settings-cloakMode') ?? false;\n}\n\nsubscribeKey(states, 'notificationsLast', (v) => {\n  console.log('CHANGE', v);\n  store.account.set('notificationsLast', states.notificationsLast);\n});\nsubscribe(states, (changes) => {\n  console.debug('STATES change', changes);\n  for (const [action, path, value, prevValue] of changes) {\n    if (path.join('.') === 'settings.autoRefresh') {\n      store.account.set('settings-autoRefresh', !!value);\n    }\n    if (path.join('.') === 'settings.boostsCarousel') {\n      store.account.set('settings-boostsCarousel', !!value);\n    }\n    if (path.join('.') === 'settings.shortcutsViewMode') {\n      store.account.set('settings-shortcutsViewMode', value);\n    }\n    if (path.join('.') === 'settings.contentTranslation') {\n      store.account.set('settings-contentTranslation', !!value);\n    }\n    if (path.join('.') === 'settings.contentTranslationAutoInline') {\n      store.account.set('settings-contentTranslationAutoInline', !!value);\n    }\n    if (path.join('.') === 'settings.contentTranslationTargetLanguage') {\n      console.log('SET', value);\n      store.account.set('settings-contentTranslationTargetLanguage', value);\n    }\n    if (/^settings\\.contentTranslationHideLanguages/i.test(path.join('.'))) {\n      store.account.set(\n        'settings-contentTranslationHideLanguages',\n        states.settings.contentTranslationHideLanguages,\n      );\n    }\n    if (path.join('.') === 'settings.mediaAltGenerator') {\n      store.account.set('settings-mediaAltGenerator', !!value);\n    }\n    if (path?.[0] === 'shortcuts') {\n      store.account.set('shortcuts', states.shortcuts);\n    }\n    if (path.join('.') === 'settings.cloakMode') {\n      store.account.set('settings-cloakMode', !!value);\n    }\n  }\n});\n\nexport function hideAllModals() {\n  states.showCompose = false;\n  states.showSettings = false;\n  states.showAccount = false;\n  states.showAccounts = false;\n  states.showDrafts = false;\n  states.showMediaModal = false;\n  states.showShortcutsSettings = false;\n  states.showKeyboardShortcutsHelp = false;\n  states.showGenericAccounts = false;\n  states.showMediaAlt = false;\n}\n\nexport function statusKey(id, instance) {\n  if (!id) return;\n  return instance ? `${instance}/${id}` : id;\n}\n\nexport function getStatus(statusID, instance) {\n  if (instance) {\n    const key = statusKey(statusID, instance);\n    return states.statuses[key];\n  }\n  return states.statuses[statusID];\n}\n\nexport function saveStatus(status, instance, opts) {\n  if (typeof instance === 'object') {\n    opts = instance;\n    instance = null;\n  }\n  const {\n    override = true,\n    skipThreading = false,\n    skipUnfurling = false,\n  } = opts || {};\n  if (!status) return;\n  const oldStatus = getStatus(status.id, instance);\n  if (!override && oldStatus) return;\n  queueMicrotask(() => {\n    const key = statusKey(status.id, instance);\n    if (oldStatus?._pinned) status._pinned = oldStatus._pinned;\n    // if (oldStatus?._filtered) status._filtered = oldStatus._filtered;\n    states.statuses[key] = status;\n    if (status.reblog) {\n      const key = statusKey(status.reblog.id, instance);\n      states.statuses[key] = status.reblog;\n    }\n  });\n\n  // THREAD TRAVERSER\n  if (!skipThreading) {\n    queueMicrotask(() => {\n      threadifyStatus(status, instance);\n      if (status.reblog) {\n        queueMicrotask(() => {\n          threadifyStatus(status.reblog, instance);\n        });\n      }\n    });\n  }\n\n  // UNFURLER\n  if (!skipUnfurling) {\n    queueMicrotask(() => {\n      unfurlStatus(status, instance);\n    });\n  }\n}\n\nfunction _threadifyStatus(status, propInstance) {\n  const { masto, instance } = api({ instance: propInstance });\n  // Return all statuses in the thread, via inReplyToId, if inReplyToAccountId === account.id\n  let fetchIndex = 0;\n  async function traverse(status, index = 0) {\n    const { inReplyToId, inReplyToAccountId } = status;\n    if (!inReplyToId || inReplyToAccountId !== status.account.id) {\n      return [status];\n    }\n    if (inReplyToId && inReplyToAccountId !== status.account.id) {\n      throw 'Not a thread';\n      // Possibly thread of replies by multiple people?\n    }\n    const key = statusKey(inReplyToId, instance);\n    let prevStatus = states.statuses[key];\n    if (!prevStatus) {\n      if (fetchIndex++ > 3) throw 'Too many fetches for thread'; // Some people revive old threads\n      await new Promise((r) => setTimeout(r, 500 * fetchIndex)); // Be nice to rate limits\n      // prevStatus = await masto.v1.statuses.$.select(inReplyToId).fetch();\n      prevStatus = await fetchStatus(inReplyToId, masto);\n      saveStatus(prevStatus, instance, { skipThreading: true });\n    }\n    // Prepend so that first status in thread will be index 0\n    return [...(await traverse(prevStatus, ++index)), status];\n  }\n  return traverse(status)\n    .then((statuses) => {\n      if (statuses.length > 1) {\n        console.debug('THREAD', statuses);\n        statuses.forEach((status, index) => {\n          const key = statusKey(status.id, instance);\n          states.statusThreadNumber[key] = index + 1;\n        });\n      }\n    })\n    .catch((e) => {\n      console.error(e, status);\n    });\n}\nexport const threadifyStatus = rateLimit(_threadifyStatus, 100);\n\nconst fauxDiv = document.createElement('div');\nexport function unfurlStatus(status, instance) {\n  const { instance: currentInstance } = api();\n  const content = status.reblog?.content || status.content;\n  const hasLink = /<a/i.test(content);\n  if (hasLink) {\n    const sKey = statusKey(status?.reblog?.id || status?.id, instance);\n    fauxDiv.innerHTML = content;\n    const links = fauxDiv.querySelectorAll(\n      'a[href]:not(.u-url):not(.mention):not(.hashtag)',\n    );\n    [...links]\n      .filter((a) => {\n        const url = a.href;\n        const isPostItself = url === status.url || url === status.uri;\n        return !isPostItself && isMastodonLinkMaybe(url);\n      })\n      .forEach((a, i) => {\n        unfurlMastodonLink(currentInstance, a.href).then((result) => {\n          if (!result) return;\n          if (!sKey) return;\n          if (!Array.isArray(states.statusQuotes[sKey])) {\n            states.statusQuotes[sKey] = [];\n          }\n          if (!states.statusQuotes[sKey][i]) {\n            states.statusQuotes[sKey].splice(i, 0, result);\n          }\n        });\n      });\n  }\n}\n\nconst fetchStatus = pmem((statusID, masto) => {\n  return masto.v1.statuses.$select(statusID).fetch();\n});\n","import { useEffect, useRef } from 'preact/hooks';\n\nfunction useInterval(fn, delay, deps, immediate) {\n  const savedCallback = useRef(fn);\n  useEffect(() => {\n    savedCallback.current = fn;\n  }, [fn, deps]);\n\n  useEffect(() => {\n    if (!immediate || delay === null || delay === false) return;\n    savedCallback.current();\n  }, [immediate]);\n\n  useEffect(() => {\n    if (delay === null || delay === false) return;\n    const tick = () => savedCallback.current();\n    const id = setInterval(tick, delay);\n    return () => clearInterval(id);\n  }, [delay]);\n}\n\nexport default useInterval;\n","export default function openCompose(opts) {\n  const url = new URL('/compose/', window.location);\n  const { width: screenWidth, height: screenHeight } = window.screen;\n  const left = Math.max(0, (screenWidth - 600) / 2);\n  const top = Math.max(0, (screenHeight - 450) / 2);\n  const width = Math.min(screenWidth, 600);\n  const height = Math.min(screenHeight, 450);\n  const winUID = opts?.uid || Math.random();\n  const newWin = window.open(\n    url,\n    'compose' + winUID,\n    `width=${width},height=${height},left=${left},top=${top}`,\n  );\n\n  if (newWin) {\n    // if (masto) {\n    //   newWin.masto = masto;\n    // }\n\n    newWin.__COMPOSE__ = opts;\n  } else {\n    alert('Looks like your browser is blocking popups.');\n  }\n\n  return newWin;\n}\n","import { useEffect, useRef, useState } from 'preact/hooks';\n\nconst SIZES = {\n  s: 12,\n  m: 16,\n  l: 20,\n  xl: 24,\n  xxl: 32,\n};\n\nexport const ICONS = {\n  x: () => import('@iconify-icons/mingcute/close-line'),\n  heart: () => import('@iconify-icons/mingcute/heart-line'),\n  bookmark: () => import('@iconify-icons/mingcute/bookmark-line'),\n  'check-circle': () => import('@iconify-icons/mingcute/check-circle-line'),\n  'x-circle': () => import('@iconify-icons/mingcute/close-circle-line'),\n  transfer: () => import('@iconify-icons/mingcute/transfer-4-line'),\n  rocket: () => import('@iconify-icons/mingcute/rocket-line'),\n  'arrow-left': () => import('@iconify-icons/mingcute/arrow-left-line'),\n  'arrow-right': () => import('@iconify-icons/mingcute/arrow-right-line'),\n  'arrow-up': () => import('@iconify-icons/mingcute/arrow-up-line'),\n  'arrow-down': () => import('@iconify-icons/mingcute/arrow-down-line'),\n  earth: () => import('@iconify-icons/mingcute/earth-line'),\n  lock: () => import('@iconify-icons/mingcute/lock-line'),\n  unlock: () => import('@iconify-icons/mingcute/unlock-line'),\n  'eye-close': () => import('@iconify-icons/mingcute/eye-close-line'),\n  'eye-open': () => import('@iconify-icons/mingcute/eye-2-line'),\n  message: () => import('@iconify-icons/mingcute/mail-line'),\n  comment: () => import('@iconify-icons/mingcute/chat-3-line'),\n  comment2: () => import('@iconify-icons/mingcute/comment-2-line'),\n  home: () => import('@iconify-icons/mingcute/home-3-line'),\n  notification: () => import('@iconify-icons/mingcute/notification-line'),\n  follow: () => import('@iconify-icons/mingcute/user-follow-line'),\n  'follow-add': () => import('@iconify-icons/mingcute/user-add-line'),\n  poll: [() => import('@iconify-icons/mingcute/chart-bar-line'), '90deg'],\n  pencil: () => import('@iconify-icons/mingcute/pencil-line'),\n  quill: () => import('@iconify-icons/mingcute/quill-pen-line'),\n  at: () => import('@iconify-icons/mingcute/at-line'),\n  attachment: () => import('@iconify-icons/mingcute/attachment-line'),\n  upload: () => import('@iconify-icons/mingcute/upload-3-line'),\n  gear: () => import('@iconify-icons/mingcute/settings-3-line'),\n  more: () => import('@iconify-icons/mingcute/more-3-line'),\n  external: () => import('@iconify-icons/mingcute/external-link-line'),\n  popout: () => import('@iconify-icons/mingcute/external-link-line'),\n  popin: [() => import('@iconify-icons/mingcute/external-link-line'), '180deg'],\n  plus: () => import('@iconify-icons/mingcute/add-circle-line'),\n  'chevron-left': () => import('@iconify-icons/mingcute/left-line'),\n  'chevron-right': () => import('@iconify-icons/mingcute/right-line'),\n  'chevron-down': () => import('@iconify-icons/mingcute/down-line'),\n  reply: [\n    () => import('@iconify-icons/mingcute/share-forward-line'),\n    '180deg',\n    'horizontal',\n  ],\n  thread: () => import('@iconify-icons/mingcute/route-line'),\n  group: () => import('@iconify-icons/mingcute/group-line'),\n  bot: () => import('@iconify-icons/mingcute/android-2-line'),\n  menu: () => import('@iconify-icons/mingcute/rows-4-line'),\n  list: () => import('@iconify-icons/mingcute/list-check-line'),\n  search: () => import('@iconify-icons/mingcute/search-2-line'),\n  hashtag: () => import('@iconify-icons/mingcute/hashtag-line'),\n  info: () => import('@iconify-icons/mingcute/information-line'),\n  shortcut: () => import('@iconify-icons/mingcute/lightning-line'),\n  user: () => import('@iconify-icons/mingcute/user-4-line'),\n  following: () => import('@iconify-icons/mingcute/walk-line'),\n  pin: () => import('@iconify-icons/mingcute/pin-line'),\n  bus: () => import('@iconify-icons/mingcute/bus-2-line'),\n  link: () => import('@iconify-icons/mingcute/link-2-line'),\n  history: () => import('@iconify-icons/mingcute/history-line'),\n  share: () => import('@iconify-icons/mingcute/share-2-line'),\n  sparkles: () => import('@iconify-icons/mingcute/sparkles-line'),\n  sparkles2: () => import('@iconify-icons/mingcute/sparkles-2-line'),\n  exit: () => import('@iconify-icons/mingcute/exit-line'),\n  translate: () => import('@iconify-icons/mingcute/translate-line'),\n  play: () => import('@iconify-icons/mingcute/play-fill'),\n  trash: () => import('@iconify-icons/mingcute/delete-2-line'),\n  mute: () => import('@iconify-icons/mingcute/volume-mute-line'),\n  unmute: () => import('@iconify-icons/mingcute/volume-line'),\n  block: () => import('@iconify-icons/mingcute/forbid-circle-line'),\n  unblock: [\n    () => import('@iconify-icons/mingcute/forbid-circle-line'),\n    '180deg',\n  ],\n  flag: () => import('@iconify-icons/mingcute/flag-4-line'),\n  time: () => import('@iconify-icons/mingcute/time-line'),\n  refresh: () => import('@iconify-icons/mingcute/refresh-2-line'),\n  emoji2: () => import('@iconify-icons/mingcute/emoji-2-line'),\n  filter: () => import('@iconify-icons/mingcute/filter-2-line'),\n  chart: () => import('@iconify-icons/mingcute/chart-line-line'),\n  react: () => import('@iconify-icons/mingcute/react-line'),\n  layout4: () => import('@iconify-icons/mingcute/layout-4-line'),\n  layout5: () => import('@iconify-icons/mingcute/layout-5-line'),\n  announce: () => import('@iconify-icons/mingcute/announcement-line'),\n  alert: () => import('@iconify-icons/mingcute/alert-line'),\n  round: () => import('@iconify-icons/mingcute/round-fill'),\n  'arrow-up-circle': () =>\n    import('@iconify-icons/mingcute/arrow-up-circle-line'),\n  'arrow-down-circle': () =>\n    import('@iconify-icons/mingcute/arrow-down-circle-line'),\n  clipboard: () => import('@iconify-icons/mingcute/clipboard-line'),\n  'account-edit': () => import('@iconify-icons/mingcute/user-edit-line'),\n  'account-warning': () => import('@iconify-icons/mingcute/user-warning-line'),\n  keyboard: () => import('@iconify-icons/mingcute/keyboard-line'),\n  cloud: () => import('@iconify-icons/mingcute/cloud-line'),\n  month: () => import('@iconify-icons/mingcute/calendar-month-line'),\n  media: () => import('@iconify-icons/mingcute/photo-album-line'),\n  speak: () => import('@iconify-icons/mingcute/radar-line'),\n  building: () => import('@iconify-icons/mingcute/building-5-line'),\n};\n\nconst ICONDATA = {};\n\nfunction Icon({\n  icon,\n  size = 'm',\n  alt,\n  title,\n  class: className = '',\n  style = {},\n}) {\n  if (!icon) return null;\n\n  const iconSize = SIZES[size];\n  let iconBlock = ICONS[icon];\n  let rotate, flip;\n  if (Array.isArray(iconBlock)) {\n    [iconBlock, rotate, flip] = iconBlock;\n  }\n\n  const [iconData, setIconData] = useState(ICONDATA[icon]);\n  const currentIcon = useRef(icon);\n  useEffect(() => {\n    if (iconData && currentIcon.current === icon) return;\n    (async () => {\n      const iconB = await iconBlock();\n      setIconData(iconB.default);\n      ICONDATA[icon] = iconB.default;\n    })();\n    currentIcon.current = icon;\n  }, [icon]);\n\n  return (\n    <span\n      class={`icon ${className}`}\n      title={title || alt}\n      style={{\n        width: `${iconSize}px`,\n        height: `${iconSize}px`,\n        ...style,\n      }}\n    >\n      {iconData && (\n        <svg\n          width={iconSize}\n          height={iconSize}\n          viewBox={`0 0 ${iconData.width} ${iconData.height}`}\n          dangerouslySetInnerHTML={{ __html: iconData.body }}\n          style={{\n            transform: `${rotate ? `rotate(${rotate})` : ''} ${\n              flip ? `scaleX(-1)` : ''\n            }`,\n          }}\n        />\n      )}\n    </span>\n  );\n}\n\nexport default Icon;\n","import { useEffect } from 'preact/hooks';\n\nfunction useCloseWatcher(fn, deps = []) {\n  if (!fn || typeof fn !== 'function') return;\n  useEffect(() => {\n    const watcher = new CloseWatcher();\n    watcher.addEventListener('close', fn);\n    return () => {\n      watcher.destroy();\n    };\n  }, deps);\n}\n\nexport default window.CloseWatcher ? useCloseWatcher : () => {};\n","import './modal.css';\n\nimport { createPortal } from 'preact/compat';\nimport { useEffect, useRef } from 'preact/hooks';\nimport { useHotkeys } from 'react-hotkeys-hook';\n\nimport useCloseWatcher from '../utils/useCloseWatcher';\n\nconst $modalContainer = document.getElementById('modal-container');\n\nfunction Modal({ children, onClose, onClick, class: className }) {\n  if (!children) return null;\n\n  const modalRef = useRef();\n  useEffect(() => {\n    let timer = setTimeout(() => {\n      const focusElement = modalRef.current?.querySelector('[tabindex=\"-1\"]');\n      if (focusElement) {\n        focusElement.focus();\n      }\n    }, 100);\n    return () => clearTimeout(timer);\n  }, []);\n\n  const supportsCloseWatcher = window.CloseWatcher;\n  const escRef = useHotkeys(\n    'esc',\n    () => {\n      setTimeout(() => {\n        onClose?.();\n      }, 0);\n    },\n    {\n      enabled: !supportsCloseWatcher && !!onClose,\n      // Using keyup and setTimeout above\n      // This will run \"later\" to prevent clash with esc handlers from other components\n      keydown: false,\n      keyup: true,\n    },\n    [onClose],\n  );\n  useCloseWatcher(onClose, [onClose]);\n\n  const Modal = (\n    <div\n      ref={(node) => {\n        modalRef.current = node;\n        escRef.current = node?.querySelector?.('[tabindex=\"-1\"]') || node;\n      }}\n      className={className}\n      onClick={(e) => {\n        onClick?.(e);\n        if (e.target === e.currentTarget) {\n          onClose?.(e);\n        }\n      }}\n      tabIndex=\"-1\"\n      onFocus={(e) => {\n        if (e.target === e.currentTarget) {\n          modalRef.current?.querySelector?.('[tabindex=\"-1\"]')?.focus?.();\n        }\n      }}\n    >\n      {children}\n    </div>\n  );\n\n  return createPortal(Modal, $modalContainer);\n\n  // return createPortal(children, $modalContainer);\n}\n\nexport default Modal;\n","import './loader.css';\n\nfunction Loader({ abrupt, hidden, ...props }) {\n  return (\n    <span\n      {...props}\n      class={`loader-container ${abrupt ? 'abrupt' : ''} ${\n        hidden ? 'hidden' : ''\n      }`}\n    >\n      <span class=\"loader\" />\n    </span>\n  );\n}\n\nexport default Loader;\n","import './avatar.css';\n\nimport { useRef } from 'preact/hooks';\n\nimport mem from '../utils/mem';\n\nconst SIZES = {\n  s: 16,\n  m: 20,\n  l: 24,\n  xl: 32,\n  xxl: 50,\n  xxxl: 64,\n};\n\nconst alphaCache = {};\n\nconst canvas = window.OffscreenCanvas\n  ? new OffscreenCanvas(1, 1)\n  : document.createElement('canvas');\nconst ctx = canvas.getContext('2d', {\n  willReadFrequently: true,\n});\n\nfunction Avatar({ url, size, alt = '', squircle, ...props }) {\n  size = SIZES[size] || size || SIZES.m;\n  const avatarRef = useRef();\n  const isMissing = /missing\\.png$/.test(url);\n  return (\n    <span\n      ref={avatarRef}\n      class={`avatar ${squircle ? 'squircle' : ''} ${\n        alphaCache[url] ? 'has-alpha' : ''\n      }`}\n      style={{\n        width: size,\n        height: size,\n      }}\n      title={alt}\n      {...props}\n    >\n      {!!url && (\n        <img\n          src={url}\n          width={size}\n          height={size}\n          alt={alt}\n          loading=\"lazy\"\n          decoding=\"async\"\n          crossOrigin={\n            alphaCache[url] === undefined && !isMissing\n              ? 'anonymous'\n              : undefined\n          }\n          onError={(e) => {\n            if (e.target.crossOrigin) {\n              e.target.crossOrigin = null;\n              e.target.src = url;\n            }\n          }}\n          onLoad={(e) => {\n            if (avatarRef.current) avatarRef.current.dataset.loaded = true;\n            if (alphaCache[url] !== undefined) return;\n            if (isMissing) return;\n            queueMicrotask(() => {\n              try {\n                // Check if image has alpha channel\n                const { width, height } = e.target;\n                if (canvas.width !== width) canvas.width = width;\n                if (canvas.height !== height) canvas.height = height;\n                ctx.drawImage(e.target, 0, 0);\n                const allPixels = ctx.getImageData(0, 0, width, height);\n                // At least 10% of pixels have alpha <= 128\n                const hasAlpha =\n                  allPixels.data.filter(\n                    (pixel, i) => i % 4 === 3 && pixel <= 128,\n                  ).length /\n                    (allPixels.data.length / 4) >\n                  0.1;\n                if (hasAlpha) {\n                  // console.log('hasAlpha', hasAlpha, allPixels.data);\n                  avatarRef.current.classList.add('has-alpha');\n                }\n                alphaCache[url] = hasAlpha;\n                ctx.clearRect(0, 0, width, height);\n              } catch (e) {\n                // Silent fail\n                alphaCache[url] = false;\n              }\n            });\n          }}\n        />\n      )}\n    </span>\n  );\n}\n\nexport default mem(Avatar);\n","import { forwardRef } from 'preact/compat';\nimport { useLocation } from 'react-router-dom';\n\nimport states from '../utils/states';\n\n/* NOTES\n   =====\n   Initially this uses <NavLink> from react-router-dom, but it doesn't work:\n   1. It interferes with nested <a> inside <a> and it's difficult to preventDefault/stopPropagation from the nested <a>\n   2. isActive doesn't work properly with the weird routes that's set up in this app, due to the faux \"location\" to make the modals work and prevent unmounting\n   3. Not using <Link state/> because it modifies history.state that *persists* across page reloads. I don't need that, so using valtio's states instead.\n*/\n\nconst Link = forwardRef((props, ref) => {\n  let routerLocation;\n  try {\n    routerLocation = useLocation();\n  } catch (e) {}\n  let hash = (location.hash || '').replace(/^#/, '').trim();\n  if (hash === '') hash = '/';\n  const { to, ...restProps } = props;\n\n  // Handle encodeURIComponent of searchParams values\n  if (!!hash && hash !== '/' && hash.includes('?')) {\n    try {\n      const parsedHash = new URL(hash, location.origin); // Fake base URL\n      if (parsedHash.searchParams.size) {\n        const searchParamsStr = Array.from(parsedHash.searchParams.entries())\n          .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)\n          .join('&');\n        hash = parsedHash.pathname + '?' + searchParamsStr;\n      }\n    } catch (e) {}\n  }\n\n  const isActive = hash === to || decodeURIComponent(hash) === to;\n  return (\n    <a\n      ref={ref}\n      href={`#${to}`}\n      {...restProps}\n      class={`${props.class || ''} ${isActive ? 'is-active' : ''}`}\n      onClick={(e) => {\n        if (e.currentTarget?.parentNode?.closest('a')) {\n          // If this <a> is nested inside another <a>\n          e.stopPropagation();\n        }\n        if (routerLocation) states.prevLocation = routerLocation;\n        props.onClick?.(e);\n      }}\n    />\n  );\n});\n\nexport default Link;\n","const root = document.documentElement;\nconst style = getComputedStyle(root);\nconst defaultBoundingBoxPadding = 8;\n\nlet safeAreaInsets = [0, 0, 0, 0];\nfunction getSafeAreaInsets() {\n  // Get safe area inset variables from root\n  const safeAreaInsetTop = style.getPropertyValue('--sai-top');\n  const safeAreaInsetRight = style.getPropertyValue('--sai-right');\n  const safeAreaInsetBottom = style.getPropertyValue('--sai-bottom');\n  const safeAreaInsetLeft = style.getPropertyValue('--sai-left');\n  safeAreaInsets = [\n    // top, right, bottom, left (clockwise)\n    Math.max(0, parseInt(safeAreaInsetTop, 10)),\n    Math.max(0, parseInt(safeAreaInsetRight, 10)),\n    Math.max(0, parseInt(safeAreaInsetBottom, 10)),\n    Math.max(0, parseInt(safeAreaInsetLeft, 10)),\n  ];\n}\nrequestAnimationFrame(getSafeAreaInsets);\n\nfunction safeBoundingBoxPadding(paddings = []) {\n  const str = safeAreaInsets\n    .map((v, i) => (v || defaultBoundingBoxPadding) + (paddings[i] || 0))\n    .join(' ');\n  // console.log(str);\n  return str;\n}\n\n// Update safe area insets when orientation or resize\nif (CSS.supports('top: env(safe-area-inset-top)')) {\n  window.addEventListener('resize', getSafeAreaInsets, { passive: true });\n}\n\nexport default safeBoundingBoxPadding;\n","import { Menu } from '@szhsin/react-menu';\nimport { useWindowSize } from '@uidotdev/usehooks';\nimport { useRef } from 'preact/hooks';\n\nimport safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';\n\n// It's like Menu but with sensible defaults, bug fixes and improvements.\nfunction Menu2(props) {\n  const { containerProps, instanceRef: _instanceRef } = props;\n  const size = useWindowSize();\n  const instanceRef = _instanceRef?.current ? _instanceRef : useRef();\n\n  return (\n    <Menu\n      boundingBoxPadding={safeBoundingBoxPadding()}\n      repositionFlag={`${size.width}x${size.height}`}\n      unmountOnClose\n      {...props}\n      instanceRef={instanceRef}\n      containerProps={{\n        onClick: (e) => {\n          if (e.target === e.currentTarget) {\n            instanceRef.current?.closeMenu?.();\n          }\n          containerProps?.onClick?.(e);\n        },\n        ...containerProps,\n      }}\n    />\n  );\n}\n\nexport default Menu2;\n","import { Menu, MenuItem, SubMenu } from '@szhsin/react-menu';\nimport { cloneElement } from 'preact';\nimport { useRef } from 'preact/hooks';\n\nimport Menu2 from './menu2';\n\nfunction MenuConfirm({\n  subMenu = false,\n  confirm = true,\n  confirmLabel,\n  menuItemClassName,\n  menuFooter,\n  ...props\n}) {\n  const { children, onClick, ...restProps } = props;\n  if (!confirm) {\n    if (subMenu) return <MenuItem {...props} />;\n    if (onClick) {\n      return cloneElement(children, {\n        onClick,\n      });\n    }\n    return children;\n  }\n  const Parent = subMenu ? SubMenu : Menu2;\n  const menuRef = useRef();\n  return (\n    <Parent\n      instanceRef={menuRef}\n      openTrigger=\"clickOnly\"\n      direction=\"bottom\"\n      overflow=\"auto\"\n      gap={-8}\n      shift={8}\n      menuClassName=\"menu-emphasized\"\n      {...restProps}\n      menuButton={subMenu ? undefined : children}\n      label={subMenu ? children : undefined}\n      // Test fix for bug; submenus not opening on Android\n      itemProps={{\n        onPointerMove: (e) => {\n          if (e.pointerType === 'touch') {\n            menuRef.current?.openMenu?.();\n          }\n        },\n        onPointerLeave: (e) => {\n          if (e.pointerType === 'touch') {\n            menuRef.current?.openMenu?.();\n          }\n        },\n      }}\n    >\n      <MenuItem className={menuItemClassName} onClick={onClick}>\n        {confirmLabel}\n      </MenuItem>\n      {menuFooter}\n    </Parent>\n  );\n}\n\nexport default MenuConfirm;\n","function EmojiText({ text, emojis }) {\n  if (!text) return '';\n  if (!emojis?.length) return text;\n  if (text.indexOf(':') === -1) return text;\n  const regex = new RegExp(\n    `:(${emojis.map((e) => e.shortcode).join('|')}):`,\n    'g',\n  );\n  const elements = text.split(regex).map((word) => {\n    const emoji = emojis.find((e) => e.shortcode === word);\n    if (emoji) {\n      const { url, staticUrl } = emoji;\n      return (\n        <picture>\n          <source srcset={staticUrl} media=\"(prefers-reduced-motion: reduce)\" />\n          <img\n            key={word}\n            src={url}\n            alt={word}\n            class=\"shortcode-emoji emoji\"\n            width=\"16\"\n            height=\"16\"\n            loading=\"lazy\"\n            decoding=\"async\"\n          />\n        </picture>\n      );\n    }\n    return word;\n  });\n  return elements;\n}\n\nexport default EmojiText;\n","import './name-text.css';\n\nimport { memo } from 'preact/compat';\n\nimport states from '../utils/states';\n\nimport Avatar from './avatar';\nimport EmojiText from './emoji-text';\n\nconst nameCollator = new Intl.Collator('en', {\n  sensitivity: 'base',\n});\n\nfunction NameText({\n  account,\n  instance,\n  showAvatar,\n  showAcct,\n  short,\n  external,\n  onClick,\n}) {\n  const { acct, avatar, avatarStatic, id, url, displayName, emojis, bot } =\n    account;\n  let { username } = account;\n  const [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct];\n\n  const trimmedUsername = username.toLowerCase().trim();\n  const trimmedDisplayName = (displayName || '').toLowerCase().trim();\n  const shortenedDisplayName = trimmedDisplayName\n    .replace(/(\\:(\\w|\\+|\\-)+\\:)(?=|[\\!\\.\\?]|$)/g, '') // Remove shortcodes, regex from https://regex101.com/r/iE9uV0/1\n    .replace(/\\s+/g, ''); // E.g. \"My name\" === \"myname\"\n  const shortenedAlphaNumericDisplayName = shortenedDisplayName.replace(\n    /[^a-z0-9]/gi,\n    '',\n  ); // Remove non-alphanumeric characters\n\n  if (\n    !short &&\n    (trimmedUsername === trimmedDisplayName ||\n      trimmedUsername === shortenedDisplayName ||\n      trimmedUsername === shortenedAlphaNumericDisplayName ||\n      nameCollator.compare(trimmedUsername, shortenedDisplayName) === 0)\n  ) {\n    username = null;\n  }\n\n  return (\n    <a\n      class={`name-text ${showAcct ? 'show-acct' : ''} ${short ? 'short' : ''}`}\n      href={url}\n      target={external ? '_blank' : null}\n      title={`${displayName ? `${displayName} ` : ''}@${acct}`}\n      onClick={(e) => {\n        if (external) return;\n        e.preventDefault();\n        e.stopPropagation();\n        if (onClick) return onClick(e);\n        states.showAccount = {\n          account,\n          instance,\n        };\n      }}\n    >\n      {showAvatar && (\n        <>\n          <Avatar url={avatarStatic || avatar} squircle={bot} />{' '}\n        </>\n      )}\n      {displayName && !short ? (\n        <>\n          <b>\n            <EmojiText text={displayName} emojis={emojis} />\n          </b>\n          {!showAcct && username && (\n            <>\n              {' '}\n              <i>@{username}</i>\n            </>\n          )}\n        </>\n      ) : short ? (\n        <i>{username}</i>\n      ) : (\n        <b>{username}</b>\n      )}\n      {showAcct && (\n        <>\n          <br />\n          <i>\n            @{acct1}\n            <span class=\"ib\">{acct2}</span>\n          </i>\n        </>\n      )}\n    </a>\n  );\n}\n\nexport default memo(NameText);\n","// Twitter-style relative time component\n// Seconds = 1s\n// Minutes = 1m\n// Hours = 1h\n// Days = 1d\n// After 7 days, use DD/MM/YYYY or MM/DD/YYYY\nimport dayjs from 'dayjs';\nimport dayjsTwitter from 'dayjs-twitter';\nimport localizedFormat from 'dayjs/plugin/localizedFormat';\nimport relativeTime from 'dayjs/plugin/relativeTime';\n\ndayjs.extend(dayjsTwitter);\ndayjs.extend(localizedFormat);\ndayjs.extend(relativeTime);\n\nconst dtf = new Intl.DateTimeFormat();\n\nexport default function RelativeTime({ datetime, format }) {\n  if (!datetime) return null;\n  const date = dayjs(datetime);\n  let dateStr;\n  if (format === 'micro') {\n    // If date <= 1 day ago or day is within this year\n    const now = dayjs();\n    const dayDiff = now.diff(date, 'day');\n    if (dayDiff <= 1 || now.year() === date.year()) {\n      dateStr = date.twitter();\n    } else {\n      dateStr = dtf.format(date.toDate());\n    }\n  } else {\n    dateStr = date.fromNow();\n  }\n\n  return (\n    <time datetime={date.toISOString()} title={date.format('LLLL')}>\n      {dateStr}\n    </time>\n  );\n}\n","import { match } from '@formatjs/intl-localematcher';\n\nimport mem from './mem';\n\nfunction _localeMatch(...args) {\n  // Wrap in try/catch because localeMatcher throws on invalid locales\n  try {\n    return match(...args);\n  } catch (e) {\n    const defaultLocale = args[2];\n    return defaultLocale || false;\n  }\n}\nconst localeMatch = mem(_localeMatch);\n\nexport default localeMatch;\n","import translationTargetLanguages from '../data/lingva-target-languages';\n\nimport localeMatch from './locale-match';\nimport states from './states';\n\nconst locales = [\n  new Intl.DateTimeFormat().resolvedOptions().locale,\n  ...navigator.languages,\n];\n\nconst localeTargetLanguages = localeMatch(\n  locales,\n  translationTargetLanguages.map((l) => l.code.replace('_', '-')), // The underscore will fail Intl.Locale inside `match`\n  'en',\n);\n\nfunction getTranslateTargetLanguage(fromSettings = false) {\n  if (fromSettings) {\n    const { contentTranslationTargetLanguage } = states.settings;\n    if (contentTranslationTargetLanguage) {\n      return contentTranslationTargetLanguage;\n    }\n  }\n  return localeTargetLanguages;\n}\n\nexport default getTranslateTargetLanguage;\n","export default function localeCode2Text(code) {\n  try {\n    return new Intl.DisplayNames(navigator.languages, {\n      type: 'language',\n    }).of(code);\n  } catch (e) {\n    console.error(e);\n    return null;\n  }\n}\n","function emojifyText(text, emojis = []) {\n  if (!text) return '';\n  if (!emojis.length) return text;\n  if (text.indexOf(':') === -1) return text;\n  // Replace shortcodes in text with emoji\n  // emojis = [{ shortcode: 'smile', url: 'https://example.com/emoji.png' }]\n  emojis.forEach((emoji) => {\n    const { shortcode, staticUrl, url } = emoji;\n    text = text.replace(\n      new RegExp(`:${shortcode}:`, 'g'),\n      `<picture><source srcset=\"${staticUrl}\" media=\"(prefers-reduced-motion: reduce)\"></source><img class=\"shortcode-emoji emoji\" src=\"${url}\" alt=\":${shortcode}:\" width=\"16\" height=\"16\" loading=\"lazy\" decoding=\"async\" /></picture>`,\n    );\n  });\n  // console.log(text, emojis);\n  return text;\n}\n\nexport default emojifyText;\n","import emojifyText from './emojify-text';\nimport mem from './mem';\n\nconst fauxDiv = document.createElement('div');\nconst whitelistLinkClasses = ['u-url', 'mention', 'hashtag'];\n\nfunction _enhanceContent(content, opts = {}) {\n  const { emojis, postEnhanceDOM = () => {} } = opts;\n  let enhancedContent = content;\n  const dom = document.createElement('div');\n  dom.innerHTML = enhancedContent;\n  const hasLink = /<a/i.test(enhancedContent);\n  const hasCodeBlock = enhancedContent.includes('```');\n\n  if (hasLink) {\n    // Add target=\"_blank\" to all links with no target=\"_blank\"\n    // E.g. `note` in `account`\n    const noTargetBlankLinks = dom.querySelectorAll('a:not([target=\"_blank\"])');\n    noTargetBlankLinks.forEach((link) => {\n      link.setAttribute('target', '_blank');\n    });\n\n    // Remove all classes except `u-url`, `mention`, `hashtag`\n    const links = dom.querySelectorAll('a[class]');\n    links.forEach((link) => {\n      link.classList.forEach((c) => {\n        if (!whitelistLinkClasses.includes(c)) {\n          link.classList.remove(c);\n        }\n      });\n    });\n  }\n\n  // Add 'has-url-text' to all links that contains a url\n  if (hasLink) {\n    const links = dom.querySelectorAll('a[href]');\n    links.forEach((link) => {\n      if (/^https?:\\/\\//i.test(link.textContent.trim())) {\n        link.classList.add('has-url-text');\n      }\n    });\n  }\n\n  // Spanify un-spanned mentions\n  if (hasLink) {\n    const links = dom.querySelectorAll('a[href]');\n    const usernames = [];\n    links.forEach((link) => {\n      const text = link.innerText.trim();\n      const hasChildren = link.querySelector('*');\n      // If text looks like @username@domain, then it's a mention\n      if (/^@[^@]+(@[^@]+)?$/g.test(text)) {\n        // Only show @username\n        const [_, username, domain] = text.split('@');\n        if (!hasChildren) {\n          if (\n            !usernames.some(([u]) => u === username) ||\n            usernames.some(([u, d]) => u === username && d === domain)\n          ) {\n            link.innerHTML = `@<span>${username}</span>`;\n            usernames.push([username, domain]);\n          } else {\n            link.innerHTML = `@<span>${username}@${domain}</span>`;\n          }\n        }\n        link.classList.add('mention');\n      }\n      // If text looks like #hashtag, then it's a hashtag\n      if (/^#[^#]+$/g.test(text)) {\n        if (!hasChildren) link.innerHTML = `#<span>${text.slice(1)}</span>`;\n        link.classList.add('mention', 'hashtag');\n      }\n    });\n  }\n\n  // EMOJIS\n  // ======\n  // Convert :shortcode: to <img />\n  let textNodes;\n  if (enhancedContent.includes(':')) {\n    textNodes = extractTextNodes(dom);\n    textNodes.forEach((node) => {\n      let html = node.nodeValue\n        .replace(/&/g, '&amp;')\n        .replace(/</g, '&lt;')\n        .replace(/>/g, '&gt;');\n      if (emojis) {\n        html = emojifyText(html, emojis);\n      }\n      fauxDiv.innerHTML = html;\n      // const nodes = [...fauxDiv.childNodes];\n      node.replaceWith(...fauxDiv.childNodes);\n    });\n  }\n\n  // CODE BLOCKS\n  // ===========\n  // Convert ```code``` to <pre><code>code</code></pre>\n  if (hasCodeBlock) {\n    const blocks = [...dom.querySelectorAll('p')].filter((p) =>\n      /^```[^]+```$/g.test(p.innerText.trim()),\n    );\n    blocks.forEach((block) => {\n      const pre = document.createElement('pre');\n      // Replace <br /> with newlines\n      block.querySelectorAll('br').forEach((br) => br.replaceWith('\\n'));\n      pre.innerHTML = `<code>${block.innerHTML.trim()}</code>`;\n      block.replaceWith(pre);\n    });\n  }\n\n  // Convert multi-paragraph code blocks to <pre><code>code</code></pre>\n  if (hasCodeBlock) {\n    const paragraphs = [...dom.querySelectorAll('p')];\n    // Filter out paragraphs with ``` in beginning only\n    const codeBlocks = paragraphs.filter((p) => /^```/g.test(p.innerText));\n    // For each codeBlocks, get all paragraphs until the last paragraph with ``` at the end only\n    codeBlocks.forEach((block) => {\n      const nextParagraphs = [block];\n      let hasCodeBlock = false;\n      let currentBlock = block;\n      while (currentBlock.nextElementSibling) {\n        const next = currentBlock.nextElementSibling;\n        if (next && next.tagName === 'P') {\n          if (/```$/g.test(next.innerText)) {\n            nextParagraphs.push(next);\n            hasCodeBlock = true;\n            break;\n          } else {\n            nextParagraphs.push(next);\n          }\n        } else {\n          break;\n        }\n        currentBlock = next;\n      }\n      if (hasCodeBlock) {\n        const pre = document.createElement('pre');\n        nextParagraphs.forEach((p) => {\n          // Replace <br /> with newlines\n          p.querySelectorAll('br').forEach((br) => br.replaceWith('\\n'));\n        });\n        const codeText = nextParagraphs.map((p) => p.innerHTML).join('\\n\\n');\n        pre.innerHTML = `<code tabindex=\"0\">${codeText}</code>`;\n        block.replaceWith(pre);\n        nextParagraphs.forEach((p) => p.remove());\n      }\n    });\n  }\n\n  // INLINE CODE\n  // ===========\n  // Convert `code` to <code>code</code>\n  if (enhancedContent.includes('`')) {\n    textNodes = extractTextNodes(dom);\n    textNodes.forEach((node) => {\n      let html = node.nodeValue\n        .replace(/&/g, '&amp;')\n        .replace(/</g, '&lt;')\n        .replace(/>/g, '&gt;');\n      if (/`[^`]+`/g.test(html)) {\n        html = html.replaceAll(/(`[^]+?`)/g, '<code>$1</code>');\n      }\n      fauxDiv.innerHTML = html;\n      // const nodes = [...fauxDiv.childNodes];\n      node.replaceWith(...fauxDiv.childNodes);\n    });\n  }\n\n  // TWITTER USERNAMES\n  // =================\n  // Convert @username@twitter.com to <a href=\"https://twitter.com/username\">@username@twitter.com</a>\n  if (/twitter\\.com/i.test(enhancedContent)) {\n    textNodes = extractTextNodes(dom, {\n      rejectFilter: ['A'],\n    });\n    textNodes.forEach((node) => {\n      let html = node.nodeValue\n        .replace(/&/g, '&amp;')\n        .replace(/</g, '&lt;')\n        .replace(/>/g, '&gt;');\n      if (/@[a-zA-Z0-9_]+@twitter\\.com/g.test(html)) {\n        html = html.replaceAll(\n          /(@([a-zA-Z0-9_]+)@twitter\\.com)/g,\n          '<a href=\"https://twitter.com/$2\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">$1</a>',\n        );\n      }\n      fauxDiv.innerHTML = html;\n      // const nodes = [...fauxDiv.childNodes];\n      node.replaceWith(...fauxDiv.childNodes);\n    });\n  }\n\n  // HASHTAG STUFFING\n  // ================\n  // Get the <p> that contains a lot of hashtags, add a class to it\n  if (enhancedContent.includes('#')) {\n    let prevIndex = null;\n    const hashtagStuffedParagraphs = [...dom.querySelectorAll('p')].filter(\n      (p, index) => {\n        let hashtagCount = 0;\n        for (let i = 0; i < p.childNodes.length; i++) {\n          const node = p.childNodes[i];\n\n          if (node.nodeType === Node.TEXT_NODE) {\n            const text = node.textContent.trim();\n            if (text !== '') {\n              return false;\n            }\n          } else if (node.tagName === 'BR') {\n            // Ignore <br />\n          } else if (node.tagName === 'A') {\n            const linkText = node.textContent.trim();\n            if (!linkText || !linkText.startsWith('#')) {\n              return false;\n            } else {\n              hashtagCount++;\n            }\n          } else {\n            return false;\n          }\n        }\n        // Only consider \"stuffing\" if:\n        // - there are more than 3 hashtags\n        // - there are more than 1 hashtag in adjacent paragraphs\n        if (hashtagCount > 3) {\n          prevIndex = index;\n          return true;\n        }\n        if (hashtagCount > 1 && prevIndex && index === prevIndex + 1) {\n          prevIndex = index;\n          return true;\n        }\n      },\n    );\n    if (hashtagStuffedParagraphs?.length) {\n      hashtagStuffedParagraphs.forEach((p) => {\n        p.classList.add('hashtag-stuffing');\n        p.title = p.innerText;\n      });\n    }\n  }\n\n  if (postEnhanceDOM) {\n    queueMicrotask(() => postEnhanceDOM(dom));\n    // postEnhanceDOM(dom); // mutate dom\n  }\n\n  enhancedContent = dom.innerHTML;\n\n  return enhancedContent;\n}\nconst enhanceContent = mem(_enhanceContent);\n\nconst defaultRejectFilter = [\n  // Document metadata\n  'STYLE',\n  // Image and multimedia\n  'IMG',\n  'VIDEO',\n  'AUDIO',\n  'AREA',\n  'MAP',\n  'TRACK',\n  // Embedded content\n  'EMBED',\n  'IFRAME',\n  'OBJECT',\n  'PICTURE',\n  'PORTAL',\n  'SOURCE',\n  // SVG and MathML\n  'SVG',\n  'MATH',\n  // Scripting\n  'CANVAS',\n  'NOSCRIPT',\n  'SCRIPT',\n  // Forms\n  'INPUT',\n  'OPTION',\n  'TEXTAREA',\n  // Web Components\n  'SLOT',\n  'TEMPLATE',\n];\nconst defaultRejectFilterMap = Object.fromEntries(\n  defaultRejectFilter.map((nodeName) => [nodeName, true]),\n);\nfunction extractTextNodes(dom, opts = {}) {\n  const textNodes = [];\n  const rejectFilterMap = Object.assign(\n    {},\n    defaultRejectFilterMap,\n    opts.rejectFilter?.reduce((acc, cur) => {\n      acc[cur] = true;\n      return acc;\n    }, {}),\n  );\n  const walk = document.createTreeWalker(\n    dom,\n    NodeFilter.SHOW_TEXT,\n    {\n      acceptNode(node) {\n        if (rejectFilterMap[node.parentNode.nodeName]) {\n          return NodeFilter.FILTER_REJECT;\n        }\n        return NodeFilter.FILTER_ACCEPT;\n      },\n    },\n    false,\n  );\n  let node;\n  while ((node = walk.nextNode())) {\n    textNodes.push(node);\n  }\n  return textNodes;\n}\n\nexport default enhanceContent;\n","import mem from './mem';\n\nconst div = document.createElement('div');\nfunction getHTMLText(html) {\n  if (!html) return '';\n  div.innerHTML = html\n    .replace(/<\\/p>/g, '</p>\\n\\n')\n    .replace(/<\\/li>/g, '</li>\\n');\n  div.querySelectorAll('br').forEach((br) => {\n    br.replaceWith('\\n');\n  });\n  return div.innerText.replace(/[\\r\\n]{3,}/g, '\\n\\n').trim();\n}\n\nexport default mem(getHTMLText);\n","import states from './states';\n\nfunction handleContentLinks(opts) {\n  const { mentions = [], instance, previewMode, statusURL } = opts || {};\n  return (e) => {\n    let { target } = e;\n    target = target.closest('a');\n    if (!target) return;\n\n    // If cmd/ctrl/shift/alt key is pressed or middle-click, let the browser handle it\n    if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.which === 2) {\n      return;\n    }\n\n    const prevText = target.previousSibling?.textContent;\n    const textBeforeLinkIsAt = prevText?.endsWith('@');\n    const textStartsWithAt = target.innerText.startsWith('@');\n    if (\n      (target.classList.contains('u-url') && textStartsWithAt) ||\n      (textBeforeLinkIsAt && !textStartsWithAt)\n    ) {\n      const targetText = (\n        target.querySelector('span') || target\n      ).innerText.trim();\n      const username = targetText.replace(/^@/, '');\n      const url = target.getAttribute('href');\n      const mention = mentions.find(\n        (mention) =>\n          mention.username === username ||\n          mention.acct === username ||\n          mention.url === url,\n      );\n      if (mention) {\n        e.preventDefault();\n        e.stopPropagation();\n        states.showAccount = {\n          account: mention.acct,\n          instance,\n        };\n      } else if (!/^http/i.test(targetText)) {\n        console.log('mention not found', targetText);\n        e.preventDefault();\n        e.stopPropagation();\n        const href = target.getAttribute('href');\n        states.showAccount = {\n          account: href,\n          instance,\n        };\n      }\n    } else if (!previewMode) {\n      const textBeforeLinkIsHash = prevText?.endsWith('#');\n      if (target.classList.contains('hashtag') || textBeforeLinkIsHash) {\n        e.preventDefault();\n        e.stopPropagation();\n        const tag = target.innerText.replace(/^#/, '').trim();\n        const hashURL = instance ? `#/${instance}/t/${tag}` : `#/t/${tag}`;\n        console.log({ hashURL });\n        location.hash = hashURL;\n      } else if (\n        states.unfurledLinks[target.href]?.url &&\n        statusURL !== target.href\n      ) {\n        // If unfurled AND not self-referential\n        e.preventDefault();\n        e.stopPropagation();\n        states.prevLocation = {\n          pathname: location.hash.replace(/^#/, ''),\n        };\n        location.hash = `#${states.unfurledLinks[target.href].url}`;\n      }\n    }\n  };\n}\n\nexport default handleContentLinks;\n","import mem from './mem';\n\nconst { locale } = new Intl.DateTimeFormat().resolvedOptions();\n\nconst _DateTimeFormat = (opts) => {\n  const { dateYear, hideTime, formatOpts } = opts || {};\n  const currentYear = new Date().getFullYear();\n  return Intl.DateTimeFormat(locale, {\n    // Show year if not current year\n    year: dateYear === currentYear ? undefined : 'numeric',\n    month: 'short',\n    day: 'numeric',\n    // Hide time if requested\n    hour: hideTime ? undefined : 'numeric',\n    minute: hideTime ? undefined : 'numeric',\n    ...formatOpts,\n  });\n};\nconst DateTimeFormat = mem(_DateTimeFormat);\n\nfunction niceDateTime(date, dtfOpts) {\n  if (!(date instanceof Date)) {\n    date = new Date(date);\n  }\n  const DTF = DateTimeFormat({\n    dateYear: date.getFullYear(),\n    ...dtfOpts,\n  });\n  const dateText = DTF.format(date);\n  return dateText;\n}\n\nexport default niceDateTime;\n","const { locale } = Intl.NumberFormat().resolvedOptions();\nconst shortenNumber = Intl.NumberFormat(locale, {\n  notation: 'compact',\n}).format;\nexport default shortenNumber;\n","import './account-block.css';\n\n// import { useNavigate } from 'react-router-dom';\nimport enhanceContent from '../utils/enhance-content';\nimport niceDateTime from '../utils/nice-date-time';\nimport shortenNumber from '../utils/shorten-number';\nimport states from '../utils/states';\n\nimport Avatar from './avatar';\nimport EmojiText from './emoji-text';\nimport Icon from './icon';\n\nfunction AccountBlock({\n  skeleton,\n  account,\n  avatarSize = 'xl',\n  useAvatarStatic = false,\n  instance,\n  external,\n  internal,\n  onClick,\n  showActivity = false,\n  showStats = false,\n  accountInstance,\n  hideDisplayName = false,\n  relationship = {},\n  excludeRelationshipAttrs = [],\n}) {\n  if (skeleton) {\n    return (\n      <div class=\"account-block skeleton\">\n        <Avatar size={avatarSize} />\n        <span>\n          <b>████████</b>\n          <br />\n          <span class=\"account-block-acct\">@██████</span>\n        </span>\n      </div>\n    );\n  }\n\n  if (!account) {\n    return null;\n  }\n\n  // const navigate = useNavigate();\n\n  const {\n    id,\n    acct,\n    avatar,\n    avatarStatic,\n    displayName,\n    username,\n    emojis,\n    url,\n    statusesCount,\n    lastStatusAt,\n    bot,\n    fields,\n    note,\n    group,\n    followersCount,\n  } = account;\n  let [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct];\n  if (accountInstance) {\n    acct2 = `@${accountInstance}`;\n  }\n\n  const verifiedField = fields?.find((f) => !!f.verifiedAt && !!f.value);\n\n  const excludedRelationship = {};\n  for (const r in relationship) {\n    if (!excludeRelationshipAttrs.includes(r)) {\n      excludedRelationship[r] = relationship[r];\n    }\n  }\n  const hasRelationship =\n    excludedRelationship.following ||\n    excludedRelationship.followedBy ||\n    excludedRelationship.requested;\n\n  return (\n    <a\n      class=\"account-block\"\n      href={url}\n      target={external ? '_blank' : null}\n      title={`@${acct}`}\n      onClick={(e) => {\n        if (external) return;\n        e.preventDefault();\n        if (onClick) return onClick(e);\n        if (internal) {\n          // navigate(`/${instance}/a/${id}`);\n          location.hash = `/${instance}/a/${id}`;\n        } else {\n          states.showAccount = {\n            account,\n            instance,\n          };\n        }\n      }}\n    >\n      <Avatar\n        url={useAvatarStatic ? avatarStatic : avatar || avatarStatic}\n        size={avatarSize}\n        squircle={bot}\n      />\n      <span class=\"account-block-content\">\n        {!hideDisplayName && (\n          <>\n            {displayName ? (\n              <b>\n                <EmojiText text={displayName} emojis={emojis} />\n              </b>\n            ) : (\n              <b>{username}</b>\n            )}\n          </>\n        )}{' '}\n        <span class=\"account-block-acct\">\n          @{acct1}\n          <wbr />\n          {acct2}\n        </span>\n        {showActivity && (\n          <>\n            <br />\n            <small class=\"last-status-at insignificant\">\n              Posts: {statusesCount}\n              {!!lastStatusAt && (\n                <>\n                  {' '}\n                  &middot; Last posted:{' '}\n                  {niceDateTime(lastStatusAt, {\n                    hideTime: true,\n                  })}\n                </>\n              )}\n            </small>\n          </>\n        )}\n        {showStats && (\n          <div class=\"account-block-stats\">\n            {bot && (\n              <>\n                <span class=\"tag collapsed\">\n                  <Icon icon=\"bot\" /> Automated\n                </span>\n              </>\n            )}\n            {!!group && (\n              <>\n                <span class=\"tag collapsed\">\n                  <Icon icon=\"group\" /> Group\n                </span>\n              </>\n            )}\n            {hasRelationship && (\n              <div key={relationship.id} class=\"shazam-container-horizontal\">\n                <div class=\"shazam-container-inner\">\n                  {excludedRelationship.following &&\n                  excludedRelationship.followedBy ? (\n                    <span class=\"tag minimal\">Mutual</span>\n                  ) : excludedRelationship.requested ? (\n                    <span class=\"tag minimal\">Requested</span>\n                  ) : excludedRelationship.following ? (\n                    <span class=\"tag minimal\">Following</span>\n                  ) : excludedRelationship.followedBy ? (\n                    <span class=\"tag minimal\">Follows you</span>\n                  ) : null}\n                </div>\n              </div>\n            )}\n            {!!followersCount && (\n              <span class=\"ib\">\n                {shortenNumber(followersCount)}{' '}\n                {followersCount === 1 ? 'follower' : 'followers'}\n              </span>\n            )}\n            {!!verifiedField && (\n              <span class=\"verified-field\">\n                <Icon icon=\"check-circle\" size=\"s\" />{' '}\n                <span\n                  dangerouslySetInnerHTML={{\n                    __html: enhanceContent(verifiedField.value, { emojis }),\n                  }}\n                />\n              </span>\n            )}\n          </div>\n        )}\n      </span>\n    </a>\n  );\n}\n\nexport default AccountBlock;\n","import { satisfies } from 'compare-versions';\n\nimport features from '../data/features.json';\n\nimport { getCurrentInstance } from './store-utils';\n\nconst supportsCache = {};\n\nfunction supports(feature) {\n  try {\n    const { version, domain } = getCurrentInstance();\n    const key = `${domain}-${feature}`;\n    if (supportsCache[key]) return supportsCache[key];\n    const range = features[feature];\n    if (!range) return false;\n    return (supportsCache[key] = satisfies(version, range, {\n      includePrerelease: true,\n      loose: true,\n    }));\n  } catch (e) {\n    return false;\n  }\n}\n\nexport default supports;\n","import './translation-block.css';\n\nimport pRetry from 'p-retry';\nimport pThrottle from 'p-throttle';\nimport { useEffect, useRef, useState } from 'preact/hooks';\n\nimport sourceLanguages from '../data/lingva-source-languages';\nimport getTranslateTargetLanguage from '../utils/get-translate-target-language';\nimport localeCode2Text from '../utils/localeCode2Text';\nimport pmem from '../utils/pmem';\n\nimport Icon from './icon';\nimport Loader from './loader';\n\nconst { PHANPY_LINGVA_INSTANCES } = import.meta.env;\nconst LINGVA_INSTANCES = PHANPY_LINGVA_INSTANCES\n  ? PHANPY_LINGVA_INSTANCES.split(/\\s+/)\n  : [];\n\nconst throttle = pThrottle({\n  limit: 1,\n  interval: 2000,\n});\n\nlet currentLingvaInstance = 0;\n\nfunction _lingvaTranslate(text, source, target) {\n  console.log('TRANSLATE', text, source, target);\n  const fetchCall = () => {\n    let instance = LINGVA_INSTANCES[currentLingvaInstance];\n    return fetch(\n      `https://${instance}/api/v1/${source}/${target}/${encodeURIComponent(\n        text,\n      )}`,\n    )\n      .then((res) => {\n        if (!res.ok) throw new Error(res.statusText);\n        return res.json();\n      })\n      .then((res) => {\n        return {\n          provider: 'lingva',\n          content: res.translation,\n          detectedSourceLanguage: res.info?.detectedSource,\n          info: res.info,\n        };\n      });\n  };\n  return pRetry(fetchCall, {\n    retries: 3,\n    onFailedAttempt: (e) => {\n      currentLingvaInstance =\n        (currentLingvaInstance + 1) % LINGVA_INSTANCES.length;\n      console.log(\n        'Retrying translation with another instance',\n        currentLingvaInstance,\n      );\n    },\n  });\n  // return masto.v1.statuses.$select(id).translate({\n  //   lang: DEFAULT_LANG,\n  // });\n}\nconst TRANSLATED_MAX_AGE = 1000 * 60 * 60; // 1 hour\nconst lingvaTranslate = pmem(_lingvaTranslate, {\n  maxAge: TRANSLATED_MAX_AGE,\n});\nconst throttledLingvaTranslate = pmem(throttle(lingvaTranslate), {\n  // I know, this is double-layered memoization\n  maxAge: TRANSLATED_MAX_AGE,\n});\n\nfunction TranslationBlock({\n  forceTranslate,\n  sourceLanguage,\n  onTranslate,\n  text = '',\n  mini,\n}) {\n  const targetLang = getTranslateTargetLanguage(true);\n  const [uiState, setUIState] = useState('default');\n  const [pronunciationContent, setPronunciationContent] = useState(null);\n  const [translatedContent, setTranslatedContent] = useState(null);\n  const [detectedLang, setDetectedLang] = useState(null);\n  const detailsRef = useRef();\n\n  const sourceLangText = sourceLanguage\n    ? localeCode2Text(sourceLanguage)\n    : null;\n  const targetLangText = localeCode2Text(targetLang);\n  const apiSourceLang = useRef('auto');\n\n  if (!onTranslate) {\n    onTranslate = mini ? throttledLingvaTranslate : lingvaTranslate;\n  }\n\n  const translate = async () => {\n    setUIState('loading');\n    try {\n      const { content, detectedSourceLanguage, provider, error, ...props } =\n        await onTranslate(text, apiSourceLang.current, targetLang);\n      if (content) {\n        if (detectedSourceLanguage) {\n          const detectedLangText = localeCode2Text(detectedSourceLanguage);\n          setDetectedLang(detectedLangText);\n        }\n        if (provider === 'lingva') {\n          const pronunciation = props?.info?.pronunciation?.query;\n          if (pronunciation) {\n            setPronunciationContent(pronunciation);\n          }\n        }\n        setTranslatedContent(content);\n        setUIState('default');\n        if (!mini && content.trim() !== text.trim()) {\n          detailsRef.current.open = true;\n          detailsRef.current.scrollIntoView({\n            behavior: 'smooth',\n            block: 'nearest',\n          });\n        }\n      } else {\n        if (error) console.error(error);\n        setUIState('error');\n      }\n    } catch (e) {\n      console.error(e);\n      setUIState('error');\n    }\n  };\n\n  useEffect(() => {\n    if (forceTranslate) {\n      translate();\n    }\n  }, [forceTranslate]);\n\n  if (mini) {\n    if (\n      !!translatedContent &&\n      translatedContent.trim() !== text.trim() &&\n      detectedLang !== targetLangText\n    ) {\n      return (\n        <div class=\"shazam-container\">\n          <div class=\"shazam-container-inner\">\n            <div class=\"status-translation-block-mini\">\n              <Icon\n                icon=\"translate\"\n                alt={`Auto-translated from ${sourceLangText}`}\n              />\n              <output\n                lang={targetLang}\n                dir=\"auto\"\n                title={pronunciationContent || ''}\n              >\n                {translatedContent}\n              </output>\n            </div>\n          </div>\n        </div>\n      );\n    }\n    return null;\n  }\n\n  return (\n    <div\n      class=\"status-translation-block\"\n      onClick={(e) => {\n        e.preventDefault();\n      }}\n    >\n      <details ref={detailsRef}>\n        <summary>\n          <button\n            type=\"button\"\n            onClick={async (e) => {\n              e.preventDefault();\n              e.stopPropagation();\n              detailsRef.current.open = !detailsRef.current.open;\n              if (uiState === 'loading') return;\n              if (!translatedContent) translate();\n            }}\n          >\n            <Icon icon=\"translate\" />{' '}\n            <span>\n              {uiState === 'loading'\n                ? 'Translating…'\n                : sourceLanguage && sourceLangText && !detectedLang\n                ? `Translate from ${sourceLangText}`\n                : `Translate`}\n            </span>\n          </button>\n        </summary>\n        <div class=\"translated-block\">\n          <div class=\"translation-info insignificant\">\n            <select\n              class=\"translated-source-select\"\n              disabled={uiState === 'loading'}\n              onChange={(e) => {\n                apiSourceLang.current = e.target.value;\n                translate();\n              }}\n            >\n              {sourceLanguages.map((l) => (\n                <option value={l.code}>\n                  {l.code === 'auto' ? `Auto (${detectedLang ?? '…'})` : l.name}\n                </option>\n              ))}\n            </select>{' '}\n            <span>→ {targetLangText}</span>\n            <Loader abrupt hidden={uiState !== 'loading'} />\n          </div>\n          {uiState === 'error' ? (\n            <p class=\"ui-state\">Failed to translate</p>\n          ) : (\n            !!translatedContent && (\n              <>\n                <output class=\"translated-content\" lang={targetLang} dir=\"auto\">\n                  {translatedContent}\n                </output>\n                {!!pronunciationContent && (\n                  <output\n                    class=\"translated-pronunciation-content\"\n                    tabIndex={-1}\n                    onClick={(e) => {\n                      e.target.classList.toggle('expand');\n                    }}\n                  >\n                    {pronunciationContent}\n                  </output>\n                )}\n              </>\n            )\n          )}\n        </div>\n      </details>\n    </div>\n  );\n}\n\nexport default LINGVA_INSTANCES?.length ? TranslationBlock : () => null;\n","import {\n  clear,\n  createStore,\n  del,\n  delMany,\n  get,\n  getMany,\n  keys,\n  set,\n} from 'idb-keyval';\n\nconst draftsStore = createStore('drafts-db', 'drafts-store');\n\n// Add additonal `draftsStore` parameter to all methods\n\nconst drafts = {\n  set: (key, val) => set(key, val, draftsStore),\n  get: (key) => get(key, draftsStore),\n  getMany: (keys) => getMany(keys, draftsStore),\n  del: (key) => del(key, draftsStore),\n  delMany: (keys) => delMany(keys, draftsStore),\n  clear: () => clear(draftsStore),\n  keys: () => keys(draftsStore),\n};\n\nexport default {\n  drafts,\n};\n","export default {\n  public: 'earth',\n  unlisted: 'group',\n  private: 'lock',\n  direct: 'message',\n};\n","import { useState } from 'preact/hooks';\n\nimport shortenNumber from '../utils/shorten-number';\n\nimport EmojiText from './emoji-text';\nimport Icon from './icon';\nimport RelativeTime from './relative-time';\n\nexport default function Poll({\n  poll,\n  lang,\n  readOnly,\n  refresh = () => {},\n  votePoll = () => {},\n}) {\n  const [uiState, setUIState] = useState('default');\n  const {\n    expired,\n    expiresAt,\n    id,\n    multiple,\n    options,\n    ownVotes,\n    voted,\n    votersCount,\n    votesCount,\n    emojis,\n  } = poll;\n  const expiresAtDate = !!expiresAt && new Date(expiresAt); // Update poll at point of expiry\n  // NOTE: Disable this because setTimeout runs immediately if delay is too large\n  // https://stackoverflow.com/a/56718027/20838\n  // useEffect(() => {\n  //   let timeout;\n  //   if (!expired && expiresAtDate) {\n  //     const ms = expiresAtDate.getTime() - Date.now() + 1; // +1 to give it a little buffer\n  //     if (ms > 0) {\n  //       timeout = setTimeout(() => {\n  //         setUIState('loading');\n  //         (async () => {\n  //           // await refresh();\n  //           setUIState('default');\n  //         })();\n  //       }, ms);\n  //     }\n  //   }\n  //   return () => {\n  //     clearTimeout(timeout);\n  //   };\n  // }, [expired, expiresAtDate]);\n\n  const pollVotesCount = votersCount || votesCount;\n  let roundPrecision = 0;\n\n  if (pollVotesCount <= 1000) {\n    roundPrecision = 0;\n  } else if (pollVotesCount <= 10000) {\n    roundPrecision = 1;\n  } else if (pollVotesCount <= 100000) {\n    roundPrecision = 2;\n  }\n\n  const [showResults, setShowResults] = useState(false);\n  const optionsHaveVoteCounts = options.every((o) => o.votesCount !== null);\n\n  return (\n    <div\n      lang={lang}\n      dir=\"auto\"\n      class={`poll ${readOnly ? 'read-only' : ''} ${\n        uiState === 'loading' ? 'loading' : ''\n      }`}\n    >\n      {(showResults && optionsHaveVoteCounts) || voted || expired ? (\n        <>\n          <div class=\"poll-options\">\n            {options.map((option, i) => {\n              const { title, votesCount: optionVotesCount } = option;\n              const percentage = pollVotesCount\n                ? ((optionVotesCount / pollVotesCount) * 100).toFixed(\n                    roundPrecision,\n                  )\n                : 0; // check if current poll choice is the leading one\n\n              const isLeading =\n                optionVotesCount > 0 &&\n                optionVotesCount ===\n                  Math.max(...options.map((o) => o.votesCount));\n              return (\n                <div\n                  key={`${i}-${title}-${optionVotesCount}`}\n                  class={`poll-option poll-result ${\n                    isLeading ? 'poll-option-leading' : ''\n                  }`}\n                  style={{\n                    '--percentage': `${percentage}%`,\n                  }}\n                >\n                  <div class=\"poll-option-title\">\n                    <span>\n                      <EmojiText text={title} emojis={emojis} />\n                    </span>\n                    {voted && ownVotes.includes(i) && (\n                      <>\n                        {' '}\n                        <Icon icon=\"check-circle\" />\n                      </>\n                    )}\n                  </div>\n                  <div\n                    class=\"poll-option-votes\"\n                    title={`${optionVotesCount} vote${\n                      optionVotesCount === 1 ? '' : 's'\n                    }`}\n                  >\n                    {percentage}%\n                  </div>\n                </div>\n              );\n            })}\n          </div>\n          {!expired && !voted && (\n            <button\n              class=\"poll-vote-button plain2\"\n              disabled={uiState === 'loading'}\n              onClick={(e) => {\n                e.preventDefault();\n                setShowResults(false);\n              }}\n            >\n              <Icon icon=\"arrow-left\" size=\"s\" /> Hide results\n            </button>\n          )}\n        </>\n      ) : (\n        <form\n          onSubmit={async (e) => {\n            e.preventDefault();\n            const form = e.target;\n            const formData = new FormData(form);\n            const choices = [];\n            formData.forEach((value, key) => {\n              if (key === 'poll') {\n                choices.push(value);\n              }\n            });\n            if (!choices.length) return;\n            setUIState('loading');\n            await votePoll(choices);\n            setUIState('default');\n          }}\n        >\n          <div class=\"poll-options\">\n            {options.map((option, i) => {\n              const { title } = option;\n              return (\n                <div class=\"poll-option\">\n                  <label class=\"poll-label\">\n                    <input\n                      type={multiple ? 'checkbox' : 'radio'}\n                      name=\"poll\"\n                      value={i}\n                      disabled={uiState === 'loading'}\n                      readOnly={readOnly}\n                    />\n                    <span class=\"poll-option-title\">\n                      <EmojiText text={title} emojis={emojis} />\n                    </span>\n                  </label>\n                </div>\n              );\n            })}\n          </div>\n          {!readOnly && (\n            <button\n              class=\"poll-vote-button\"\n              type=\"submit\"\n              disabled={uiState === 'loading'}\n            >\n              Vote\n            </button>\n          )}\n        </form>\n      )}\n      <p class=\"poll-meta\">\n        {!expired && !readOnly && (\n          <button\n            type=\"button\"\n            class=\"plain small\"\n            disabled={uiState === 'loading'}\n            style={{\n              marginLeft: -8,\n            }}\n            onClick={(e) => {\n              e.preventDefault();\n              setUIState('loading');\n\n              (async () => {\n                await refresh();\n                setUIState('default');\n              })();\n            }}\n            title=\"Refresh\"\n          >\n            <Icon icon=\"refresh\" alt=\"Refresh\" />\n          </button>\n        )}\n        {!voted && !expired && !readOnly && optionsHaveVoteCounts && (\n          <button\n            type=\"button\"\n            class=\"plain small\"\n            disabled={uiState === 'loading'}\n            onClick={(e) => {\n              e.preventDefault();\n              setShowResults(!showResults);\n            }}\n            title={showResults ? 'Hide results' : 'Show results'}\n          >\n            <Icon\n              icon={showResults ? 'eye-open' : 'eye-close'}\n              alt={showResults ? 'Hide results' : 'Show results'}\n            />{' '}\n          </button>\n        )}\n        {!expired && !readOnly && ' '}\n        <span title={votesCount}>{shortenNumber(votesCount)}</span> vote\n        {votesCount === 1 ? '' : 's'}\n        {!!votersCount && votersCount !== votesCount && (\n          <>\n            {' '}\n            &bull; <span title={votersCount}>\n              {shortenNumber(votersCount)}\n            </span>{' '}\n            voter\n            {votersCount === 1 ? '' : 's'}\n          </>\n        )}{' '}\n        &bull; {expired ? 'Ended' : 'Ending'}{' '}\n        {!!expiresAtDate && <RelativeTime datetime={expiresAtDate} />}\n      </p>{' '}\n    </div>\n  );\n}\n","import { createContext } from 'preact';\n\nconst FilterContext = createContext();\nexport default FilterContext;\n","import mem from './mem';\nimport store from './store';\n\nfunction _isFiltered(filtered, filterContext) {\n  if (!filtered?.length) return false;\n  const appliedFilters = filtered.filter((f) => {\n    const { filter } = f;\n    const hasContext = filter.context.includes(filterContext);\n    if (!hasContext) return false;\n    if (!filter.expiresAt) return hasContext;\n    return new Date(filter.expiresAt) > new Date();\n  });\n  if (!appliedFilters.length) return false;\n  const isHidden = appliedFilters.some((f) => f.filter.filterAction === 'hide');\n  if (isHidden)\n    return {\n      action: 'hide',\n    };\n  const isWarn = appliedFilters.some((f) => f.filter.filterAction === 'warn');\n  if (isWarn) {\n    const filterTitles = appliedFilters.map((f) => f.filter.title);\n    return {\n      action: 'warn',\n      titles: filterTitles,\n      titlesStr: filterTitles.join(' • '),\n    };\n  }\n  return false;\n}\nexport const isFiltered = mem(_isFiltered);\n\nexport function filteredItem(item, filterContext, currentAccountID) {\n  const { filtered } = item;\n  if (!filtered?.length) return true;\n  const isSelf = currentAccountID && item.account?.id === currentAccountID;\n  if (isSelf) return true;\n  const filterState = isFiltered(filtered, filterContext);\n  if (!filterState) return true;\n  if (filterState.action === 'hide') return false;\n  // item._filtered = filterState;\n  return true;\n}\nexport function filteredItems(items, filterContext) {\n  if (!items?.length) return [];\n  if (!filterContext) return items;\n  const currentAccountID = store.session.get('currentAccount');\n  return items.filter((item) =>\n    filteredItem(item, filterContext, currentAccountID),\n  );\n}\n","const div = document.createElement('div');\nexport default function htmlContentLength(html) {\n  if (!html) return 0;\n  div.innerHTML = html;\n  // .invisible spans for links\n  // e.g. <span class=\"invisible\">https://</span>mastodon.social\n  div.querySelectorAll('.invisible').forEach((el) => {\n    el.remove();\n  });\n  return div.innerText.length;\n}\n","export const supportsTTS = 'speechSynthesis' in window;\n\nexport function speak(text, lang) {\n  if (!supportsTTS) return;\n  try {\n    if (speechSynthesis.speaking) {\n      speechSynthesis.cancel();\n    }\n    const utterance = new SpeechSynthesisUtterance(text);\n    if (lang) utterance.lang = lang;\n    speechSynthesis.speak(utterance);\n  } catch (e) {\n    alert(e);\n  }\n}\n","import getHTMLText from './getHTMLText';\n\nfunction statusPeek(status) {\n  const { spoilerText, content, poll, mediaAttachments } = status;\n  let text = '';\n  if (spoilerText?.trim()) {\n    text += spoilerText;\n  } else {\n    text += getHTMLText(content);\n  }\n  text = text.trim();\n  if (poll) {\n    text += ' 📊';\n  }\n  if (mediaAttachments?.length) {\n    text +=\n      ' ' +\n      mediaAttachments\n        .map(\n          (m) =>\n            ({\n              image: '🖼️',\n              gifv: '🎞️',\n              video: '📹',\n              audio: '🎵',\n              unknown: '',\n            }[m.type] || ''),\n        )\n        .join('');\n  }\n  return text;\n}\n\nexport default statusPeek;\n","import { useRef } from 'preact/hooks';\nimport { useThrottledCallback } from 'use-debounce';\nimport useResizeObserver from 'use-resize-observer';\n\nexport default function useTruncated({ className = 'truncated' } = {}) {\n  const ref = useRef();\n  const onResize = useThrottledCallback(({ height }) => {\n    if (ref.current) {\n      const { scrollHeight } = ref.current;\n      let truncated = scrollHeight > height;\n      if (truncated) {\n        const { height: _height, maxHeight } = getComputedStyle(ref.current);\n        const computedHeight = parseInt(maxHeight || _height, 10);\n        truncated = scrollHeight > computedHeight;\n      }\n      ref.current.classList.toggle(className, truncated);\n    }\n  }, 300);\n  useResizeObserver({\n    ref,\n    box: 'border-box',\n    onResize,\n  });\n  return ref;\n}\n","import { getBlurHashAverageColor } from 'fast-blurhash';\nimport { Fragment } from 'preact';\nimport {\n  useCallback,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'preact/hooks';\nimport QuickPinchZoom, { make3dTransformValue } from 'react-quick-pinch-zoom';\n\nimport mem from '../utils/mem';\nimport states from '../utils/states';\n\nimport Icon from './icon';\nimport Link from './link';\nimport { formatDuration } from './status';\n\nconst isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); // https://stackoverflow.com/a/23522755\n\n/*\nMedia type\n===\nunknown = unsupported or unrecognized file type\nimage = Static image\ngifv = Looping, soundless animation\nvideo = Video clip\naudio = Audio track\n*/\n\nconst dataAltLabel = 'ALT';\nconst AltBadge = (props) => {\n  const { alt, lang, index, ...rest } = props;\n  if (!alt || !alt.trim()) return null;\n  return (\n    <button\n      type=\"button\"\n      class=\"alt-badge clickable\"\n      {...rest}\n      onClick={(e) => {\n        e.stopPropagation();\n        e.preventDefault();\n        states.showMediaAlt = {\n          alt,\n          lang,\n        };\n      }}\n      title=\"Media description\"\n    >\n      {dataAltLabel}\n      {!!index && <sup>{index}</sup>}\n    </button>\n  );\n};\n\nconst MEDIA_CAPTION_LIMIT = 140;\nexport const isMediaCaptionLong = mem((caption) =>\n  caption?.length\n    ? caption.length > MEDIA_CAPTION_LIMIT ||\n      /[\\n\\r].*[\\n\\r]/.test(caption.trim())\n    : false,\n);\n\nfunction Media({\n  class: className = '',\n  media,\n  to,\n  lang,\n  showOriginal,\n  autoAnimate,\n  showCaption,\n  altIndex,\n  onClick = () => {},\n}) {\n  const {\n    blurhash,\n    description,\n    meta,\n    previewRemoteUrl,\n    previewUrl,\n    remoteUrl,\n    url,\n    type,\n  } = media;\n  const { original = {}, small, focus } = meta || {};\n\n  const width = showOriginal ? original?.width : small?.width;\n  const height = showOriginal ? original?.height : small?.height;\n  const mediaURL = showOriginal ? url : previewUrl || url;\n  const remoteMediaURL = showOriginal\n    ? remoteUrl\n    : previewRemoteUrl || remoteUrl;\n  const orientation = width >= height ? 'landscape' : 'portrait';\n\n  const rgbAverageColor = blurhash ? getBlurHashAverageColor(blurhash) : null;\n\n  const videoRef = useRef();\n\n  let focalPosition;\n  if (focus) {\n    // Convert focal point to CSS background position\n    // Formula from jquery-focuspoint\n    // x = -1, y = 1 => 0% 0%\n    // x = 0, y = 0 => 50% 50%\n    // x = 1, y = -1 => 100% 100%\n    const x = ((focus.x + 1) / 2) * 100;\n    const y = ((1 - focus.y) / 2) * 100;\n    focalPosition = `${x.toFixed(0)}% ${y.toFixed(0)}%`;\n  }\n\n  const mediaRef = useRef();\n  const onUpdate = useCallback(({ x, y, scale }) => {\n    const { current: media } = mediaRef;\n\n    if (media) {\n      const value = make3dTransformValue({ x, y, scale });\n\n      if (scale === 1) {\n        media.style.removeProperty('transform');\n      } else {\n        media.style.setProperty('transform', value);\n      }\n\n      media.closest('.media-zoom').style.touchAction =\n        scale <= 1.01 ? 'pan-x' : '';\n    }\n  }, []);\n\n  const [pinchZoomEnabled, setPinchZoomEnabled] = useState(false);\n  const quickPinchZoomProps = {\n    enabled: pinchZoomEnabled,\n    draggableUnZoomed: false,\n    inertiaFriction: 0.9,\n    doubleTapZoomOutOnMaxScale: true,\n    containerProps: {\n      className: 'media-zoom',\n      style: {\n        overflow: 'visible',\n        //   width: 'inherit',\n        //   height: 'inherit',\n        //   justifyContent: 'inherit',\n        //   alignItems: 'inherit',\n        //   display: 'inherit',\n      },\n    },\n    onUpdate,\n  };\n\n  const Parent = useMemo(\n    () => (to ? (props) => <Link to={to} {...props} /> : 'div'),\n    [to],\n  );\n\n  const isVideoMaybe =\n    type === 'unknown' &&\n    /\\.(mp4|m4a|m4p|m4b|m4r|m4v|mov|webm)$/i.test(remoteMediaURL);\n  const isImage =\n    type === 'image' || (type === 'unknown' && previewUrl && !isVideoMaybe);\n\n  const parentRef = useRef();\n  const [imageSmallerThanParent, setImageSmallerThanParent] = useState(false);\n  useLayoutEffect(() => {\n    if (!isImage) return;\n    if (!showOriginal) return;\n    if (!parentRef.current) return;\n    const { offsetWidth, offsetHeight } = parentRef.current;\n    const smaller = width < offsetWidth && height < offsetHeight;\n    if (smaller) setImageSmallerThanParent(smaller);\n  }, [width, height]);\n\n  const maxAspectHeight =\n    window.innerHeight * (orientation === 'portrait' ? 0.45 : 0.33);\n  const maxHeight = orientation === 'portrait' ? 0 : 160;\n  const averageColorStyle = {\n    '--average-color': rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,\n  };\n  const mediaStyles =\n    width && height\n      ? {\n          '--width': `${width}px`,\n          '--height': `${height}px`,\n          // Calculate '--aspectWidth' based on aspect ratio calculated from '--width' and '--height', max height has to be 160px\n          '--aspectWidth': `${\n            (width / height) * Math.max(maxHeight, maxAspectHeight)\n          }px`,\n          aspectRatio: `${width} / ${height}`,\n          ...averageColorStyle,\n        }\n      : {\n          ...averageColorStyle,\n        };\n\n  const longDesc = isMediaCaptionLong(description);\n  const showInlineDesc =\n    !!showCaption && !showOriginal && !!description && !longDesc;\n  const Figure = !showInlineDesc\n    ? Fragment\n    : (props) => {\n        const { children, ...restProps } = props;\n        return (\n          <figure {...restProps}>\n            {children}\n            <figcaption\n              class=\"media-caption\"\n              lang={lang}\n              dir=\"auto\"\n              onClick={(e) => {\n                e.preventDefault();\n                e.stopPropagation();\n                states.showMediaAlt = {\n                  alt: description,\n                  lang,\n                };\n              }}\n            >\n              {description}\n            </figcaption>\n          </figure>\n        );\n      };\n\n  if (isImage) {\n    // Note: type: unknown might not have width/height\n    quickPinchZoomProps.containerProps.style.display = 'inherit';\n\n    useLayoutEffect(() => {\n      if (!isSafari) return;\n      if (!showOriginal) return;\n      (async () => {\n        try {\n          await fetch(mediaURL, { mode: 'no-cors' });\n          mediaRef.current.src = mediaURL;\n        } catch (e) {\n          // Ignore\n        }\n      })();\n    }, [mediaURL]);\n\n    return (\n      <Figure>\n        <Parent\n          ref={parentRef}\n          class={`media media-image ${className}`}\n          onClick={onClick}\n          data-orientation={orientation}\n          data-has-alt={!showInlineDesc}\n          style={\n            showOriginal\n              ? {\n                  backgroundImage: `url(${previewUrl})`,\n                  backgroundSize: imageSmallerThanParent\n                    ? `${width}px ${height}px`\n                    : undefined,\n                  ...averageColorStyle,\n                }\n              : mediaStyles\n          }\n        >\n          {showOriginal ? (\n            <QuickPinchZoom {...quickPinchZoomProps}>\n              <img\n                ref={mediaRef}\n                src={mediaURL}\n                alt={description}\n                width={width}\n                height={height}\n                data-orientation={orientation}\n                loading=\"eager\"\n                decoding=\"sync\"\n                onLoad={(e) => {\n                  e.target.closest('.media-image').style.backgroundImage = '';\n                  e.target.closest('.media-zoom').style.display = '';\n                  setPinchZoomEnabled(true);\n                }}\n                onError={(e) => {\n                  const { src } = e.target;\n                  if (src === mediaURL && mediaURL !== remoteMediaURL) {\n                    e.target.src = remoteMediaURL;\n                  }\n                }}\n              />\n            </QuickPinchZoom>\n          ) : (\n            <>\n              <img\n                src={mediaURL}\n                alt={showInlineDesc ? '' : description}\n                width={width}\n                height={height}\n                data-orientation={orientation}\n                loading=\"lazy\"\n                style={{\n                  // backgroundColor:\n                  //   rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,\n                  // backgroundPosition: focalBackgroundPosition || 'center',\n                  // Duration based on width or height in pixels\n                  objectPosition: focalPosition || 'center',\n                  // 100px per second (rough estimate)\n                  // Clamp between 5s and 120s\n                  '--anim-duration': `${Math.min(\n                    Math.max(Math.max(width, height) / 100, 5),\n                    120,\n                  )}s`,\n                }}\n                onLoad={(e) => {\n                  // e.target.closest('.media-image').style.backgroundImage = '';\n                  e.target.dataset.loaded = true;\n                }}\n                onError={(e) => {\n                  const { src } = e.target;\n                  if (src === mediaURL && mediaURL !== remoteMediaURL) {\n                    e.target.src = remoteMediaURL;\n                  }\n                }}\n              />\n              {!showInlineDesc && (\n                <AltBadge alt={description} lang={lang} index={altIndex} />\n              )}\n            </>\n          )}\n        </Parent>\n      </Figure>\n    );\n  } else if (type === 'gifv' || type === 'video' || isVideoMaybe) {\n    const shortDuration = original.duration < 31;\n    const isGIF = type === 'gifv' && shortDuration;\n    // If GIF is too long, treat it as a video\n    const loopable = original.duration < 61;\n    const formattedDuration = formatDuration(original.duration);\n    const hoverAnimate = !showOriginal && !autoAnimate && isGIF;\n    const autoGIFAnimate = !showOriginal && autoAnimate && isGIF;\n    const showProgress = original.duration > 5;\n\n    const videoHTML = `\n    <video\n      src=\"${url}\"\n      poster=\"${previewUrl}\"\n      width=\"${width}\"\n      height=\"${height}\"\n      data-orientation=\"${orientation}\"\n      preload=\"auto\"\n      autoplay\n      muted=\"${isGIF}\"\n      ${isGIF ? '' : 'controls'}\n      playsinline\n      loop=\"${loopable}\"\n      ${isGIF ? 'ondblclick=\"this.paused ? this.play() : this.pause()\"' : ''}\n      ${\n        isGIF && showProgress\n          ? \"ontimeupdate=\\\"this.closest('.media-gif') && this.closest('.media-gif').style.setProperty('--progress', `${~~((this.currentTime / this.duration) * 100)}%`)\\\"\"\n          : ''\n      }\n    ></video>\n  `;\n\n    return (\n      <Figure>\n        <Parent\n          class={`media ${className} media-${isGIF ? 'gif' : 'video'} ${\n            autoGIFAnimate ? 'media-contain' : ''\n          } ${hoverAnimate ? 'media-hover-animate' : ''}`}\n          data-orientation={orientation}\n          data-formatted-duration={\n            !showOriginal ? formattedDuration : undefined\n          }\n          data-label={isGIF && !showOriginal && !autoGIFAnimate ? 'GIF' : ''}\n          data-has-alt={!showInlineDesc}\n          // style={{\n          //   backgroundColor:\n          //     rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,\n          // }}\n          style={!showOriginal && mediaStyles}\n          onClick={(e) => {\n            if (hoverAnimate) {\n              try {\n                videoRef.current.pause();\n              } catch (e) {}\n            }\n            onClick(e);\n          }}\n          onMouseEnter={() => {\n            if (hoverAnimate) {\n              try {\n                videoRef.current.play();\n              } catch (e) {}\n            }\n          }}\n          onMouseLeave={() => {\n            if (hoverAnimate) {\n              try {\n                videoRef.current.pause();\n              } catch (e) {}\n            }\n          }}\n          onFocus={() => {\n            if (hoverAnimate) {\n              try {\n                videoRef.current.play();\n              } catch (e) {}\n            }\n          }}\n          onBlur={() => {\n            if (hoverAnimate) {\n              try {\n                videoRef.current.pause();\n              } catch (e) {}\n            }\n          }}\n        >\n          {showOriginal || autoGIFAnimate ? (\n            isGIF && showOriginal ? (\n              <QuickPinchZoom {...quickPinchZoomProps} enabled>\n                <div\n                  ref={mediaRef}\n                  dangerouslySetInnerHTML={{\n                    __html: videoHTML,\n                  }}\n                />\n              </QuickPinchZoom>\n            ) : (\n              <div\n                class=\"video-container\"\n                dangerouslySetInnerHTML={{\n                  __html: videoHTML,\n                }}\n              />\n            )\n          ) : isGIF ? (\n            <video\n              ref={videoRef}\n              src={url}\n              poster={previewUrl}\n              width={width}\n              height={height}\n              data-orientation={orientation}\n              preload=\"auto\"\n              // controls\n              playsinline\n              loop\n              muted\n              onTimeUpdate={\n                showProgress\n                  ? (e) => {\n                      const { target } = e;\n                      const container = target?.closest('.media-gif');\n                      if (container) {\n                        const percentage =\n                          (target.currentTime / target.duration) * 100;\n                        container.style.setProperty(\n                          '--progress',\n                          `${percentage}%`,\n                        );\n                      }\n                    }\n                  : undefined\n              }\n            />\n          ) : (\n            <>\n              <img\n                src={previewUrl}\n                alt={showInlineDesc ? '' : description}\n                width={width}\n                height={height}\n                data-orientation={orientation}\n                loading=\"lazy\"\n              />\n              <div class=\"media-play\">\n                <Icon icon=\"play\" size=\"xl\" />\n              </div>\n            </>\n          )}\n          {!showOriginal && !showInlineDesc && (\n            <AltBadge alt={description} lang={lang} index={altIndex} />\n          )}\n        </Parent>\n      </Figure>\n    );\n  } else if (type === 'audio') {\n    const formattedDuration = formatDuration(original.duration);\n    return (\n      <Figure>\n        <Parent\n          class={`media media-audio ${className}`}\n          data-formatted-duration={\n            !showOriginal ? formattedDuration : undefined\n          }\n          data-has-alt={!showInlineDesc}\n          onClick={onClick}\n          style={!showOriginal && mediaStyles}\n        >\n          {showOriginal ? (\n            <audio src={remoteUrl || url} preload=\"none\" controls autoplay />\n          ) : previewUrl ? (\n            <img\n              src={previewUrl}\n              alt={showInlineDesc ? '' : description}\n              width={width}\n              height={height}\n              data-orientation={orientation}\n              loading=\"lazy\"\n            />\n          ) : null}\n          {!showOriginal && (\n            <>\n              <div class=\"media-play\">\n                <Icon icon=\"play\" size=\"xl\" />\n              </div>\n              {!showInlineDesc && (\n                <AltBadge alt={description} lang={lang} index={altIndex} />\n              )}\n            </>\n          )}\n        </Parent>\n      </Figure>\n    );\n  }\n}\n\nexport default Media;\n","import { FocusableItem } from '@szhsin/react-menu';\n\nimport Link from './link';\n\nfunction MenuLink(props) {\n  return (\n    <FocusableItem>\n      {({ ref, closeMenu }) => (\n        <Link\n          {...props}\n          ref={ref}\n          onClick={({ detail }) =>\n            closeMenu(detail === 0 ? 'Enter' : undefined)\n          }\n        />\n      )}\n    </FocusableItem>\n  );\n}\n\nexport default MenuLink;\n","import './status.css';\n\nimport '@justinribeiro/lite-youtube';\nimport {\n  ControlledMenu,\n  Menu,\n  MenuDivider,\n  MenuHeader,\n  MenuItem,\n} from '@szhsin/react-menu';\nimport { decodeBlurHash, getBlurHashAverageColor } from 'fast-blurhash';\nimport { memo } from 'preact/compat';\nimport {\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from 'preact/hooks';\nimport { useHotkeys } from 'react-hotkeys-hook';\nimport { useLongPress } from 'use-long-press';\nimport { useSnapshot } from 'valtio';\n\nimport AccountBlock from '../components/account-block';\nimport EmojiText from '../components/emoji-text';\nimport Loader from '../components/loader';\nimport Menu2 from '../components/menu2';\nimport MenuConfirm from '../components/menu-confirm';\nimport Modal from '../components/modal';\nimport NameText from '../components/name-text';\nimport Poll from '../components/poll';\nimport { api } from '../utils/api';\nimport emojifyText from '../utils/emojify-text';\nimport enhanceContent from '../utils/enhance-content';\nimport FilterContext from '../utils/filter-context';\nimport { isFiltered } from '../utils/filters';\nimport getTranslateTargetLanguage from '../utils/get-translate-target-language';\nimport getHTMLText from '../utils/getHTMLText';\nimport handleContentLinks from '../utils/handle-content-links';\nimport htmlContentLength from '../utils/html-content-length';\nimport isMastodonLinkMaybe from '../utils/isMastodonLinkMaybe';\nimport localeMatch from '../utils/locale-match';\nimport niceDateTime from '../utils/nice-date-time';\nimport openCompose from '../utils/open-compose';\nimport pmem from '../utils/pmem';\nimport safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';\nimport shortenNumber from '../utils/shorten-number';\nimport showToast from '../utils/show-toast';\nimport { speak, supportsTTS } from '../utils/speech';\nimport states, { getStatus, saveStatus, statusKey } from '../utils/states';\nimport statusPeek from '../utils/status-peek';\nimport store from '../utils/store';\nimport unfurlMastodonLink from '../utils/unfurl-link';\nimport useTruncated from '../utils/useTruncated';\nimport visibilityIconsMap from '../utils/visibility-icons-map';\n\nimport Avatar from './avatar';\nimport Icon from './icon';\nimport Link from './link';\nimport Media from './media';\nimport { isMediaCaptionLong } from './media';\nimport MenuLink from './menu-link';\nimport RelativeTime from './relative-time';\nimport TranslationBlock from './translation-block';\n\nconst SHOW_COMMENT_COUNT_LIMIT = 280;\nconst INLINE_TRANSLATE_LIMIT = 140;\n\nfunction fetchAccount(id, masto) {\n  return masto.v1.accounts.$select(id).fetch();\n}\nconst memFetchAccount = pmem(fetchAccount);\n\nconst visibilityText = {\n  public: 'Public',\n  unlisted: 'Unlisted',\n  private: 'Followers only',\n  direct: 'Private mention',\n};\n\nconst isIOS =\n  window.ontouchstart !== undefined &&\n  /iPad|iPhone|iPod/.test(navigator.userAgent);\n\nconst REACTIONS_LIMIT = 80;\n\nfunction getPollText(poll) {\n  if (!poll?.options?.length) return '';\n  return `📊:\\n${poll.options\n    .map(\n      (option) =>\n        `- ${option.title}${\n          option.votesCount >= 0 ? ` (${option.votesCount})` : ''\n        }`,\n    )\n    .join('\\n')}`;\n}\nfunction getPostText(status) {\n  const { spoilerText, content, poll } = status;\n  return (\n    (spoilerText ? `${spoilerText}\\n\\n` : '') +\n    getHTMLText(content) +\n    getPollText(poll)\n  );\n}\n\nfunction Status({\n  statusID,\n  status,\n  instance: propInstance,\n  size = 'm',\n  contentTextWeight,\n  readOnly,\n  enableCommentHint,\n  withinContext,\n  skeleton,\n  enableTranslate,\n  forceTranslate: _forceTranslate,\n  previewMode,\n  // allowFilters,\n  onMediaClick,\n  quoted,\n  onStatusLinkClick = () => {},\n  showFollowedTags,\n}) {\n  if (skeleton) {\n    return (\n      <div class=\"status skeleton\">\n        <Avatar size=\"xxl\" />\n        <div class=\"container\">\n          <div class=\"meta\">███ ████████</div>\n          <div class=\"content-container\">\n            <div class=\"content\">\n              <p>████ ████████</p>\n            </div>\n          </div>\n        </div>\n      </div>\n    );\n  }\n  const { masto, instance, authenticated } = api({ instance: propInstance });\n  const { instance: currentInstance } = api();\n  const sameInstance = instance === currentInstance;\n\n  let sKey = statusKey(statusID || status?.id, instance);\n  const snapStates = useSnapshot(states);\n  if (!status) {\n    status = snapStates.statuses[sKey] || snapStates.statuses[statusID];\n    sKey = statusKey(status?.id, instance);\n  }\n  if (!status) {\n    return null;\n  }\n\n  const {\n    account: {\n      acct,\n      avatar,\n      avatarStatic,\n      id: accountId,\n      url: accountURL,\n      displayName,\n      username,\n      emojis: accountEmojis,\n      bot,\n      group,\n    },\n    id,\n    repliesCount,\n    reblogged,\n    reblogsCount,\n    favourited,\n    favouritesCount,\n    bookmarked,\n    poll,\n    muted,\n    sensitive,\n    spoilerText,\n    visibility, // public, unlisted, private, direct\n    language,\n    editedAt,\n    filtered,\n    card,\n    createdAt,\n    inReplyToId,\n    inReplyToAccountId,\n    content,\n    mentions,\n    mediaAttachments,\n    reblog,\n    uri,\n    url,\n    emojis,\n    tags,\n    // Non-API props\n    _deleted,\n    _pinned,\n    // _filtered,\n  } = status;\n\n  const currentAccount = useMemo(() => {\n    return store.session.get('currentAccount');\n  }, []);\n  const isSelf = useMemo(() => {\n    return currentAccount && currentAccount === accountId;\n  }, [accountId, currentAccount]);\n\n  const filterContext = useContext(FilterContext);\n  const filterInfo =\n    !isSelf && !readOnly && !previewMode && isFiltered(filtered, filterContext);\n\n  if (filterInfo?.action === 'hide') {\n    return null;\n  }\n\n  console.debug('RENDER Status', id, status?.account.displayName, quoted);\n\n  const debugHover = (e) => {\n    if (e.shiftKey) {\n      console.log({\n        ...status,\n      });\n    }\n  };\n\n  if (/*allowFilters && */ size !== 'l' && filterInfo) {\n    return (\n      <FilteredStatus\n        status={status}\n        filterInfo={filterInfo}\n        instance={instance}\n        containerProps={{\n          onMouseEnter: debugHover,\n        }}\n        showFollowedTags\n      />\n    );\n  }\n\n  const createdAtDate = new Date(createdAt);\n  const editedAtDate = new Date(editedAt);\n\n  let inReplyToAccountRef = mentions?.find(\n    (mention) => mention.id === inReplyToAccountId,\n  );\n  if (!inReplyToAccountRef && inReplyToAccountId === id) {\n    inReplyToAccountRef = { url: accountURL, username, displayName };\n  }\n  const [inReplyToAccount, setInReplyToAccount] = useState(inReplyToAccountRef);\n  if (!withinContext && !inReplyToAccount && inReplyToAccountId) {\n    const account = states.accounts[inReplyToAccountId];\n    if (account) {\n      setInReplyToAccount(account);\n    } else {\n      memFetchAccount(inReplyToAccountId, masto)\n        .then((account) => {\n          setInReplyToAccount(account);\n          states.accounts[account.id] = account;\n        })\n        .catch((e) => {});\n    }\n  }\n  const mentionSelf =\n    inReplyToAccountId === currentAccount ||\n    mentions?.find((mention) => mention.id === currentAccount);\n\n  const readingExpandSpoilers = useMemo(() => {\n    const prefs = store.account.get('preferences') || {};\n    return !!prefs['reading:expand:spoilers'];\n  }, []);\n  const readingExpandMedia = useMemo(() => {\n    // default | show_all | hide_all\n    // Ignore hide_all because it means hide *ALL* media including non-sensitive ones\n    const prefs = store.account.get('preferences') || {};\n    return prefs['reading:expand:media'] || 'default';\n  }, []);\n  // FOR TESTING:\n  // const readingExpandSpoilers = true;\n  // const readingExpandMedia = 'show_all';\n  const showSpoiler =\n    previewMode || readingExpandSpoilers || !!snapStates.spoilers[id];\n  const showSpoilerMedia =\n    previewMode ||\n    readingExpandMedia === 'show_all' ||\n    !!snapStates.spoilersMedia[id];\n\n  if (reblog) {\n    // If has statusID, means useItemID (cached in states)\n\n    if (group) {\n      return (\n        <div\n          data-state-post-id={sKey}\n          class=\"status-group\"\n          onMouseEnter={debugHover}\n        >\n          <div class=\"status-pre-meta\">\n            <Icon icon=\"group\" size=\"l\" alt=\"Group\" />{' '}\n            <NameText account={status.account} instance={instance} showAvatar />\n          </div>\n          <Status\n            status={statusID ? null : reblog}\n            statusID={statusID ? reblog.id : null}\n            instance={instance}\n            size={size}\n            contentTextWeight={contentTextWeight}\n            readOnly={readOnly}\n          />\n        </div>\n      );\n    }\n\n    return (\n      <div\n        data-state-post-id={sKey}\n        class=\"status-reblog\"\n        onMouseEnter={debugHover}\n      >\n        <div class=\"status-pre-meta\">\n          <Icon icon=\"rocket\" size=\"l\" />{' '}\n          <NameText account={status.account} instance={instance} showAvatar />{' '}\n          <span>boosted</span>\n        </div>\n        <Status\n          status={statusID ? null : reblog}\n          statusID={statusID ? reblog.id : null}\n          instance={instance}\n          size={size}\n          contentTextWeight={contentTextWeight}\n          readOnly={readOnly}\n          enableCommentHint\n        />\n      </div>\n    );\n  }\n\n  // Check followedTags\n  if (showFollowedTags && !!snapStates.statusFollowedTags[sKey]?.length) {\n    return (\n      <div\n        data-state-post-id={sKey}\n        class=\"status-followed-tags\"\n        onMouseEnter={debugHover}\n      >\n        <div class=\"status-pre-meta\">\n          <Icon icon=\"hashtag\" size=\"l\" />{' '}\n          {snapStates.statusFollowedTags[sKey].slice(0, 3).map((tag) => (\n            <Link\n              key={tag}\n              to={instance ? `/${instance}/t/${tag}` : `/t/${tag}`}\n              class=\"status-followed-tag-item\"\n            >\n              {tag}\n            </Link>\n          ))}\n        </div>\n        <Status\n          status={statusID ? null : status}\n          statusID={statusID ? status.id : null}\n          instance={instance}\n          size={size}\n          contentTextWeight={contentTextWeight}\n          readOnly={readOnly}\n          enableCommentHint\n        />\n      </div>\n    );\n  }\n\n  const isSizeLarge = size === 'l';\n\n  const [forceTranslate, setForceTranslate] = useState(_forceTranslate);\n  const targetLanguage = getTranslateTargetLanguage(true);\n  const contentTranslationHideLanguages =\n    snapStates.settings.contentTranslationHideLanguages || [];\n  const { contentTranslation, contentTranslationAutoInline } =\n    snapStates.settings;\n  if (!contentTranslation) enableTranslate = false;\n  const inlineTranslate = useMemo(() => {\n    if (\n      !contentTranslation ||\n      !contentTranslationAutoInline ||\n      readOnly ||\n      (withinContext && !isSizeLarge) ||\n      previewMode ||\n      spoilerText ||\n      sensitive ||\n      poll ||\n      card ||\n      mediaAttachments?.length\n    ) {\n      return false;\n    }\n    const contentLength = htmlContentLength(content);\n    return contentLength > 0 && contentLength <= INLINE_TRANSLATE_LIMIT;\n  }, [\n    contentTranslation,\n    contentTranslationAutoInline,\n    readOnly,\n    withinContext,\n    isSizeLarge,\n    previewMode,\n    spoilerText,\n    sensitive,\n    poll,\n    card,\n    mediaAttachments,\n    content,\n  ]);\n\n  const [showEdited, setShowEdited] = useState(false);\n\n  const spoilerContentRef = useTruncated();\n  const contentRef = useTruncated();\n  const mediaContainerRef = useTruncated();\n  const readMoreText = 'Read more →';\n\n  const statusRef = useRef(null);\n\n  const unauthInteractionErrorMessage = `Sorry, your current logged-in instance can't interact with this post from another instance.`;\n\n  const textWeight = useCallback(\n    () =>\n      Math.max(\n        Math.round((spoilerText.length + htmlContentLength(content)) / 140) ||\n          1,\n        1,\n      ),\n    [spoilerText, content],\n  );\n\n  const createdDateText = niceDateTime(createdAtDate);\n  const editedDateText = editedAt && niceDateTime(editedAtDate);\n\n  // Can boost if:\n  // - authenticated AND\n  // - visibility != direct OR\n  // - visibility = private AND isSelf\n  let canBoost =\n    authenticated && visibility !== 'direct' && visibility !== 'private';\n  if (visibility === 'private' && isSelf) {\n    canBoost = true;\n  }\n\n  const replyStatus = (e) => {\n    if (!sameInstance || !authenticated) {\n      return alert(unauthInteractionErrorMessage);\n    }\n    // syntheticEvent comes from MenuItem\n    if (e?.shiftKey || e?.syntheticEvent?.shiftKey) {\n      const newWin = openCompose({\n        replyToStatus: status,\n      });\n      if (newWin) return;\n    }\n    states.showCompose = {\n      replyToStatus: status,\n    };\n  };\n\n  // Check if media has no descriptions\n  const mediaNoDesc = useMemo(() => {\n    return mediaAttachments.some(\n      (attachment) => !attachment.description?.trim?.(),\n    );\n  }, [mediaAttachments]);\n  const boostStatus = async () => {\n    if (!sameInstance || !authenticated) {\n      alert(unauthInteractionErrorMessage);\n      return false;\n    }\n    try {\n      if (!reblogged) {\n        let confirmText = 'Boost this post?';\n        if (mediaNoDesc) {\n          confirmText += '\\n\\n⚠️ Some media have no descriptions.';\n        }\n        const yes = confirm(confirmText);\n        if (!yes) {\n          return false;\n        }\n      }\n      // Optimistic\n      states.statuses[sKey] = {\n        ...status,\n        reblogged: !reblogged,\n        reblogsCount: reblogsCount + (reblogged ? -1 : 1),\n      };\n      if (reblogged) {\n        const newStatus = await masto.v1.statuses.$select(id).unreblog();\n        saveStatus(newStatus, instance);\n        return true;\n      } else {\n        const newStatus = await masto.v1.statuses.$select(id).reblog();\n        saveStatus(newStatus, instance);\n        return true;\n      }\n    } catch (e) {\n      console.error(e);\n      // Revert optimistism\n      states.statuses[sKey] = status;\n      return false;\n    }\n  };\n  const confirmBoostStatus = async () => {\n    if (!sameInstance || !authenticated) {\n      alert(unauthInteractionErrorMessage);\n      return false;\n    }\n    try {\n      // Optimistic\n      states.statuses[sKey] = {\n        ...status,\n        reblogged: !reblogged,\n        reblogsCount: reblogsCount + (reblogged ? -1 : 1),\n      };\n      if (reblogged) {\n        const newStatus = await masto.v1.statuses.$select(id).unreblog();\n        saveStatus(newStatus, instance);\n        return true;\n      } else {\n        const newStatus = await masto.v1.statuses.$select(id).reblog();\n        saveStatus(newStatus, instance);\n        return true;\n      }\n    } catch (e) {\n      console.error(e);\n      // Revert optimistism\n      states.statuses[sKey] = status;\n      return false;\n    }\n  };\n\n  const favouriteStatus = async () => {\n    if (!sameInstance || !authenticated) {\n      return alert(unauthInteractionErrorMessage);\n    }\n    try {\n      // Optimistic\n      states.statuses[sKey] = {\n        ...status,\n        favourited: !favourited,\n        favouritesCount: favouritesCount + (favourited ? -1 : 1),\n      };\n      if (favourited) {\n        const newStatus = await masto.v1.statuses.$select(id).unfavourite();\n        saveStatus(newStatus, instance);\n      } else {\n        const newStatus = await masto.v1.statuses.$select(id).favourite();\n        saveStatus(newStatus, instance);\n      }\n    } catch (e) {\n      console.error(e);\n      // Revert optimistism\n      states.statuses[sKey] = status;\n    }\n  };\n\n  const bookmarkStatus = async () => {\n    if (!sameInstance || !authenticated) {\n      return alert(unauthInteractionErrorMessage);\n    }\n    try {\n      // Optimistic\n      states.statuses[sKey] = {\n        ...status,\n        bookmarked: !bookmarked,\n      };\n      if (bookmarked) {\n        const newStatus = await masto.v1.statuses.$select(id).unbookmark();\n        saveStatus(newStatus, instance);\n      } else {\n        const newStatus = await masto.v1.statuses.$select(id).bookmark();\n        saveStatus(newStatus, instance);\n      }\n    } catch (e) {\n      console.error(e);\n      // Revert optimistism\n      states.statuses[sKey] = status;\n    }\n  };\n\n  const differentLanguage =\n    !!language &&\n    language !== targetLanguage &&\n    !localeMatch([language], [targetLanguage]) &&\n    !contentTranslationHideLanguages.find(\n      (l) => language === l || localeMatch([language], [l]),\n    );\n\n  const reblogIterator = useRef();\n  const favouriteIterator = useRef();\n  async function fetchBoostedLikedByAccounts(firstLoad) {\n    if (firstLoad) {\n      reblogIterator.current = masto.v1.statuses\n        .$select(statusID)\n        .rebloggedBy.list({\n          limit: REACTIONS_LIMIT,\n        });\n      favouriteIterator.current = masto.v1.statuses\n        .$select(statusID)\n        .favouritedBy.list({\n          limit: REACTIONS_LIMIT,\n        });\n    }\n    const [{ value: reblogResults }, { value: favouriteResults }] =\n      await Promise.allSettled([\n        reblogIterator.current.next(),\n        favouriteIterator.current.next(),\n      ]);\n    if (reblogResults.value?.length || favouriteResults.value?.length) {\n      const accounts = [];\n      if (reblogResults.value?.length) {\n        accounts.push(\n          ...reblogResults.value.map((a) => {\n            a._types = ['reblog'];\n            return a;\n          }),\n        );\n      }\n      if (favouriteResults.value?.length) {\n        accounts.push(\n          ...favouriteResults.value.map((a) => {\n            a._types = ['favourite'];\n            return a;\n          }),\n        );\n      }\n      return {\n        value: accounts,\n        done: reblogResults.done && favouriteResults.done,\n      };\n    }\n    return {\n      value: [],\n      done: true,\n    };\n  }\n\n  const menuInstanceRef = useRef();\n  const StatusMenuItems = (\n    <>\n      {!isSizeLarge && (\n        <>\n          <MenuHeader>\n            <span class=\"ib\">\n              <Icon icon={visibilityIconsMap[visibility]} size=\"s\" />{' '}\n              <span>{visibilityText[visibility]}</span>\n            </span>{' '}\n            <span class=\"ib\">\n              {repliesCount > 0 && (\n                <span>\n                  <Icon icon=\"comment2\" alt=\"Replies\" size=\"s\" />{' '}\n                  <span>{shortenNumber(repliesCount)}</span>\n                </span>\n              )}{' '}\n              {reblogsCount > 0 && (\n                <span>\n                  <Icon icon=\"rocket\" alt=\"Boosts\" size=\"s\" />{' '}\n                  <span>{shortenNumber(reblogsCount)}</span>\n                </span>\n              )}{' '}\n              {favouritesCount > 0 && (\n                <span>\n                  <Icon icon=\"heart\" alt=\"Likes\" size=\"s\" />{' '}\n                  <span>{shortenNumber(favouritesCount)}</span>\n                </span>\n              )}\n            </span>\n            <br />\n            {createdDateText}\n          </MenuHeader>\n          <MenuLink\n            to={instance ? `/${instance}/s/${id}` : `/s/${id}`}\n            onClick={(e) => {\n              onStatusLinkClick(e, status);\n            }}\n          >\n            <Icon icon=\"arrow-right\" />\n            <span>View post by @{username || acct}</span>\n          </MenuLink>\n        </>\n      )}\n      {!!editedAt && (\n        <MenuItem\n          onClick={() => {\n            setShowEdited(id);\n          }}\n        >\n          <Icon icon=\"history\" />\n          <span>\n            Show Edit History\n            <br />\n            <small class=\"more-insignificant\">Edited: {editedDateText}</small>\n          </span>\n        </MenuItem>\n      )}\n      {(!isSizeLarge || !!editedAt) && <MenuDivider />}\n      {isSizeLarge && (\n        <MenuItem\n          onClick={() => {\n            states.showGenericAccounts = {\n              heading: 'Boosted/Liked by…',\n              fetchAccounts: fetchBoostedLikedByAccounts,\n              instance,\n              showReactions: true,\n            };\n          }}\n        >\n          <Icon icon=\"react\" />\n          <span>\n            Boosted/Liked by<span class=\"more-insignificant\">…</span>\n          </span>\n        </MenuItem>\n      )}\n      {!isSizeLarge && sameInstance && (\n        <>\n          <div class=\"menu-horizontal\">\n            <MenuConfirm\n              subMenu\n              confirmLabel={\n                <>\n                  <Icon icon=\"rocket\" />\n                  <span>{reblogged ? 'Unboost?' : 'Boost to everyone?'}</span>\n                </>\n              }\n              menuFooter={\n                mediaNoDesc &&\n                !reblogged && (\n                  <div class=\"footer\">\n                    <Icon icon=\"alert\" />\n                    Some media have no descriptions.\n                  </div>\n                )\n              }\n              disabled={!canBoost}\n              onClick={async () => {\n                try {\n                  const done = await confirmBoostStatus();\n                  if (!isSizeLarge && done) {\n                    showToast(\n                      reblogged\n                        ? `Unboosted @${username || acct}'s post`\n                        : `Boosted @${username || acct}'s post`,\n                    );\n                  }\n                } catch (e) {}\n              }}\n            >\n              <Icon\n                icon=\"rocket\"\n                style={{\n                  color: reblogged && 'var(--reblog-color)',\n                }}\n              />\n              <span>{reblogged ? 'Unboost' : 'Boost…'}</span>\n            </MenuConfirm>\n            <MenuItem\n              onClick={() => {\n                try {\n                  favouriteStatus();\n                  if (!isSizeLarge) {\n                    showToast(\n                      favourited\n                        ? `Unliked @${username || acct}'s post`\n                        : `Liked @${username || acct}'s post`,\n                    );\n                  }\n                } catch (e) {}\n              }}\n            >\n              <Icon\n                icon=\"heart\"\n                style={{\n                  color: favourited && 'var(--favourite-color)',\n                }}\n              />\n              <span>{favourited ? 'Unlike' : 'Like'}</span>\n            </MenuItem>\n          </div>\n          <div class=\"menu-horizontal\">\n            <MenuItem onClick={replyStatus}>\n              <Icon icon=\"reply\" />\n              <span>Reply</span>\n            </MenuItem>\n            <MenuItem\n              onClick={() => {\n                try {\n                  bookmarkStatus();\n                  if (!isSizeLarge) {\n                    showToast(\n                      bookmarked\n                        ? `Unbookmarked @${username || acct}'s post`\n                        : `Bookmarked @${username || acct}'s post`,\n                    );\n                  }\n                } catch (e) {}\n              }}\n            >\n              <Icon\n                icon=\"bookmark\"\n                style={{\n                  color: bookmarked && 'var(--link-color)',\n                }}\n              />\n              <span>{bookmarked ? 'Unbookmark' : 'Bookmark'}</span>\n            </MenuItem>\n          </div>\n        </>\n      )}\n      {enableTranslate ? (\n        <div class={supportsTTS ? 'menu-horizontal' : ''}>\n          <MenuItem\n            disabled={forceTranslate}\n            onClick={() => {\n              setForceTranslate(true);\n            }}\n          >\n            <Icon icon=\"translate\" />\n            <span>Translate</span>\n          </MenuItem>\n          {supportsTTS && (\n            <MenuItem\n              onClick={() => {\n                const postText = getPostText(status);\n                if (postText) {\n                  speak(postText, language);\n                }\n              }}\n            >\n              <Icon icon=\"speak\" />\n              <span>Speak</span>\n            </MenuItem>\n          )}\n        </div>\n      ) : (\n        (!language || differentLanguage) && (\n          <div class={supportsTTS ? 'menu-horizontal' : ''}>\n            <MenuLink\n              to={`${instance ? `/${instance}` : ''}/s/${id}?translate=1`}\n            >\n              <Icon icon=\"translate\" />\n              <span>Translate</span>\n            </MenuLink>\n            {supportsTTS && (\n              <MenuItem\n                onClick={() => {\n                  const postText = getPostText(status);\n                  if (postText) {\n                    speak(postText, language);\n                  }\n                }}\n              >\n                <Icon icon=\"speak\" />\n                <span>Speak</span>\n              </MenuItem>\n            )}\n          </div>\n        )\n      )}\n      {((!isSizeLarge && sameInstance) || enableTranslate) && <MenuDivider />}\n      <MenuItem href={url} target=\"_blank\">\n        <Icon icon=\"external\" />\n        <small class=\"menu-double-lines\">{nicePostURL(url)}</small>\n      </MenuItem>\n      <div class=\"menu-horizontal\">\n        <MenuItem\n          onClick={() => {\n            // Copy url to clipboard\n            try {\n              navigator.clipboard.writeText(url);\n              showToast('Link copied');\n            } catch (e) {\n              console.error(e);\n              showToast('Unable to copy link');\n            }\n          }}\n        >\n          <Icon icon=\"link\" />\n          <span>Copy</span>\n        </MenuItem>\n        {navigator?.share &&\n          navigator?.canShare?.({\n            url,\n          }) && (\n            <MenuItem\n              onClick={() => {\n                try {\n                  navigator.share({\n                    url,\n                  });\n                } catch (e) {\n                  console.error(e);\n                  alert(\"Sharing doesn't seem to work.\");\n                }\n              }}\n            >\n              <Icon icon=\"share\" />\n              <span>Share…</span>\n            </MenuItem>\n          )}\n      </div>\n      {(isSelf || mentionSelf) && <MenuDivider />}\n      {(isSelf || mentionSelf) && (\n        <MenuItem\n          onClick={async () => {\n            try {\n              const newStatus = await masto.v1.statuses\n                .$select(id)\n                [muted ? 'unmute' : 'mute']();\n              saveStatus(newStatus, instance);\n              showToast(muted ? 'Conversation unmuted' : 'Conversation muted');\n            } catch (e) {\n              console.error(e);\n              showToast(\n                muted\n                  ? 'Unable to unmute conversation'\n                  : 'Unable to mute conversation',\n              );\n            }\n          }}\n        >\n          {muted ? (\n            <>\n              <Icon icon=\"unmute\" />\n              <span>Unmute conversation</span>\n            </>\n          ) : (\n            <>\n              <Icon icon=\"mute\" />\n              <span>Mute conversation</span>\n            </>\n          )}\n        </MenuItem>\n      )}\n      {isSelf && (\n        <div class=\"menu-horizontal\">\n          <MenuItem\n            onClick={() => {\n              states.showCompose = {\n                editStatus: status,\n              };\n            }}\n          >\n            <Icon icon=\"pencil\" />\n            <span>Edit</span>\n          </MenuItem>\n          {isSizeLarge && (\n            <MenuConfirm\n              subMenu\n              confirmLabel={\n                <>\n                  <Icon icon=\"trash\" />\n                  <span>Delete this post?</span>\n                </>\n              }\n              menuItemClassName=\"danger\"\n              onClick={() => {\n                // const yes = confirm('Delete this post?');\n                // if (yes) {\n                (async () => {\n                  try {\n                    await masto.v1.statuses.$select(id).remove();\n                    const cachedStatus = getStatus(id, instance);\n                    cachedStatus._deleted = true;\n                    showToast('Deleted');\n                  } catch (e) {\n                    console.error(e);\n                    showToast('Unable to delete');\n                  }\n                })();\n                // }\n              }}\n            >\n              <Icon icon=\"trash\" />\n              <span>Delete…</span>\n            </MenuConfirm>\n          )}\n        </div>\n      )}\n    </>\n  );\n\n  const contextMenuRef = useRef();\n  const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);\n  const [contextMenuProps, setContextMenuProps] = useState({});\n\n  const showContextMenu = !isSizeLarge && !previewMode && !_deleted && !quoted;\n\n  // Only iOS/iPadOS browsers don't support contextmenu\n  // Some comments report iPadOS might support contextmenu if a mouse is connected\n  const bindLongPressContext = useLongPress(\n    isIOS && showContextMenu\n      ? (e) => {\n          if (e.pointerType === 'mouse') return;\n          // There's 'pen' too, but not sure if contextmenu event would trigger from a pen\n\n          const { clientX, clientY } = e.touches?.[0] || e;\n          // link detection copied from onContextMenu because here it works\n          const link = e.target.closest('a');\n          if (link && /^https?:\\/\\//.test(link.getAttribute('href'))) return;\n          e.preventDefault();\n          setContextMenuProps({\n            anchorPoint: {\n              x: clientX,\n              y: clientY,\n            },\n            direction: 'right',\n          });\n          setIsContextMenuOpen(true);\n        }\n      : null,\n    {\n      threshold: 600,\n      captureEvent: true,\n      detect: 'touch',\n      cancelOnMovement: 2, // true allows movement of up to 25 pixels\n    },\n  );\n\n  const hotkeysEnabled = !readOnly && !previewMode && !quoted;\n  const rRef = useHotkeys('r, shift+r', replyStatus, {\n    enabled: hotkeysEnabled,\n  });\n  const fRef = useHotkeys(\n    'f, l',\n    () => {\n      try {\n        favouriteStatus();\n        if (!isSizeLarge) {\n          showToast(\n            favourited\n              ? `Unliked @${username || acct}'s post`\n              : `Liked @${username || acct}'s post`,\n          );\n        }\n      } catch (e) {}\n    },\n    {\n      enabled: hotkeysEnabled,\n    },\n  );\n  const dRef = useHotkeys(\n    'd',\n    () => {\n      try {\n        bookmarkStatus();\n        if (!isSizeLarge) {\n          showToast(\n            bookmarked\n              ? `Unbookmarked @${username || acct}'s post`\n              : `Bookmarked @${username || acct}'s post`,\n          );\n        }\n      } catch (e) {}\n    },\n    {\n      enabled: hotkeysEnabled,\n    },\n  );\n  const bRef = useHotkeys(\n    'shift+b',\n    () => {\n      (async () => {\n        try {\n          const done = await confirmBoostStatus();\n          if (!isSizeLarge && done) {\n            showToast(\n              reblogged\n                ? `Unboosted @${username || acct}'s post`\n                : `Boosted @${username || acct}'s post`,\n            );\n          }\n        } catch (e) {}\n      })();\n    },\n    {\n      enabled: hotkeysEnabled && canBoost,\n    },\n  );\n  const xRef = useHotkeys('x', (e) => {\n    const activeStatus = document.activeElement.closest(\n      '.status-link, .status-focus',\n    );\n    if (activeStatus) {\n      const spoilerButton = activeStatus.querySelector(\n        '.spoiler-button:not(.spoiling)',\n      );\n      if (spoilerButton) {\n        e.stopPropagation();\n        spoilerButton.click();\n      } else {\n        const spoilerMediaButton = activeStatus.querySelector(\n          '.spoiler-media-button:not(.spoiling)',\n        );\n        if (spoilerMediaButton) {\n          e.stopPropagation();\n          spoilerMediaButton.click();\n        }\n      }\n    }\n  });\n\n  const displayedMediaAttachments = mediaAttachments.slice(\n    0,\n    isSizeLarge ? undefined : 4,\n  );\n  const showMultipleMediaCaptions =\n    mediaAttachments.length > 1 &&\n    displayedMediaAttachments.some(\n      (media) => !!media.description && !isMediaCaptionLong(media.description),\n    );\n  const captionChildren = useMemo(() => {\n    if (!showMultipleMediaCaptions) return null;\n    const attachments = [];\n    displayedMediaAttachments.forEach((media, i) => {\n      if (!media.description) return;\n      const index = attachments.findIndex(\n        (attachment) => attachment.media.description === media.description,\n      );\n      if (index === -1) {\n        attachments.push({\n          media,\n          indices: [i],\n        });\n      } else {\n        attachments[index].indices.push(i);\n      }\n    });\n    return attachments.map(({ media, indices }) => (\n      <div\n        key={media.id}\n        data-caption-index={indices.map((i) => i + 1).join(' ')}\n        onClick={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          states.showMediaAlt = {\n            alt: media.description,\n            lang: language,\n          };\n        }}\n        title={media.description}\n      >\n        <sup>{indices.map((i) => i + 1).join(' ')}</sup> {media.description}\n      </div>\n    ));\n\n    // return displayedMediaAttachments.map(\n    //   (media, i) =>\n    //     !!media.description && (\n    //       <div\n    //         key={media.id}\n    //         data-caption-index={i + 1}\n    //         onClick={(e) => {\n    //           e.preventDefault();\n    //           e.stopPropagation();\n    //           states.showMediaAlt = {\n    //             alt: media.description,\n    //             lang: language,\n    //           };\n    //         }}\n    //         title={media.description}\n    //       >\n    //         <sup>{i + 1}</sup> {media.description}\n    //       </div>\n    //     ),\n    // );\n  }, [showMultipleMediaCaptions, displayedMediaAttachments, language]);\n\n  const isThread = useMemo(() => {\n    return (\n      (!!inReplyToId && inReplyToAccountId === status.account?.id) ||\n      !!snapStates.statusThreadNumber[sKey]\n    );\n  }, [\n    inReplyToId,\n    inReplyToAccountId,\n    status.account?.id,\n    snapStates.statusThreadNumber[sKey],\n  ]);\n\n  const showCommentHint = useMemo(() => {\n    return (\n      enableCommentHint &&\n      !isThread &&\n      !withinContext &&\n      !inReplyToId &&\n      visibility === 'public' &&\n      repliesCount > 0\n    );\n  }, [\n    enableCommentHint,\n    isThread,\n    withinContext,\n    inReplyToId,\n    repliesCount,\n    visibility,\n  ]);\n  const showCommentCount = useMemo(() => {\n    if (\n      card ||\n      poll ||\n      sensitive ||\n      spoilerText ||\n      mediaAttachments?.length ||\n      isThread ||\n      withinContext ||\n      inReplyToId ||\n      repliesCount <= 0\n    ) {\n      return false;\n    }\n    const questionRegex = /[???︖❓❔⁇⁈⁉¿‽؟]/;\n    const containsQuestion = questionRegex.test(content);\n    if (!containsQuestion) return false;\n    const contentLength = htmlContentLength(content);\n    if (contentLength > 0 && contentLength <= SHOW_COMMENT_COUNT_LIMIT) {\n      return true;\n    }\n  }, [\n    card,\n    poll,\n    sensitive,\n    spoilerText,\n    mediaAttachments,\n    reblog,\n    isThread,\n    withinContext,\n    inReplyToId,\n    repliesCount,\n    content,\n  ]);\n\n  return (\n    <article\n      data-state-post-id={sKey}\n      ref={(node) => {\n        statusRef.current = node;\n        // Use parent node if it's in focus\n        // Use case: <a><status /></a>\n        // When navigating (j/k), the <a> is focused instead of <status />\n        // Hotkey binding doesn't bubble up thus this hack\n        const nodeRef =\n          node?.closest?.(\n            '.timeline-item, .timeline-item-alt, .status-link, .status-focus',\n          ) || node;\n        rRef.current = nodeRef;\n        fRef.current = nodeRef;\n        dRef.current = nodeRef;\n        bRef.current = nodeRef;\n        xRef.current = nodeRef;\n      }}\n      tabindex=\"-1\"\n      class={`status ${\n        !withinContext && inReplyToId && inReplyToAccount\n          ? 'status-reply-to'\n          : ''\n      } visibility-${visibility} ${_pinned ? 'status-pinned' : ''} ${\n        {\n          s: 'small',\n          m: 'medium',\n          l: 'large',\n        }[size]\n      } ${_deleted ? 'status-deleted' : ''} ${quoted ? 'status-card' : ''}`}\n      onMouseEnter={debugHover}\n      onContextMenu={(e) => {\n        // FIXME: this code isn't getting called on Chrome at all?\n        if (!showContextMenu) return;\n        if (e.metaKey) return;\n        // console.log('context menu', e);\n        const link = e.target.closest('a');\n        if (link && /^https?:\\/\\//.test(link.getAttribute('href'))) return;\n        e.preventDefault();\n        setContextMenuProps({\n          anchorPoint: {\n            x: e.clientX,\n            y: e.clientY,\n          },\n          direction: 'right',\n        });\n        setIsContextMenuOpen(true);\n      }}\n      {...(showContextMenu ? bindLongPressContext() : {})}\n    >\n      {showContextMenu && (\n        <ControlledMenu\n          ref={contextMenuRef}\n          state={isContextMenuOpen ? 'open' : undefined}\n          {...contextMenuProps}\n          onClose={(e) => {\n            setIsContextMenuOpen(false);\n            // statusRef.current?.focus?.();\n            if (e?.reason === 'click') {\n              statusRef.current?.closest('[tabindex]')?.focus?.();\n            }\n          }}\n          portal={{\n            target: document.body,\n          }}\n          containerProps={{\n            style: {\n              // Higher than the backdrop\n              zIndex: 1001,\n            },\n            onClick: () => {\n              contextMenuRef.current?.closeMenu?.();\n            },\n          }}\n          overflow=\"auto\"\n          boundingBoxPadding={safeBoundingBoxPadding()}\n          unmountOnClose\n        >\n          {StatusMenuItems}\n        </ControlledMenu>\n      )}\n      {size !== 'l' && (\n        <div class=\"status-badge\">\n          {reblogged && <Icon class=\"reblog\" icon=\"rocket\" size=\"s\" />}\n          {favourited && <Icon class=\"favourite\" icon=\"heart\" size=\"s\" />}\n          {bookmarked && <Icon class=\"bookmark\" icon=\"bookmark\" size=\"s\" />}\n          {_pinned && <Icon class=\"pin\" icon=\"pin\" size=\"s\" />}\n        </div>\n      )}\n      {size !== 's' && (\n        <a\n          href={accountURL}\n          tabindex=\"-1\"\n          // target=\"_blank\"\n          title={`@${acct}`}\n          onClick={(e) => {\n            e.preventDefault();\n            e.stopPropagation();\n            states.showAccount = {\n              account: status.account,\n              instance,\n            };\n          }}\n        >\n          <Avatar url={avatarStatic || avatar} size=\"xxl\" squircle={bot} />\n        </a>\n      )}\n      <div class=\"container\">\n        <div class=\"meta\">\n          <span class=\"meta-name\">\n            <NameText\n              account={status.account}\n              instance={instance}\n              showAvatar={size === 's'}\n              showAcct={isSizeLarge}\n            />\n          </span>\n          {/* {inReplyToAccount && !withinContext && size !== 's' && (\n              <>\n                {' '}\n                <span class=\"ib\">\n                  <Icon icon=\"arrow-right\" class=\"arrow\" />{' '}\n                  <NameText account={inReplyToAccount} instance={instance} short />\n                </span>\n              </>\n            )} */}\n          {/* </span> */}{' '}\n          {size !== 'l' &&\n            (_deleted ? (\n              <span class=\"status-deleted-tag\">Deleted</span>\n            ) : url && !previewMode && !quoted ? (\n              <Link\n                to={instance ? `/${instance}/s/${id}` : `/s/${id}`}\n                onClick={(e) => {\n                  if (\n                    e.metaKey ||\n                    e.ctrlKey ||\n                    e.shiftKey ||\n                    e.altKey ||\n                    e.which === 2\n                  ) {\n                    return;\n                  }\n                  e.preventDefault();\n                  e.stopPropagation();\n                  onStatusLinkClick?.(e, status);\n                  setContextMenuProps({\n                    anchorRef: {\n                      current: e.currentTarget,\n                    },\n                    align: 'end',\n                    direction: 'bottom',\n                    gap: 4,\n                  });\n                  setIsContextMenuOpen(true);\n                }}\n                class={`time ${\n                  isContextMenuOpen && contextMenuProps?.anchorRef\n                    ? 'is-open'\n                    : ''\n                }`}\n              >\n                {showCommentHint && !showCommentCount ? (\n                  <Icon\n                    icon=\"comment2\"\n                    size=\"s\"\n                    alt={`${repliesCount} ${\n                      repliesCount === 1 ? 'reply' : 'replies'\n                    }`}\n                  />\n                ) : (\n                  <Icon\n                    icon={visibilityIconsMap[visibility]}\n                    alt={visibilityText[visibility]}\n                    size=\"s\"\n                  />\n                )}{' '}\n                <RelativeTime datetime={createdAtDate} format=\"micro\" />\n              </Link>\n            ) : (\n              // <Menu\n              //   instanceRef={menuInstanceRef}\n              //   portal={{\n              //     target: document.body,\n              //   }}\n              //   containerProps={{\n              //     style: {\n              //       // Higher than the backdrop\n              //       zIndex: 1001,\n              //     },\n              //     onClick: (e) => {\n              //       if (e.target === e.currentTarget)\n              //         menuInstanceRef.current?.closeMenu?.();\n              //     },\n              //   }}\n              //   align=\"end\"\n              //   gap={4}\n              //   overflow=\"auto\"\n              //   viewScroll=\"close\"\n              //   boundingBoxPadding=\"8 8 8 8\"\n              //   unmountOnClose\n              //   menuButton={({ open }) => (\n              //     <Link\n              //       to={instance ? `/${instance}/s/${id}` : `/s/${id}`}\n              //       onClick={(e) => {\n              //         e.preventDefault();\n              //         e.stopPropagation();\n              //         onStatusLinkClick?.(e, status);\n              //       }}\n              //       class={`time ${open ? 'is-open' : ''}`}\n              //     >\n              //       <Icon\n              //         icon={visibilityIconsMap[visibility]}\n              //         alt={visibilityText[visibility]}\n              //         size=\"s\"\n              //       />{' '}\n              //       <RelativeTime datetime={createdAtDate} format=\"micro\" />\n              //     </Link>\n              //   )}\n              // >\n              //   {StatusMenuItems}\n              // </Menu>\n              <span class=\"time\">\n                <Icon\n                  icon={visibilityIconsMap[visibility]}\n                  alt={visibilityText[visibility]}\n                  size=\"s\"\n                />{' '}\n                <RelativeTime datetime={createdAtDate} format=\"micro\" />\n              </span>\n            ))}\n        </div>\n        {visibility === 'direct' && (\n          <>\n            <div class=\"status-direct-badge\">Private mention</div>{' '}\n          </>\n        )}\n        {!withinContext && (\n          <>\n            {isThread ? (\n              <div class=\"status-thread-badge\">\n                <Icon icon=\"thread\" size=\"s\" />\n                Thread\n                {snapStates.statusThreadNumber[sKey]\n                  ? ` ${snapStates.statusThreadNumber[sKey]}/X`\n                  : ''}\n              </div>\n            ) : (\n              !!inReplyToId &&\n              !!inReplyToAccount &&\n              (!!spoilerText ||\n                !mentions.find((mention) => {\n                  return mention.id === inReplyToAccountId;\n                })) && (\n                <div class=\"status-reply-badge\">\n                  <Icon icon=\"reply\" />{' '}\n                  <NameText\n                    account={inReplyToAccount}\n                    instance={instance}\n                    short\n                  />\n                </div>\n              )\n            )}\n          </>\n        )}\n        <div\n          class={`content-container ${\n            spoilerText || sensitive ? 'has-spoiler' : ''\n          } ${showSpoiler ? 'show-spoiler' : ''} ${\n            showSpoilerMedia ? 'show-media' : ''\n          }`}\n          data-content-text-weight={contentTextWeight ? textWeight() : null}\n          style={\n            (isSizeLarge || contentTextWeight) && {\n              '--content-text-weight': textWeight(),\n            }\n          }\n        >\n          {!!spoilerText && (\n            <>\n              <div\n                class=\"content spoiler-content\"\n                lang={language}\n                dir=\"auto\"\n                ref={spoilerContentRef}\n                data-read-more={readMoreText}\n              >\n                <p>\n                  <EmojiText text={spoilerText} emojis={emojis} />\n                </p>\n              </div>\n              {readingExpandSpoilers || previewMode ? (\n                <div class=\"spoiler-divider\">\n                  <Icon icon=\"eye-open\" /> Content warning\n                </div>\n              ) : (\n                <button\n                  class={`light spoiler-button ${\n                    showSpoiler ? 'spoiling' : ''\n                  }`}\n                  type=\"button\"\n                  onClick={(e) => {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    if (showSpoiler) {\n                      delete states.spoilers[id];\n                      if (!readingExpandSpoilers) {\n                        delete states.spoilersMedia[id];\n                      }\n                    } else {\n                      states.spoilers[id] = true;\n                      if (!readingExpandSpoilers) {\n                        states.spoilersMedia[id] = true;\n                      }\n                    }\n                  }}\n                >\n                  <Icon icon={showSpoiler ? 'eye-open' : 'eye-close'} />{' '}\n                  {showSpoiler ? 'Show less' : 'Show content'}\n                </button>\n              )}\n            </>\n          )}\n          {!!content && (\n            <div class=\"content\" ref={contentRef} data-read-more={readMoreText}>\n              <div\n                lang={language}\n                dir=\"auto\"\n                class=\"inner-content\"\n                onClick={handleContentLinks({\n                  mentions,\n                  instance,\n                  previewMode,\n                  statusURL: url,\n                })}\n                dangerouslySetInnerHTML={{\n                  __html: enhanceContent(content, {\n                    emojis,\n                    postEnhanceDOM: (dom) => {\n                      // Remove target=\"_blank\" from links\n                      dom\n                        .querySelectorAll('a.u-url[target=\"_blank\"]')\n                        .forEach((a) => {\n                          if (!/http/i.test(a.innerText.trim())) {\n                            a.removeAttribute('target');\n                          }\n                        });\n                      // if (previewMode) return;\n                      // Unfurl Mastodon links\n                      // Array.from(\n                      //   dom.querySelectorAll(\n                      //     'a[href]:not(.u-url):not(.mention):not(.hashtag)',\n                      //   ),\n                      // )\n                      //   .filter((a) => {\n                      //     const url = a.href;\n                      //     const isPostItself =\n                      //       url === status.url || url === status.uri;\n                      //     return !isPostItself && isMastodonLinkMaybe(url);\n                      //   })\n                      //   .forEach((a, i) => {\n                      //     unfurlMastodonLink(currentInstance, a.href).then(\n                      //       (result) => {\n                      //         if (!result) return;\n                      //         a.removeAttribute('target');\n                      //         if (!sKey) return;\n                      //         if (!Array.isArray(states.statusQuotes[sKey])) {\n                      //           states.statusQuotes[sKey] = [];\n                      //         }\n                      //         if (!states.statusQuotes[sKey][i]) {\n                      //           states.statusQuotes[sKey].splice(i, 0, result);\n                      //         }\n                      //       },\n                      //     );\n                      //   });\n                    },\n                  }),\n                }}\n              />\n              <QuoteStatuses id={id} instance={instance} level={quoted} />\n            </div>\n          )}\n          {!!poll && (\n            <Poll\n              lang={language}\n              poll={poll}\n              readOnly={readOnly || !sameInstance || !authenticated}\n              onUpdate={(newPoll) => {\n                states.statuses[sKey].poll = newPoll;\n              }}\n              refresh={() => {\n                return masto.v1.polls\n                  .$select(poll.id)\n                  .fetch()\n                  .then((pollResponse) => {\n                    states.statuses[sKey].poll = pollResponse;\n                  })\n                  .catch((e) => {}); // Silently fail\n              }}\n              votePoll={(choices) => {\n                return masto.v1.polls\n                  .$select(poll.id)\n                  .votes.create({\n                    choices,\n                  })\n                  .then((pollResponse) => {\n                    states.statuses[sKey].poll = pollResponse;\n                  })\n                  .catch((e) => {}); // Silently fail\n              }}\n            />\n          )}\n          {(((enableTranslate || inlineTranslate) &&\n            !!content.trim() &&\n            !!getHTMLText(emojifyText(content, emojis)) &&\n            differentLanguage) ||\n            forceTranslate) && (\n            <TranslationBlock\n              forceTranslate={forceTranslate || inlineTranslate}\n              mini={!isSizeLarge && !withinContext}\n              sourceLanguage={language}\n              text={getPostText(status)}\n            />\n          )}\n          {!previewMode &&\n            sensitive &&\n            !!mediaAttachments.length &&\n            readingExpandMedia !== 'show_all' && (\n              <button\n                class={`plain spoiler-media-button ${\n                  showSpoilerMedia ? 'spoiling' : ''\n                }`}\n                type=\"button\"\n                hidden={!readingExpandSpoilers && !!spoilerText}\n                onClick={(e) => {\n                  e.preventDefault();\n                  e.stopPropagation();\n                  if (showSpoilerMedia) {\n                    delete states.spoilersMedia[id];\n                  } else {\n                    states.spoilersMedia[id] = true;\n                  }\n                }}\n              >\n                <Icon icon={showSpoilerMedia ? 'eye-open' : 'eye-close'} />{' '}\n                {showSpoilerMedia ? 'Show less' : 'Show media'}\n              </button>\n            )}\n          {!!mediaAttachments.length && (\n            <MultipleMediaFigure\n              lang={language}\n              enabled={showMultipleMediaCaptions}\n              captionChildren={captionChildren}\n            >\n              <div\n                ref={mediaContainerRef}\n                class={`media-container media-eq${mediaAttachments.length} ${\n                  mediaAttachments.length > 2 ? 'media-gt2' : ''\n                } ${mediaAttachments.length > 4 ? 'media-gt4' : ''}`}\n              >\n                {displayedMediaAttachments.map((media, i) => (\n                  <Media\n                    key={media.id}\n                    media={media}\n                    autoAnimate={isSizeLarge}\n                    showCaption={mediaAttachments.length === 1}\n                    lang={language}\n                    altIndex={\n                      showMultipleMediaCaptions && !!media.description && i + 1\n                    }\n                    to={`/${instance}/s/${id}?${\n                      withinContext ? 'media' : 'media-only'\n                    }=${i + 1}`}\n                    onClick={\n                      onMediaClick\n                        ? (e) => {\n                            onMediaClick(e, i, media, status);\n                          }\n                        : undefined\n                    }\n                  />\n                ))}\n              </div>\n            </MultipleMediaFigure>\n          )}\n          {!!card &&\n            /^https/i.test(card?.url) &&\n            !sensitive &&\n            !spoilerText &&\n            !poll &&\n            !mediaAttachments.length &&\n            !snapStates.statusQuotes[sKey] && (\n              <Card\n                card={card}\n                selfReferential={\n                  card?.url === status.url || card?.url === status.uri\n                }\n                instance={currentInstance}\n              />\n            )}\n        </div>\n        {!isSizeLarge && showCommentCount && (\n          <div class=\"content-comment-hint insignificant\">\n            <Icon icon=\"comment2\" alt=\"Replies\" /> {repliesCount}\n          </div>\n        )}\n        {isSizeLarge && (\n          <>\n            <div class=\"extra-meta\">\n              {_deleted ? (\n                <span class=\"status-deleted-tag\">Deleted</span>\n              ) : (\n                <>\n                  <Icon\n                    icon={visibilityIconsMap[visibility]}\n                    alt={visibilityText[visibility]}\n                  />{' '}\n                  <a href={url} target=\"_blank\" rel=\"noopener noreferrer\">\n                    <time\n                      class=\"created\"\n                      datetime={createdAtDate.toISOString()}\n                      title={createdAtDate.toLocaleString()}\n                    >\n                      {createdDateText}\n                    </time>\n                  </a>\n                  {editedAt && (\n                    <>\n                      {' '}\n                      &bull; <Icon icon=\"pencil\" alt=\"Edited\" />{' '}\n                      <time\n                        tabIndex=\"0\"\n                        class=\"edited\"\n                        datetime={editedAtDate.toISOString()}\n                        onClick={() => {\n                          setShowEdited(id);\n                        }}\n                      >\n                        {editedDateText}\n                      </time>\n                    </>\n                  )}\n                </>\n              )}\n            </div>\n            <div class={`actions ${_deleted ? 'disabled' : ''}`}>\n              <div class=\"action has-count\">\n                <StatusButton\n                  title=\"Reply\"\n                  alt=\"Comments\"\n                  class=\"reply-button\"\n                  icon=\"comment\"\n                  count={repliesCount}\n                  onClick={replyStatus}\n                />\n              </div>\n              {/* <div class=\"action has-count\">\n                <StatusButton\n                  checked={reblogged}\n                  title={['Boost', 'Unboost']}\n                  alt={['Boost', 'Boosted']}\n                  class=\"reblog-button\"\n                  icon=\"rocket\"\n                  count={reblogsCount}\n                  onClick={boostStatus}\n                  disabled={!canBoost}\n                />\n              </div> */}\n              <MenuConfirm\n                disabled={!canBoost}\n                onClick={confirmBoostStatus}\n                confirmLabel={\n                  <>\n                    <Icon icon=\"rocket\" />\n                    <span>{reblogged ? 'Unboost?' : 'Boost to everyone?'}</span>\n                  </>\n                }\n                menuFooter={\n                  mediaNoDesc &&\n                  !reblogged && (\n                    <div class=\"footer\">\n                      <Icon icon=\"alert\" />\n                      Some media have no descriptions.\n                    </div>\n                  )\n                }\n              >\n                <div class=\"action has-count\">\n                  <StatusButton\n                    checked={reblogged}\n                    title={['Boost', 'Unboost']}\n                    alt={['Boost', 'Boosted']}\n                    class=\"reblog-button\"\n                    icon=\"rocket\"\n                    count={reblogsCount}\n                    // onClick={boostStatus}\n                    disabled={!canBoost}\n                  />\n                </div>\n              </MenuConfirm>\n              <div class=\"action has-count\">\n                <StatusButton\n                  checked={favourited}\n                  title={['Like', 'Unlike']}\n                  alt={['Like', 'Liked']}\n                  class=\"favourite-button\"\n                  icon=\"heart\"\n                  count={favouritesCount}\n                  onClick={favouriteStatus}\n                />\n              </div>\n              <div class=\"action\">\n                <StatusButton\n                  checked={bookmarked}\n                  title={['Bookmark', 'Unbookmark']}\n                  alt={['Bookmark', 'Bookmarked']}\n                  class=\"bookmark-button\"\n                  icon=\"bookmark\"\n                  onClick={bookmarkStatus}\n                />\n              </div>\n              <Menu2\n                portal={{\n                  target:\n                    document.querySelector('.status-deck') || document.body,\n                }}\n                align=\"end\"\n                gap={4}\n                overflow=\"auto\"\n                viewScroll=\"close\"\n                menuButton={\n                  <div class=\"action\">\n                    <button\n                      type=\"button\"\n                      title=\"More\"\n                      class=\"plain more-button\"\n                    >\n                      <Icon icon=\"more\" size=\"l\" alt=\"More\" />\n                    </button>\n                  </div>\n                }\n              >\n                {StatusMenuItems}\n              </Menu2>\n            </div>\n          </>\n        )}\n      </div>\n      {!!showEdited && (\n        <Modal\n          class=\"light\"\n          onClick={(e) => {\n            if (e.target === e.currentTarget) {\n              setShowEdited(false);\n              // statusRef.current?.focus();\n            }\n          }}\n        >\n          <EditedAtModal\n            statusID={showEdited}\n            instance={instance}\n            fetchStatusHistory={() => {\n              return masto.v1.statuses.$select(showEdited).history.list();\n            }}\n            onClose={() => {\n              setShowEdited(false);\n              statusRef.current?.focus();\n            }}\n          />\n        </Modal>\n      )}\n    </article>\n  );\n}\n\nfunction MultipleMediaFigure(props) {\n  const { enabled, children, lang, captionChildren } = props;\n  if (!enabled || !captionChildren) return children;\n  return (\n    <figure class=\"media-figure-multiple\">\n      {children}\n      <figcaption lang={lang} dir=\"auto\">\n        {captionChildren}\n      </figcaption>\n    </figure>\n  );\n}\n\nfunction Card({ card, selfReferential, instance }) {\n  const snapStates = useSnapshot(states);\n  const {\n    blurhash,\n    title,\n    description,\n    html,\n    providerName,\n    providerUrl,\n    authorName,\n    authorUrl,\n    width,\n    height,\n    image,\n    imageDescription,\n    url,\n    type,\n    embedUrl,\n    language,\n    publishedAt,\n  } = card;\n\n  /* type\n  link = Link OEmbed\n  photo = Photo OEmbed\n  video = Video OEmbed\n  rich = iframe OEmbed. Not currently accepted, so won’t show up in practice.\n  */\n\n  const hasText = title || providerName || authorName;\n  const isLandscape = width / height >= 1.2;\n  const size = isLandscape ? 'large' : '';\n\n  const [cardStatusURL, setCardStatusURL] = useState(null);\n  // const [cardStatusID, setCardStatusID] = useState(null);\n  useEffect(() => {\n    if (hasText && image && !selfReferential && isMastodonLinkMaybe(url)) {\n      unfurlMastodonLink(instance, url).then((result) => {\n        if (!result) return;\n        const { id, url } = result;\n        setCardStatusURL('#' + url);\n\n        // NOTE: This is for quote post\n        // (async () => {\n        //   const { masto } = api({ instance });\n        //   const status = await masto.v1.statuses.$select(id).fetch();\n        //   saveStatus(status, instance);\n        //   setCardStatusID(id);\n        // })();\n      });\n    }\n  }, [hasText, image, selfReferential]);\n\n  // if (cardStatusID) {\n  //   return (\n  //     <Status statusID={cardStatusID} instance={instance} size=\"s\" readOnly />\n  //   );\n  // }\n\n  if (snapStates.unfurledLinks[url]) return null;\n\n  if (hasText && (image || (type === 'photo' && blurhash))) {\n    const domain = new URL(url).hostname\n      .replace(/^www\\./, '')\n      .replace(/\\/$/, '');\n    let blurhashImage;\n    const rgbAverageColor =\n      image && blurhash ? getBlurHashAverageColor(blurhash) : null;\n    if (!image) {\n      const w = 44;\n      const h = 44;\n      const blurhashPixels = decodeBlurHash(blurhash, w, h);\n      const canvas = document.createElement('canvas');\n      canvas.width = w;\n      canvas.height = h;\n      const ctx = canvas.getContext('2d');\n      const imageData = ctx.createImageData(w, h);\n      imageData.data.set(blurhashPixels);\n      ctx.putImageData(imageData, 0, 0);\n      blurhashImage = canvas.toDataURL();\n    }\n    return (\n      <a\n        href={cardStatusURL || url}\n        target={cardStatusURL ? null : '_blank'}\n        rel=\"nofollow noopener noreferrer\"\n        class={`card link ${blurhashImage ? '' : size}`}\n        lang={language}\n        dir=\"auto\"\n        style={{\n          '--average-color':\n            rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,\n        }}\n      >\n        <div class=\"card-image\">\n          <img\n            src={image || blurhashImage}\n            width={width}\n            height={height}\n            loading=\"lazy\"\n            alt={imageDescription || ''}\n            onError={(e) => {\n              try {\n                e.target.style.display = 'none';\n              } catch (e) {}\n            }}\n          />\n        </div>\n        <div class=\"meta-container\">\n          <p class=\"meta domain\" dir=\"auto\">\n            {domain}\n          </p>\n          <p class=\"title\" dir=\"auto\">\n            {title}\n          </p>\n          <p class=\"meta\" dir=\"auto\">\n            {description ||\n              (!!publishedAt && (\n                <RelativeTime datetime={publishedAt} format=\"micro\" />\n              ))}\n          </p>\n        </div>\n      </a>\n    );\n  } else if (type === 'photo') {\n    return (\n      <a\n        href={url}\n        target=\"_blank\"\n        rel=\"nofollow noopener noreferrer\"\n        class=\"card photo\"\n      >\n        <img\n          src={embedUrl}\n          width={width}\n          height={height}\n          alt={title || description}\n          loading=\"lazy\"\n          style={{\n            height: 'auto',\n            aspectRatio: `${width}/${height}`,\n          }}\n        />\n      </a>\n    );\n  } else if (type === 'video') {\n    if (/youtube/i.test(providerName)) {\n      // Get ID from e.g. https://www.youtube.com/watch?v=[VIDEO_ID]\n      const videoID = url.match(/watch\\?v=([^&]+)/)?.[1];\n      if (videoID) {\n        return <lite-youtube videoid={videoID} nocookie></lite-youtube>;\n      }\n    }\n    return (\n      <div\n        class=\"card video\"\n        style={{\n          aspectRatio: `${width}/${height}`,\n        }}\n        dangerouslySetInnerHTML={{ __html: html }}\n      />\n    );\n  } else if (hasText && !image) {\n    const domain = new URL(url).hostname.replace(/^www\\./, '');\n    return (\n      <a\n        href={cardStatusURL || url}\n        target={cardStatusURL ? null : '_blank'}\n        rel=\"nofollow noopener noreferrer\"\n        class={`card link no-image`}\n        lang={language}\n      >\n        <div class=\"meta-container\">\n          <p class=\"meta domain\">\n            <Icon icon=\"link\" size=\"s\" /> <span>{domain}</span>\n          </p>\n          <p class=\"title\">{title}</p>\n          <p class=\"meta\">{description || providerName || authorName}</p>\n        </div>\n      </a>\n    );\n  }\n}\n\nfunction EditedAtModal({\n  statusID,\n  instance,\n  fetchStatusHistory = () => {},\n  onClose,\n}) {\n  const [uiState, setUIState] = useState('default');\n  const [editHistory, setEditHistory] = useState([]);\n\n  useEffect(() => {\n    setUIState('loading');\n    (async () => {\n      try {\n        const editHistory = await fetchStatusHistory();\n        console.log(editHistory);\n        setEditHistory(editHistory);\n        setUIState('default');\n      } catch (e) {\n        console.error(e);\n        setUIState('error');\n      }\n    })();\n  }, []);\n\n  return (\n    <div id=\"edit-history\" class=\"sheet\">\n      {!!onClose && (\n        <button type=\"button\" class=\"sheet-close\" onClick={onClose}>\n          <Icon icon=\"x\" />\n        </button>\n      )}\n      <header>\n        <h2>Edit History</h2>\n        {uiState === 'error' && <p>Failed to load history</p>}\n        {uiState === 'loading' && (\n          <p>\n            <Loader abrupt /> Loading&hellip;\n          </p>\n        )}\n      </header>\n      <main tabIndex=\"-1\">\n        {editHistory.length > 0 && (\n          <ol>\n            {editHistory.map((status) => {\n              const { createdAt } = status;\n              const createdAtDate = new Date(createdAt);\n              return (\n                <li key={createdAt} class=\"history-item\">\n                  <h3>\n                    <time>\n                      {niceDateTime(createdAtDate, {\n                        formatOpts: {\n                          weekday: 'short',\n                          second: 'numeric',\n                        },\n                      })}\n                    </time>\n                  </h3>\n                  <Status\n                    status={status}\n                    instance={instance}\n                    size=\"s\"\n                    withinContext\n                    readOnly\n                    previewMode\n                  />\n                </li>\n              );\n            })}\n          </ol>\n        )}\n      </main>\n    </div>\n  );\n}\n\nfunction StatusButton({\n  checked,\n  count,\n  class: className,\n  title,\n  alt,\n  icon,\n  onClick,\n  ...props\n}) {\n  if (typeof title === 'string') {\n    title = [title, title];\n  }\n  if (typeof alt === 'string') {\n    alt = [alt, alt];\n  }\n\n  const [buttonTitle, setButtonTitle] = useState(title[0] || '');\n  const [iconAlt, setIconAlt] = useState(alt[0] || '');\n\n  useEffect(() => {\n    if (checked) {\n      setButtonTitle(title[1] || '');\n      setIconAlt(alt[1] || '');\n    } else {\n      setButtonTitle(title[0] || '');\n      setIconAlt(alt[0] || '');\n    }\n  }, [checked, title, alt]);\n\n  return (\n    <button\n      type=\"button\"\n      title={buttonTitle}\n      class={`plain ${className} ${checked ? 'checked' : ''}`}\n      onClick={(e) => {\n        if (!onClick) return;\n        e.preventDefault();\n        e.stopPropagation();\n        onClick(e);\n      }}\n      {...props}\n    >\n      <Icon icon={icon} size=\"l\" alt={iconAlt} />\n      {!!count && (\n        <>\n          {' '}\n          <small title={count}>{shortenNumber(count)}</small>\n        </>\n      )}\n    </button>\n  );\n}\n\nexport function formatDuration(time) {\n  if (!time) return;\n  let hours = Math.floor(time / 3600);\n  let minutes = Math.floor((time % 3600) / 60);\n  let seconds = Math.round(time % 60);\n\n  if (hours === 0) {\n    return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n  } else {\n    return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds\n      .toString()\n      .padStart(2, '0')}`;\n  }\n}\n\nfunction nicePostURL(url) {\n  if (!url) return;\n  const urlObj = new URL(url);\n  const { host, pathname } = urlObj;\n  const path = pathname.replace(/\\/$/, '');\n  // split only first slash\n  const [_, username, restPath] = path.match(/\\/(@[^\\/]+)\\/(.*)/) || [];\n  return (\n    <>\n      {host}\n      {username ? (\n        <>\n          /{username}\n          <wbr />\n          <span class=\"more-insignificant\">/{restPath}</span>\n        </>\n      ) : (\n        <span class=\"more-insignificant\">{path}</span>\n      )}\n    </>\n  );\n}\n\nfunction FilteredStatus({\n  status,\n  filterInfo,\n  instance,\n  containerProps = {},\n  showFollowedTags,\n}) {\n  const snapStates = useSnapshot(states);\n  const {\n    id: statusID,\n    account: { avatar, avatarStatic, bot, group },\n    createdAt,\n    visibility,\n    reblog,\n  } = status;\n  const isReblog = !!reblog;\n  const filterTitleStr = filterInfo?.titlesStr || '';\n  const createdAtDate = new Date(createdAt);\n  const statusPeekText = statusPeek(status.reblog || status);\n\n  const [showPeek, setShowPeek] = useState(false);\n  const bindLongPressPeek = useLongPress(\n    () => {\n      setShowPeek(true);\n    },\n    {\n      threshold: 600,\n      captureEvent: true,\n      detect: 'touch',\n      cancelOnMovement: 2, // true allows movement of up to 25 pixels\n    },\n  );\n\n  const statusPeekRef = useTruncated();\n  const sKey = statusKey(status.id, instance);\n  const ssKey =\n    statusKey(status.id, instance) +\n    ' ' +\n    (statusKey(reblog?.id, instance) || '');\n\n  const actualStatusID = reblog?.id || statusID;\n  const url = instance\n    ? `/${instance}/s/${actualStatusID}`\n    : `/s/${actualStatusID}`;\n  const isFollowedTags =\n    showFollowedTags && !!snapStates.statusFollowedTags[sKey]?.length;\n\n  return (\n    <div\n      class={\n        isReblog\n          ? group\n            ? 'status-group'\n            : 'status-reblog'\n          : isFollowedTags\n          ? 'status-followed-tags'\n          : ''\n      }\n      {...containerProps}\n      title={statusPeekText}\n      onContextMenu={(e) => {\n        e.preventDefault();\n        setShowPeek(true);\n      }}\n      {...bindLongPressPeek()}\n    >\n      <article data-state-post-id={ssKey} class=\"status filtered\" tabindex=\"-1\">\n        <b\n          class=\"status-filtered-badge clickable badge-meta\"\n          title={filterTitleStr}\n          onClick={(e) => {\n            e.preventDefault();\n            setShowPeek(true);\n          }}\n        >\n          <span>Filtered</span>\n          <span>{filterTitleStr}</span>\n        </b>{' '}\n        <Avatar url={avatarStatic || avatar} squircle={bot} />\n        <span class=\"status-filtered-info\">\n          <span class=\"status-filtered-info-1\">\n            <NameText account={status.account} instance={instance} />{' '}\n            <Icon\n              icon={visibilityIconsMap[visibility]}\n              alt={visibilityText[visibility]}\n              size=\"s\"\n            />{' '}\n            {isReblog ? (\n              'boosted'\n            ) : isFollowedTags ? (\n              <span>\n                {snapStates.statusFollowedTags[sKey].slice(0, 3).map((tag) => (\n                  <span key={tag} class=\"status-followed-tag-item\">\n                    #{tag}\n                  </span>\n                ))}\n              </span>\n            ) : (\n              <RelativeTime datetime={createdAtDate} format=\"micro\" />\n            )}\n          </span>\n          <span class=\"status-filtered-info-2\">\n            {isReblog && (\n              <>\n                <Avatar\n                  url={reblog.account.avatarStatic || reblog.account.avatar}\n                  squircle={bot}\n                />{' '}\n              </>\n            )}\n            {statusPeekText}\n          </span>\n        </span>\n      </article>\n      {!!showPeek && (\n        <Modal\n          class=\"light\"\n          onClick={(e) => {\n            if (e.target === e.currentTarget) {\n              setShowPeek(false);\n            }\n          }}\n        >\n          <div id=\"filtered-status-peek\" class=\"sheet\">\n            <button\n              type=\"button\"\n              class=\"sheet-close\"\n              onClick={() => setShowPeek(false)}\n            >\n              <Icon icon=\"x\" />\n            </button>\n            <header>\n              <b class=\"status-filtered-badge\">Filtered</b> {filterTitleStr}\n            </header>\n            <main tabIndex=\"-1\">\n              <Link\n                ref={statusPeekRef}\n                class=\"status-link\"\n                to={url}\n                onClick={() => {\n                  setShowPeek(false);\n                }}\n                data-read-more=\"Read more →\"\n              >\n                <Status status={status} instance={instance} size=\"s\" readOnly />\n              </Link>\n            </main>\n          </div>\n        </Modal>\n      )}\n    </div>\n  );\n}\n\nconst QuoteStatuses = memo(({ id, instance, level = 0 }) => {\n  if (!id || !instance) return;\n  const snapStates = useSnapshot(states);\n  const sKey = statusKey(id, instance);\n  const quotes = snapStates.statusQuotes[sKey];\n  const uniqueQuotes = quotes?.filter(\n    (q, i, arr) => arr.findIndex((q2) => q2.url === q.url) === i,\n  );\n\n  if (!uniqueQuotes?.length) return;\n  if (level > 2) return;\n\n  return uniqueQuotes.map((q) => {\n    return (\n      <Link\n        key={q.instance + q.id}\n        to={`${q.instance ? `/${q.instance}` : ''}/s/${q.id}`}\n        class=\"status-card-link\"\n        data-read-more=\"Read more →\"\n      >\n        <Status\n          statusID={q.id}\n          instance={q.instance}\n          size=\"s\"\n          quoted={level + 1}\n          enableCommentHint\n        />\n      </Link>\n    );\n  });\n});\n\nexport default memo(Status);\n","import './compose.css';\n\nimport '@github/text-expander-element';\nimport { MenuItem } from '@szhsin/react-menu';\nimport equal from 'fast-deep-equal';\nimport { forwardRef } from 'preact/compat';\nimport { useEffect, useMemo, useRef, useState } from 'preact/hooks';\nimport { useHotkeys } from 'react-hotkeys-hook';\nimport { substring } from 'runes2';\nimport stringLength from 'string-length';\nimport { uid } from 'uid/single';\nimport { useDebouncedCallback, useThrottledCallback } from 'use-debounce';\nimport { useSnapshot } from 'valtio';\n\nimport Menu2 from '../components/menu2';\nimport supportedLanguages from '../data/status-supported-languages';\nimport urlRegex from '../data/url-regex';\nimport { api } from '../utils/api';\nimport db from '../utils/db';\nimport emojifyText from '../utils/emojify-text';\nimport localeMatch from '../utils/locale-match';\nimport openCompose from '../utils/open-compose';\nimport shortenNumber from '../utils/shorten-number';\nimport showToast from '../utils/show-toast';\nimport states, { saveStatus } from '../utils/states';\nimport store from '../utils/store';\nimport {\n  getCurrentAccount,\n  getCurrentAccountNS,\n  getCurrentInstance,\n  getCurrentInstanceConfiguration,\n} from '../utils/store-utils';\nimport supports from '../utils/supports';\nimport useCloseWatcher from '../utils/useCloseWatcher';\nimport useInterval from '../utils/useInterval';\nimport visibilityIconsMap from '../utils/visibility-icons-map';\n\nimport AccountBlock from './account-block';\n// import Avatar from './avatar';\nimport Icon from './icon';\nimport Loader from './loader';\nimport Modal from './modal';\nimport Status from './status';\n\nconst { PHANPY_IMG_ALT_API_URL: IMG_ALT_API_URL } = import.meta.env;\n\nconst supportedLanguagesMap = supportedLanguages.reduce((acc, l) => {\n  const [code, common, native] = l;\n  acc[code] = {\n    common,\n    native,\n  };\n  return acc;\n}, {});\n\n/* NOTES:\n  - Max character limit includes BOTH status text and Content Warning text\n*/\n\nconst expiryOptions = {\n  '5 minutes': 5 * 60,\n  '30 minutes': 30 * 60,\n  '1 hour': 60 * 60,\n  '6 hours': 6 * 60 * 60,\n  '12 hours': 12 * 60 * 60,\n  '1 day': 24 * 60 * 60,\n  '3 days': 3 * 24 * 60 * 60,\n  '7 days': 7 * 24 * 60 * 60,\n};\nconst expirySeconds = Object.values(expiryOptions);\nconst oneDay = 24 * 60 * 60;\n\nconst expiresInFromExpiresAt = (expiresAt) => {\n  if (!expiresAt) return oneDay;\n  const delta = (new Date(expiresAt).getTime() - Date.now()) / 1000;\n  return expirySeconds.find((s) => s >= delta) || oneDay;\n};\n\nconst menu = document.createElement('ul');\nmenu.role = 'listbox';\nmenu.className = 'text-expander-menu';\n\n// Set IntersectionObserver on menu, reposition it because text-expander doesn't handle it\nconst windowMargin = 16;\nconst observer = new IntersectionObserver((entries) => {\n  entries.forEach((entry) => {\n    if (entry.isIntersecting) {\n      const { left, width } = entry.boundingClientRect;\n      const { innerWidth } = window;\n      if (left + width > innerWidth) {\n        menu.style.left = innerWidth - width - windowMargin + 'px';\n      }\n    }\n  });\n});\nobserver.observe(menu);\n\nconst DEFAULT_LANG = localeMatch(\n  [new Intl.DateTimeFormat().resolvedOptions().locale, ...navigator.languages],\n  supportedLanguages.map((l) => l[0]),\n  'en',\n);\n\n// https://github.com/mastodon/mastodon/blob/c4a429ed47e85a6bbf0d470a41cc2f64cf120c19/app/javascript/mastodon/features/compose/util/counter.js\nconst urlRegexObj = new RegExp(urlRegex.source, urlRegex.flags);\nconst usernameRegex = /(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)/gi;\nconst urlPlaceholder = '$2xxxxxxxxxxxxxxxxxxxxxxx';\nfunction countableText(inputText) {\n  return inputText\n    .replace(urlRegexObj, urlPlaceholder)\n    .replace(usernameRegex, '$1@$3');\n}\n\n// https://github.com/mastodon/mastodon/blob/c03bd2a238741a012aa4b98dc4902d6cf948ab63/app/models/account.rb#L69\nconst USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i;\nconst MENTION_RE = new RegExp(\n  `(^|[^=\\\\/\\\\w])(@${USERNAME_RE.source}(?:@[\\\\p{L}\\\\w.-]+[\\\\w]+)?)`,\n  'uig',\n);\n\n// AI-generated, all other regexes are too complicated\nconst HASHTAG_RE = new RegExp(\n  `(^|[^=\\\\/\\\\w])(#[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?)(?![\\\\/\\\\w])`,\n  'ig',\n);\n\n// https://github.com/mastodon/mastodon/blob/23e32a4b3031d1da8b911e0145d61b4dd47c4f96/app/models/custom_emoji.rb#L31\nconst SHORTCODE_RE_FRAGMENT = '[a-zA-Z0-9_]{2,}';\nconst SCAN_RE = new RegExp(\n  `([^A-Za-z0-9_:\\\\n]|^)(:${SHORTCODE_RE_FRAGMENT}:)(?=[^A-Za-z0-9_:]|$)`,\n  'g',\n);\n\nfunction highlightText(text, { maxCharacters = Infinity }) {\n  // Accept text string, return formatted HTML string\n  let html = text;\n  // Exceeded characters limit\n  const { composerCharacterCount } = states;\n  let leftoverHTML = '';\n  if (composerCharacterCount > maxCharacters) {\n    // NOTE: runes2 substring considers surrogate pairs\n    // const leftoverCount = composerCharacterCount - maxCharacters;\n    // Highlight exceeded characters\n    leftoverHTML =\n      '<mark class=\"compose-highlight-exceeded\">' +\n      // html.slice(-leftoverCount) +\n      substring(html, maxCharacters) +\n      '</mark>';\n    // html = html.slice(0, -leftoverCount);\n    html = substring(html, 0, maxCharacters);\n    return html + leftoverHTML;\n  }\n\n  return html\n    .replace(urlRegexObj, '$2<mark class=\"compose-highlight-url\">$3</mark>') // URLs\n    .replace(MENTION_RE, '$1<mark class=\"compose-highlight-mention\">$2</mark>') // Mentions\n    .replace(HASHTAG_RE, '$1<mark class=\"compose-highlight-hashtag\">$2</mark>') // Hashtags\n    .replace(\n      SCAN_RE,\n      '$1<mark class=\"compose-highlight-emoji-shortcode\">$2</mark>',\n    ); // Emoji shortcodes\n}\n\nfunction Compose({\n  onClose,\n  replyToStatus,\n  editStatus,\n  draftStatus,\n  standalone,\n  hasOpener,\n}) {\n  console.warn('RENDER COMPOSER');\n  const { masto, instance } = api();\n  const [uiState, setUIState] = useState('default');\n  const UID = useRef(draftStatus?.uid || uid());\n  console.log('Compose UID', UID.current);\n\n  const currentAccount = getCurrentAccount();\n  const currentAccountInfo = currentAccount.info;\n\n  const configuration = getCurrentInstanceConfiguration();\n  console.log('⚙️ Configuration', configuration);\n\n  const {\n    statuses: {\n      maxCharacters,\n      maxMediaAttachments,\n      charactersReservedPerUrl,\n    } = {},\n    mediaAttachments: {\n      supportedMimeTypes = [],\n      imageSizeLimit,\n      imageMatrixLimit,\n      videoSizeLimit,\n      videoMatrixLimit,\n      videoFrameRateLimit,\n    } = {},\n    polls: {\n      maxOptions,\n      maxCharactersPerOption,\n      maxExpiration,\n      minExpiration,\n    } = {},\n  } = configuration || {};\n\n  const textareaRef = useRef();\n  const spoilerTextRef = useRef();\n  const [visibility, setVisibility] = useState('public');\n  const [sensitive, setSensitive] = useState(false);\n  const [language, setLanguage] = useState(\n    store.session.get('currentLanguage') || DEFAULT_LANG,\n  );\n  const prevLanguage = useRef(language);\n  const [mediaAttachments, setMediaAttachments] = useState([]);\n  const [poll, setPoll] = useState(null);\n\n  const prefs = store.account.get('preferences') || {};\n\n  const oninputTextarea = () => {\n    if (!textareaRef.current) return;\n    textareaRef.current.dispatchEvent(new Event('input'));\n  };\n  const focusTextarea = () => {\n    setTimeout(() => {\n      console.debug('FOCUS textarea');\n      textareaRef.current?.focus();\n    }, 300);\n  };\n\n  useEffect(() => {\n    if (replyToStatus) {\n      const { spoilerText, visibility, language, sensitive } = replyToStatus;\n      if (spoilerText && spoilerTextRef.current) {\n        spoilerTextRef.current.value = spoilerText;\n      }\n      const mentions = new Set([\n        replyToStatus.account.acct,\n        ...replyToStatus.mentions.map((m) => m.acct),\n      ]);\n      const allMentions = [...mentions].filter(\n        (m) => m !== currentAccountInfo.acct,\n      );\n      if (allMentions.length > 0) {\n        textareaRef.current.value = `${allMentions\n          .map((m) => `@${m}`)\n          .join(' ')} `;\n        oninputTextarea();\n      }\n      focusTextarea();\n      setVisibility(\n        visibility === 'public' && prefs['posting:default:visibility']\n          ? prefs['posting:default:visibility']\n          : visibility,\n      );\n      setLanguage(language || prefs.postingDefaultLanguage || DEFAULT_LANG);\n      setSensitive(sensitive && !!spoilerText);\n    } else if (editStatus) {\n      const { visibility, language, sensitive, poll, mediaAttachments } =\n        editStatus;\n      const composablePoll = !!poll?.options && {\n        ...poll,\n        options: poll.options.map((o) => o?.title || o),\n        expiresIn: poll?.expiresIn || expiresInFromExpiresAt(poll.expiresAt),\n      };\n      setUIState('loading');\n      (async () => {\n        try {\n          const statusSource = await masto.v1.statuses\n            .$select(editStatus.id)\n            .source.fetch();\n          console.log({ statusSource });\n          const { text, spoilerText } = statusSource;\n          textareaRef.current.value = text;\n          textareaRef.current.dataset.source = text;\n          oninputTextarea();\n          focusTextarea();\n          spoilerTextRef.current.value = spoilerText;\n          setVisibility(visibility);\n          setLanguage(language || presf.postingDefaultLanguage || DEFAULT_LANG);\n          setSensitive(sensitive);\n          setPoll(composablePoll);\n          setMediaAttachments(mediaAttachments);\n          setUIState('default');\n        } catch (e) {\n          console.error(e);\n          alert(e?.reason || e);\n          setUIState('error');\n        }\n      })();\n    } else {\n      focusTextarea();\n      console.log('Apply prefs', prefs);\n      if (prefs['posting:default:visibility']) {\n        setVisibility(prefs['posting:default:visibility']);\n      }\n      if (prefs['posting:default:language']) {\n        setLanguage(prefs['posting:default:language']);\n      }\n      if (prefs['posting:default:sensitive']) {\n        setSensitive(prefs['posting:default:sensitive']);\n      }\n    }\n    if (draftStatus) {\n      const {\n        status,\n        spoilerText,\n        visibility,\n        language,\n        sensitive,\n        poll,\n        mediaAttachments,\n      } = draftStatus;\n      const composablePoll = !!poll?.options && {\n        ...poll,\n        options: poll.options.map((o) => o?.title || o),\n        expiresIn: poll?.expiresIn || expiresInFromExpiresAt(poll.expiresAt),\n      };\n      textareaRef.current.value = status;\n      oninputTextarea();\n      focusTextarea();\n      if (spoilerText) spoilerTextRef.current.value = spoilerText;\n      if (visibility) setVisibility(visibility);\n      setLanguage(language || prefs.postingDefaultLanguage || DEFAULT_LANG);\n      if (sensitive !== null) setSensitive(sensitive);\n      if (composablePoll) setPoll(composablePoll);\n      if (mediaAttachments) setMediaAttachments(mediaAttachments);\n    }\n  }, [draftStatus, editStatus, replyToStatus]);\n\n  const formRef = useRef();\n\n  const beforeUnloadCopy = 'You have unsaved changes. Discard this post?';\n  const canClose = () => {\n    const { value, dataset } = textareaRef.current;\n\n    // check if loading\n    if (uiState === 'loading') {\n      console.log('canClose', { uiState });\n      return false;\n    }\n\n    // check for status and media attachments\n    const hasMediaAttachments = mediaAttachments.length > 0;\n    if (!value && !hasMediaAttachments) {\n      console.log('canClose', { value, mediaAttachments });\n      return true;\n    }\n\n    // check if all media attachments have IDs\n    const hasIDMediaAttachments =\n      mediaAttachments.length > 0 &&\n      mediaAttachments.every((media) => media.id);\n    if (hasIDMediaAttachments) {\n      console.log('canClose', { hasIDMediaAttachments });\n      return true;\n    }\n\n    // check if status contains only \"@acct\", if replying\n    const isSelf = replyToStatus?.account.id === currentAccountInfo.id;\n    const hasOnlyAcct =\n      replyToStatus && value.trim() === `@${replyToStatus.account.acct}`;\n    // TODO: check for mentions, or maybe just generic \"@username<space>\", including multiple mentions like \"@username1<space>@username2<space>\"\n    if (!isSelf && hasOnlyAcct) {\n      console.log('canClose', { isSelf, hasOnlyAcct });\n      return true;\n    }\n\n    // check if status is same with source\n    const sameWithSource = value === dataset?.source;\n    if (sameWithSource) {\n      console.log('canClose', { sameWithSource });\n      return true;\n    }\n\n    console.log('canClose', {\n      value,\n      hasMediaAttachments,\n      hasIDMediaAttachments,\n      poll,\n      isSelf,\n      hasOnlyAcct,\n      sameWithSource,\n      uiState,\n    });\n\n    return false;\n  };\n\n  const confirmClose = () => {\n    if (!canClose()) {\n      const yes = confirm(beforeUnloadCopy);\n      return yes;\n    }\n    return true;\n  };\n\n  useEffect(() => {\n    // Show warning if user tries to close window with unsaved changes\n    const handleBeforeUnload = (e) => {\n      if (!canClose()) {\n        e.preventDefault();\n        e.returnValue = beforeUnloadCopy;\n      }\n    };\n    window.addEventListener('beforeunload', handleBeforeUnload, {\n      capture: true,\n    });\n    return () =>\n      window.removeEventListener('beforeunload', handleBeforeUnload, {\n        capture: true,\n      });\n  }, []);\n\n  const getCharCount = () => {\n    const { value } = textareaRef.current;\n    const { value: spoilerText } = spoilerTextRef.current;\n    return stringLength(countableText(value)) + stringLength(spoilerText);\n  };\n  const updateCharCount = () => {\n    const count = getCharCount();\n    states.composerCharacterCount = count;\n  };\n  useEffect(updateCharCount, []);\n\n  const supportsCloseWatcher = window.CloseWatcher;\n  const escDownRef = useRef(false);\n  useHotkeys(\n    'esc',\n    () => {\n      escDownRef.current = true;\n      // This won't be true if this event is already handled and not propagated 🤞\n    },\n    {\n      enabled: !supportsCloseWatcher,\n      enableOnFormTags: true,\n    },\n  );\n  useHotkeys(\n    'esc',\n    () => {\n      if (!standalone && escDownRef.current && confirmClose()) {\n        onClose();\n      }\n      escDownRef.current = false;\n    },\n    {\n      enabled: !supportsCloseWatcher,\n      enableOnFormTags: true,\n      // Use keyup because Esc keydown will close the confirm dialog on Safari\n      keyup: true,\n      ignoreEventWhen: (e) => {\n        const modals = document.querySelectorAll('#modal-container > *');\n        const hasModal = !!modals;\n        const hasOnlyComposer =\n          modals.length === 1 && modals[0].querySelector('#compose-container');\n        return hasModal && !hasOnlyComposer;\n      },\n    },\n  );\n  useCloseWatcher(() => {\n    if (!standalone && confirmClose()) {\n      onClose();\n    }\n  }, [standalone, confirmClose, onClose]);\n\n  const prevBackgroundDraft = useRef({});\n  const draftKey = () => {\n    const ns = getCurrentAccountNS();\n    return `${ns}#${UID.current}`;\n  };\n  const saveUnsavedDraft = () => {\n    // Not enabling this for editing status\n    // I don't think this warrant a draft mode for a status that's already posted\n    // Maybe it could be a big edit change but it should be rare\n    if (editStatus) return;\n    const key = draftKey();\n    const backgroundDraft = {\n      key,\n      replyTo: replyToStatus\n        ? {\n            /* Smaller payload of replyToStatus. Reasons:\n              - No point storing whole thing\n              - Could have media attachments\n              - Could be deleted/edited later\n            */\n            id: replyToStatus.id,\n            account: {\n              id: replyToStatus.account.id,\n              username: replyToStatus.account.username,\n              acct: replyToStatus.account.acct,\n            },\n          }\n        : null,\n      draftStatus: {\n        uid: UID.current,\n        status: textareaRef.current.value,\n        spoilerText: spoilerTextRef.current.value,\n        visibility,\n        language,\n        sensitive,\n        poll,\n        mediaAttachments,\n      },\n    };\n    if (!equal(backgroundDraft, prevBackgroundDraft.current) && !canClose()) {\n      console.debug('not equal', backgroundDraft, prevBackgroundDraft.current);\n      db.drafts\n        .set(key, {\n          ...backgroundDraft,\n          state: 'unsaved',\n          updatedAt: Date.now(),\n        })\n        .then(() => {\n          console.debug('DRAFT saved', key, backgroundDraft);\n        })\n        .catch((e) => {\n          console.error('DRAFT failed', key, e);\n        });\n      prevBackgroundDraft.current = structuredClone(backgroundDraft);\n    }\n  };\n  useInterval(saveUnsavedDraft, 5000); // background save every 5s\n  useEffect(() => {\n    saveUnsavedDraft();\n    // If unmounted, means user discarded the draft\n    // Also means pop-out 🙈, but it's okay because the pop-out will persist the ID and re-create the draft\n    return () => {\n      db.drafts.del(draftKey());\n    };\n  }, []);\n\n  useEffect(() => {\n    const handleItems = (e) => {\n      const { items } = e.clipboardData || e.dataTransfer;\n      const files = [];\n      for (let i = 0; i < items.length; i++) {\n        const item = items[i];\n        if (item.kind === 'file') {\n          const file = item.getAsFile();\n          if (file && supportedMimeTypes.includes(file.type)) {\n            files.push(file);\n          }\n        }\n      }\n      if (files.length > 0 && mediaAttachments.length >= maxMediaAttachments) {\n        alert(`You can only attach up to ${maxMediaAttachments} files.`);\n        return;\n      }\n      console.log({ files });\n      if (files.length > 0) {\n        e.preventDefault();\n        e.stopPropagation();\n        // Auto-cut-off files to avoid exceeding maxMediaAttachments\n        const max = maxMediaAttachments - mediaAttachments.length;\n        const allowedFiles = files.slice(0, max);\n        if (allowedFiles.length <= 0) {\n          alert(`You can only attach up to ${maxMediaAttachments} files.`);\n          return;\n        }\n        const mediaFiles = allowedFiles.map((file) => ({\n          file,\n          type: file.type,\n          size: file.size,\n          url: URL.createObjectURL(file),\n          id: null,\n          description: null,\n        }));\n        setMediaAttachments([...mediaAttachments, ...mediaFiles]);\n      }\n    };\n    window.addEventListener('paste', handleItems);\n    const handleDragover = (e) => {\n      // Prevent default if there's files\n      if (e.dataTransfer.items.length > 0) {\n        e.preventDefault();\n        e.stopPropagation();\n      }\n    };\n    window.addEventListener('dragover', handleDragover);\n    window.addEventListener('drop', handleItems);\n    return () => {\n      window.removeEventListener('paste', handleItems);\n      window.removeEventListener('dragover', handleDragover);\n      window.removeEventListener('drop', handleItems);\n    };\n  }, [mediaAttachments]);\n\n  const [showEmoji2Picker, setShowEmoji2Picker] = useState(false);\n\n  const [topSupportedLanguages, restSupportedLanguages] = useMemo(() => {\n    const topLanguages = [];\n    const restLanguages = [];\n    const { contentTranslationHideLanguages = [] } = states.settings;\n    supportedLanguages.forEach((l) => {\n      const [code] = l;\n      if (\n        code === language ||\n        code === prevLanguage.current ||\n        code === DEFAULT_LANG ||\n        contentTranslationHideLanguages.includes(code)\n      ) {\n        topLanguages.push(l);\n      } else {\n        restLanguages.push(l);\n      }\n    });\n    topLanguages.sort(([codeA, commonA], [codeB, commonB]) => {\n      if (codeA === language) return -1;\n      if (codeB === language) return 1;\n      return commonA.localeCompare(commonB);\n    });\n    restLanguages.sort(([codeA, commonA], [codeB, commonB]) =>\n      commonA.localeCompare(commonB),\n    );\n    return [topLanguages, restLanguages];\n  }, [language]);\n\n  return (\n    <div id=\"compose-container-outer\">\n      <div id=\"compose-container\" class={standalone ? 'standalone' : ''}>\n        <div class=\"compose-top\">\n          {currentAccountInfo?.avatarStatic && (\n            // <Avatar\n            //   url={currentAccountInfo.avatarStatic}\n            //   size=\"xl\"\n            //   alt={currentAccountInfo.username}\n            //   squircle={currentAccountInfo?.bot}\n            // />\n            <AccountBlock\n              account={currentAccountInfo}\n              accountInstance={currentAccount.instanceURL}\n              hideDisplayName\n              useAvatarStatic\n            />\n          )}\n          {!standalone ? (\n            <span>\n              <button\n                type=\"button\"\n                class=\"light pop-button\"\n                disabled={uiState === 'loading'}\n                onClick={() => {\n                  // If there are non-ID media attachments (not yet uploaded), show confirmation dialog because they are not going to be passed to the new window\n                  // const containNonIDMediaAttachments =\n                  //   mediaAttachments.length > 0 &&\n                  //   mediaAttachments.some((media) => !media.id);\n                  // if (containNonIDMediaAttachments) {\n                  //   const yes = confirm(\n                  //     'You have media attachments that are not yet uploaded. Opening a new window will discard them and you will need to re-attach them. Are you sure you want to continue?',\n                  //   );\n                  //   if (!yes) {\n                  //     return;\n                  //   }\n                  // }\n\n                  // const mediaAttachmentsWithIDs = mediaAttachments.filter(\n                  //   (media) => media.id,\n                  // );\n\n                  const newWin = openCompose({\n                    editStatus,\n                    replyToStatus,\n                    draftStatus: {\n                      uid: UID.current,\n                      status: textareaRef.current.value,\n                      spoilerText: spoilerTextRef.current.value,\n                      visibility,\n                      language,\n                      sensitive,\n                      poll,\n                      mediaAttachments,\n                    },\n                  });\n\n                  if (!newWin) {\n                    return;\n                  }\n\n                  onClose();\n                }}\n              >\n                <Icon icon=\"popout\" alt=\"Pop out\" />\n              </button>{' '}\n              <button\n                type=\"button\"\n                class=\"light close-button\"\n                disabled={uiState === 'loading'}\n                onClick={() => {\n                  if (confirmClose()) {\n                    onClose();\n                  }\n                }}\n              >\n                <Icon icon=\"x\" />\n              </button>\n            </span>\n          ) : (\n            hasOpener && (\n              <button\n                type=\"button\"\n                class=\"light pop-button\"\n                disabled={uiState === 'loading'}\n                onClick={() => {\n                  // If there are non-ID media attachments (not yet uploaded), show confirmation dialog because they are not going to be passed to the new window\n                  // const containNonIDMediaAttachments =\n                  //   mediaAttachments.length > 0 &&\n                  //   mediaAttachments.some((media) => !media.id);\n                  // if (containNonIDMediaAttachments) {\n                  //   const yes = confirm(\n                  //     'You have media attachments that are not yet uploaded. Opening a new window will discard them and you will need to re-attach them. Are you sure you want to continue?',\n                  //   );\n                  //   if (!yes) {\n                  //     return;\n                  //   }\n                  // }\n\n                  if (!window.opener) {\n                    alert('Looks like you closed the parent window.');\n                    return;\n                  }\n\n                  if (window.opener.__STATES__.showCompose) {\n                    const yes = confirm(\n                      'Looks like you already have a compose field open in the parent window. Popping in this window will discard the changes you made in the parent window. Continue?',\n                    );\n                    if (!yes) return;\n                  }\n\n                  // const mediaAttachmentsWithIDs = mediaAttachments.filter(\n                  //   (media) => media.id,\n                  // );\n\n                  onClose({\n                    fn: () => {\n                      const passData = {\n                        editStatus,\n                        replyToStatus,\n                        draftStatus: {\n                          uid: UID.current,\n                          status: textareaRef.current.value,\n                          spoilerText: spoilerTextRef.current.value,\n                          visibility,\n                          language,\n                          sensitive,\n                          poll,\n                          mediaAttachments,\n                        },\n                      };\n                      window.opener.__COMPOSE__ = passData; // Pass it here instead of `showCompose` due to some weird proxy issue again\n                      window.opener.__STATES__.showCompose = true;\n                    },\n                  });\n                }}\n              >\n                <Icon icon=\"popin\" alt=\"Pop in\" />\n              </button>\n            )\n          )}\n        </div>\n        {!!replyToStatus && (\n          <div class=\"status-preview\">\n            <Status status={replyToStatus} size=\"s\" previewMode />\n            <div class=\"status-preview-legend reply-to\">\n              Replying to @\n              {replyToStatus.account.acct || replyToStatus.account.username}\n              &rsquo;s post\n            </div>\n          </div>\n        )}\n        {!!editStatus && (\n          <div class=\"status-preview\">\n            <Status status={editStatus} size=\"s\" previewMode />\n            <div class=\"status-preview-legend\">Editing source post</div>\n          </div>\n        )}\n        <form\n          ref={formRef}\n          class={`form-visibility-${visibility}`}\n          style={{\n            pointerEvents: uiState === 'loading' ? 'none' : 'auto',\n            opacity: uiState === 'loading' ? 0.5 : 1,\n          }}\n          onKeyDown={(e) => {\n            if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {\n              formRef.current.dispatchEvent(\n                new Event('submit', { cancelable: true }),\n              );\n            }\n          }}\n          onSubmit={(e) => {\n            e.preventDefault();\n\n            const formData = new FormData(e.target);\n            const entries = Object.fromEntries(formData.entries());\n            console.log('ENTRIES', entries);\n            let { status, visibility, sensitive, spoilerText } = entries;\n\n            // Pre-cleanup\n            sensitive = sensitive === 'on'; // checkboxes return \"on\" if checked\n\n            // Validation\n            /* Let the backend validate this\n          if (stringLength(status) > maxCharacters) {\n            alert(`Status is too long! Max characters: ${maxCharacters}`);\n            return;\n          }\n          if (\n            sensitive &&\n            stringLength(status) + stringLength(spoilerText) > maxCharacters\n          ) {\n            alert(\n              `Status and content warning is too long! Max characters: ${maxCharacters}`,\n            );\n            return;\n          }\n          */\n            if (poll) {\n              if (poll.options.length < 2) {\n                alert('Poll must have at least 2 options');\n                return;\n              }\n              if (poll.options.some((option) => option === '')) {\n                alert('Some poll choices are empty');\n                return;\n              }\n            }\n            // TODO: check for URLs and use `charactersReservedPerUrl` to calculate max characters\n\n            if (mediaAttachments.length > 0) {\n              // If there are media attachments, check if they have no descriptions\n              const hasNoDescriptions = mediaAttachments.some(\n                (media) => !media.description?.trim?.(),\n              );\n              if (hasNoDescriptions) {\n                const yes = confirm(\n                  'Some media have no descriptions. Continue?',\n                );\n                if (!yes) return;\n              }\n            }\n\n            // Post-cleanup\n            spoilerText = (sensitive && spoilerText) || undefined;\n            status = status === '' ? undefined : status;\n\n            setUIState('loading');\n            (async () => {\n              try {\n                console.log('MEDIA ATTACHMENTS', mediaAttachments);\n                if (mediaAttachments.length > 0) {\n                  // Upload media attachments first\n                  const mediaPromises = mediaAttachments.map((attachment) => {\n                    const { file, description, id } = attachment;\n                    console.log('UPLOADING', attachment);\n                    if (id) {\n                      // If already uploaded\n                      return attachment;\n                    } else {\n                      const params = removeNullUndefined({\n                        file,\n                        description,\n                      });\n                      return masto.v2.media.create(params).then((res) => {\n                        if (res.id) {\n                          attachment.id = res.id;\n                        }\n                        return res;\n                      });\n                    }\n                  });\n                  const results = await Promise.allSettled(mediaPromises);\n\n                  // If any failed, return\n                  if (\n                    results.some((result) => {\n                      return result.status === 'rejected' || !result.value?.id;\n                    })\n                  ) {\n                    setUIState('error');\n                    // Alert all the reasons\n                    results.forEach((result) => {\n                      if (result.status === 'rejected') {\n                        console.error(result);\n                        alert(result.reason || `Attachment #${i} failed`);\n                      }\n                    });\n                    return;\n                  }\n\n                  console.log({ results, mediaAttachments });\n                }\n\n                /* NOTE:\n                Using snakecase here because masto.js's `isObject` returns false for `params`, ONLY happens when opening in pop-out window. This is maybe due to `window.masto` variable being passed from the parent window. The check that failed is `x.constructor === Object`, so maybe the `Object` in new window is different than parent window's?\n                Code: https://github.com/neet/masto.js/blob/dd0d649067b6a2b6e60fbb0a96597c373a255b00/src/serializers/is-object.ts#L2\n\n                // TODO: Note above is no longer true in Masto.js v6. Revisit this.\n              */\n                let params = {\n                  status,\n                  // spoilerText,\n                  spoiler_text: spoilerText,\n                  language,\n                  sensitive,\n                  poll,\n                  // mediaIds: mediaAttachments.map((attachment) => attachment.id),\n                  media_ids: mediaAttachments.map(\n                    (attachment) => attachment.id,\n                  ),\n                };\n                if (editStatus && supports('@mastodon/edit-media-attributes')) {\n                  params.media_attributes = mediaAttachments.map(\n                    (attachment) => {\n                      return {\n                        id: attachment.id,\n                        description: attachment.description,\n                        // focus\n                        // thumbnail\n                      };\n                    },\n                  );\n                } else if (!editStatus) {\n                  params.visibility = visibility;\n                  // params.inReplyToId = replyToStatus?.id || undefined;\n                  params.in_reply_to_id = replyToStatus?.id || undefined;\n                }\n                params = removeNullUndefined(params);\n                console.log('POST', params);\n\n                let newStatus;\n                if (editStatus) {\n                  newStatus = await masto.v1.statuses\n                    .$select(editStatus.id)\n                    .update(params);\n                  saveStatus(newStatus, instance, {\n                    skipThreading: true,\n                  });\n                } else {\n                  try {\n                    newStatus = await masto.v1.statuses.create(params, {\n                      idempotencyKey: UID.current,\n                    });\n                  } catch (_) {\n                    // If idempotency key fails, try again without it\n                    newStatus = await masto.v1.statuses.create(params);\n                  }\n                }\n                setUIState('default');\n\n                // Close\n                onClose({\n                  // type: post, reply, edit\n                  type: editStatus ? 'edit' : replyToStatus ? 'reply' : 'post',\n                  newStatus,\n                  instance,\n                });\n              } catch (e) {\n                console.error(e);\n                alert(e?.reason || e);\n                setUIState('error');\n              }\n            })();\n          }}\n        >\n          <div class=\"toolbar stretch\">\n            <input\n              ref={spoilerTextRef}\n              type=\"text\"\n              name=\"spoilerText\"\n              placeholder=\"Content warning\"\n              disabled={uiState === 'loading'}\n              class=\"spoiler-text-field\"\n              lang={language}\n              spellCheck=\"true\"\n              dir=\"auto\"\n              style={{\n                opacity: sensitive ? 1 : 0,\n                pointerEvents: sensitive ? 'auto' : 'none',\n              }}\n              onInput={() => {\n                updateCharCount();\n              }}\n            />\n            <label\n              class={`toolbar-button ${sensitive ? 'highlight' : ''}`}\n              title=\"Content warning or sensitive media\"\n            >\n              <input\n                name=\"sensitive\"\n                type=\"checkbox\"\n                checked={sensitive}\n                disabled={uiState === 'loading'}\n                onChange={(e) => {\n                  const sensitive = e.target.checked;\n                  setSensitive(sensitive);\n                  if (sensitive) {\n                    spoilerTextRef.current?.focus();\n                  } else {\n                    textareaRef.current?.focus();\n                  }\n                }}\n              />\n              <Icon icon={`eye-${sensitive ? 'close' : 'open'}`} />\n            </label>{' '}\n            <label\n              class={`toolbar-button ${\n                visibility !== 'public' && !sensitive ? 'show-field' : ''\n              } ${visibility !== 'public' ? 'highlight' : ''}`}\n              title={`Visibility: ${visibility}`}\n            >\n              <Icon icon={visibilityIconsMap[visibility]} alt={visibility} />\n              <select\n                name=\"visibility\"\n                value={visibility}\n                onChange={(e) => {\n                  setVisibility(e.target.value);\n                }}\n                disabled={uiState === 'loading' || !!editStatus}\n              >\n                <option value=\"public\">\n                  Public <Icon icon=\"earth\" />\n                </option>\n                <option value=\"unlisted\">Unlisted</option>\n                <option value=\"private\">Followers only</option>\n                <option value=\"direct\">Private mention</option>\n              </select>\n            </label>{' '}\n          </div>\n          <Textarea\n            ref={textareaRef}\n            placeholder={\n              replyToStatus\n                ? 'Post your reply'\n                : editStatus\n                ? 'Edit your post'\n                : 'What are you doing?'\n            }\n            required={mediaAttachments?.length === 0}\n            disabled={uiState === 'loading'}\n            lang={language}\n            onInput={() => {\n              updateCharCount();\n            }}\n            maxCharacters={maxCharacters}\n            performSearch={(params) => {\n              const { type, q, limit } = params;\n              if (type === 'accounts') {\n                return masto.v1.accounts.search.list({\n                  q,\n                  limit,\n                  resolve: false,\n                });\n              }\n              return masto.v2.search.fetch(params);\n            }}\n          />\n          {mediaAttachments?.length > 0 && (\n            <div class=\"media-attachments\">\n              {mediaAttachments.map((attachment, i) => {\n                const { id, file } = attachment;\n                const fileID = file?.size + file?.type + file?.name;\n                return (\n                  <MediaAttachment\n                    key={id || fileID || i}\n                    attachment={attachment}\n                    disabled={uiState === 'loading'}\n                    lang={language}\n                    onDescriptionChange={(value) => {\n                      setMediaAttachments((attachments) => {\n                        const newAttachments = [...attachments];\n                        newAttachments[i].description = value;\n                        return newAttachments;\n                      });\n                    }}\n                    onRemove={() => {\n                      setMediaAttachments((attachments) => {\n                        return attachments.filter((_, j) => j !== i);\n                      });\n                    }}\n                  />\n                );\n              })}\n              <label class=\"media-sensitive\">\n                <input\n                  name=\"sensitive\"\n                  type=\"checkbox\"\n                  checked={sensitive}\n                  disabled={uiState === 'loading'}\n                  onChange={(e) => {\n                    const sensitive = e.target.checked;\n                    setSensitive(sensitive);\n                  }}\n                />{' '}\n                <span>Mark media as sensitive</span>{' '}\n                <Icon icon={`eye-${sensitive ? 'close' : 'open'}`} />\n              </label>\n            </div>\n          )}\n          {!!poll && (\n            <Poll\n              lang={language}\n              maxOptions={maxOptions}\n              maxExpiration={maxExpiration}\n              minExpiration={minExpiration}\n              maxCharactersPerOption={maxCharactersPerOption}\n              poll={poll}\n              disabled={uiState === 'loading'}\n              onInput={(poll) => {\n                if (poll) {\n                  const newPoll = { ...poll };\n                  setPoll(newPoll);\n                } else {\n                  setPoll(null);\n                }\n              }}\n            />\n          )}\n          <div\n            class=\"toolbar wrap\"\n            style={{\n              justifyContent: 'flex-end',\n            }}\n          >\n            <span>\n              <label class=\"toolbar-button\">\n                <input\n                  type=\"file\"\n                  accept={supportedMimeTypes.join(',')}\n                  multiple={mediaAttachments.length < maxMediaAttachments - 1}\n                  disabled={\n                    uiState === 'loading' ||\n                    mediaAttachments.length >= maxMediaAttachments ||\n                    !!poll\n                  }\n                  onChange={(e) => {\n                    const files = e.target.files;\n                    if (!files) return;\n\n                    const mediaFiles = Array.from(files).map((file) => ({\n                      file,\n                      type: file.type,\n                      size: file.size,\n                      url: URL.createObjectURL(file),\n                      id: null, // indicate uploaded state\n                      description: null,\n                    }));\n                    console.log('MEDIA ATTACHMENTS', files, mediaFiles);\n\n                    // Validate max media attachments\n                    if (\n                      mediaAttachments.length + mediaFiles.length >\n                      maxMediaAttachments\n                    ) {\n                      alert(\n                        `You can only attach up to ${maxMediaAttachments} files.`,\n                      );\n                    } else {\n                      setMediaAttachments((attachments) => {\n                        return attachments.concat(mediaFiles);\n                      });\n                    }\n                    // Reset\n                    e.target.value = '';\n                  }}\n                />\n                <Icon icon=\"attachment\" />\n              </label>{' '}\n              <button\n                type=\"button\"\n                class=\"toolbar-button\"\n                disabled={\n                  uiState === 'loading' || !!poll || !!mediaAttachments.length\n                }\n                onClick={() => {\n                  setPoll({\n                    options: ['', ''],\n                    expiresIn: 24 * 60 * 60, // 1 day\n                    multiple: false,\n                  });\n                }}\n              >\n                <Icon icon=\"poll\" alt=\"Add poll\" />\n              </button>{' '}\n              <button\n                type=\"button\"\n                class=\"toolbar-button\"\n                disabled={uiState === 'loading'}\n                onClick={() => {\n                  setShowEmoji2Picker(true);\n                }}\n              >\n                <Icon icon=\"emoji2\" />\n              </button>\n            </span>\n            <div class=\"spacer\" />\n            {uiState === 'loading' ? (\n              <Loader abrupt />\n            ) : (\n              <CharCountMeter\n                maxCharacters={maxCharacters}\n                hidden={uiState === 'loading'}\n              />\n            )}\n            <label\n              class={`toolbar-button ${\n                language !== prevLanguage.current ? 'highlight' : ''\n              }`}\n            >\n              <span class=\"icon-text\">\n                {supportedLanguagesMap[language]?.native}\n              </span>\n              <select\n                name=\"language\"\n                value={language}\n                onChange={(e) => {\n                  const { value } = e.target;\n                  setLanguage(value || DEFAULT_LANG);\n                  store.session.set('currentLanguage', value || DEFAULT_LANG);\n                }}\n                disabled={uiState === 'loading'}\n              >\n                {topSupportedLanguages.map(([code, common, native]) => (\n                  <option value={code} key={code}>\n                    {common} ({native})\n                  </option>\n                ))}\n                <hr />\n                {restSupportedLanguages.map(([code, common, native]) => (\n                  <option value={code} key={code}>\n                    {common} ({native})\n                  </option>\n                ))}\n              </select>\n            </label>{' '}\n            <button\n              type=\"submit\"\n              class=\"large\"\n              disabled={uiState === 'loading'}\n            >\n              {replyToStatus ? 'Reply' : editStatus ? 'Update' : 'Post'}\n            </button>\n          </div>\n        </form>\n      </div>\n      {showEmoji2Picker && (\n        <Modal\n          class=\"light\"\n          onClick={(e) => {\n            if (e.target === e.currentTarget) {\n              setShowEmoji2Picker(false);\n            }\n          }}\n        >\n          <CustomEmojisModal\n            masto={masto}\n            instance={instance}\n            onClose={() => {\n              setShowEmoji2Picker(false);\n            }}\n            onSelect={(emoji) => {\n              const emojiWithSpace = ` ${emoji} `;\n              const textarea = textareaRef.current;\n              if (!textarea) return;\n              const { selectionStart, selectionEnd } = textarea;\n              const text = textarea.value;\n              const newText =\n                text.slice(0, selectionStart) +\n                emojiWithSpace +\n                text.slice(selectionEnd);\n              textarea.value = newText;\n              textarea.selectionStart = textarea.selectionEnd =\n                selectionEnd + emojiWithSpace.length;\n              textarea.focus();\n              textarea.dispatchEvent(new Event('input'));\n            }}\n          />\n        </Modal>\n      )}\n    </div>\n  );\n}\n\nfunction autoResizeTextarea(textarea) {\n  if (!textarea) return;\n  const { value, offsetHeight, scrollHeight, clientHeight } = textarea;\n  if (offsetHeight < window.innerHeight) {\n    // NOTE: This check is needed because the offsetHeight return 50000 (really large number) on first render\n    // No idea why it does that, will re-investigate in far future\n    const offset = offsetHeight - clientHeight;\n    const height = value ? scrollHeight + offset + 'px' : null;\n    textarea.style.height = height;\n  }\n}\n\nconst Textarea = forwardRef((props, ref) => {\n  const { masto } = api();\n  const [text, setText] = useState(ref.current?.value || '');\n  const { maxCharacters, performSearch = () => {}, ...textareaProps } = props;\n  // const snapStates = useSnapshot(states);\n  // const charCount = snapStates.composerCharacterCount;\n\n  const customEmojis = useRef();\n  useEffect(() => {\n    (async () => {\n      try {\n        const emojis = await masto.v1.customEmojis.list();\n        console.log({ emojis });\n        customEmojis.current = emojis;\n      } catch (e) {\n        // silent fail\n        console.error(e);\n      }\n    })();\n  }, []);\n\n  const textExpanderRef = useRef();\n  const textExpanderTextRef = useRef('');\n  useEffect(() => {\n    let handleChange, handleValue, handleCommited;\n    if (textExpanderRef.current) {\n      handleChange = (e) => {\n        // console.log('text-expander-change', e);\n        const { key, provide, text } = e.detail;\n        textExpanderTextRef.current = text;\n\n        if (text === '') {\n          provide(\n            Promise.resolve({\n              matched: false,\n            }),\n          );\n          return;\n        }\n\n        if (key === ':') {\n          // const emojis = customEmojis.current.filter((emoji) =>\n          //   emoji.shortcode.startsWith(text),\n          // );\n          const emojis = filterShortcodes(customEmojis.current, text);\n          let html = '';\n          emojis.forEach((emoji) => {\n            const { shortcode, url } = emoji;\n            html += `\n                <li role=\"option\" data-value=\"${encodeHTML(shortcode)}\">\n                <img src=\"${encodeHTML(\n                  url,\n                )}\" width=\"16\" height=\"16\" alt=\"\" loading=\"lazy\" /> \n                :${encodeHTML(shortcode)}:\n              </li>`;\n          });\n          // console.log({ emojis, html });\n          menu.innerHTML = html;\n          provide(\n            Promise.resolve({\n              matched: emojis.length > 0,\n              fragment: menu,\n            }),\n          );\n          return;\n        }\n\n        const type = {\n          '@': 'accounts',\n          '#': 'hashtags',\n        }[key];\n        provide(\n          new Promise((resolve) => {\n            const searchResults = performSearch({\n              type,\n              q: text,\n              limit: 5,\n            });\n            searchResults.then((value) => {\n              if (text !== textExpanderTextRef.current) {\n                return;\n              }\n              console.log({ value, type, v: value[type] });\n              const results = value[type] || value;\n              console.log('RESULTS', value, results);\n              let html = '';\n              results.forEach((result) => {\n                const {\n                  name,\n                  avatarStatic,\n                  displayName,\n                  username,\n                  acct,\n                  emojis,\n                  history,\n                } = result;\n                const displayNameWithEmoji = emojifyText(displayName, emojis);\n                // const item = menuItem.cloneNode();\n                if (acct) {\n                  html += `\n                    <li role=\"option\" data-value=\"${encodeHTML(acct)}\">\n                      <span class=\"avatar\">\n                        <img src=\"${encodeHTML(\n                          avatarStatic,\n                        )}\" width=\"16\" height=\"16\" alt=\"\" loading=\"lazy\" />\n                      </span>\n                      <span>\n                        <b>${displayNameWithEmoji || username}</b>\n                        <br>@${encodeHTML(acct)}\n                      </span>\n                    </li>\n                  `;\n                } else {\n                  const total = history?.reduce?.(\n                    (acc, cur) => acc + +cur.uses,\n                    0,\n                  );\n                  html += `\n                    <li role=\"option\" data-value=\"${encodeHTML(name)}\">\n                      <span class=\"grow\">#<b>${encodeHTML(name)}</b></span>\n                      ${\n                        total\n                          ? `<span class=\"count\">${shortenNumber(total)}</span>`\n                          : ''\n                      }\n                    </li>\n                  `;\n                }\n                menu.innerHTML = html;\n              });\n              console.log('MENU', results, menu);\n              resolve({\n                matched: results.length > 0,\n                fragment: menu,\n              });\n            });\n          }),\n        );\n      };\n\n      textExpanderRef.current.addEventListener(\n        'text-expander-change',\n        handleChange,\n      );\n\n      handleValue = (e) => {\n        const { key, item } = e.detail;\n        if (key === ':') {\n          e.detail.value = `:${item.dataset.value}:`;\n        } else {\n          e.detail.value = `${key}${item.dataset.value}`;\n        }\n      };\n\n      textExpanderRef.current.addEventListener(\n        'text-expander-value',\n        handleValue,\n      );\n\n      handleCommited = (e) => {\n        const { input } = e.detail;\n        setText(input.value);\n        // fire input event\n        if (ref.current) {\n          const event = new Event('input', { bubbles: true });\n          ref.current.dispatchEvent(event);\n        }\n      };\n\n      textExpanderRef.current.addEventListener(\n        'text-expander-committed',\n        handleCommited,\n      );\n    }\n\n    return () => {\n      if (textExpanderRef.current) {\n        textExpanderRef.current.removeEventListener(\n          'text-expander-change',\n          handleChange,\n        );\n        textExpanderRef.current.removeEventListener(\n          'text-expander-value',\n          handleValue,\n        );\n        textExpanderRef.current.removeEventListener(\n          'text-expander-committed',\n          handleCommited,\n        );\n      }\n    };\n  }, []);\n\n  useEffect(() => {\n    // Resize observer for textarea\n    const textarea = ref.current;\n    if (!textarea) return;\n    const resizeObserver = new ResizeObserver(() => {\n      // Get height of textarea, set height to textExpander\n      if (textExpanderRef.current) {\n        const { height } = textarea.getBoundingClientRect();\n        textExpanderRef.current.style.height = height + 'px';\n      }\n    });\n    resizeObserver.observe(textarea);\n  }, []);\n\n  const slowHighlightPerf = useRef(0); // increment if slow\n  const composeHighlightRef = useRef();\n  const throttleHighlightText = useThrottledCallback((text) => {\n    if (!composeHighlightRef.current) return;\n    if (slowHighlightPerf.current > 3) {\n      // After 3 times of lag, disable highlighting\n      composeHighlightRef.current.innerHTML = '';\n      composeHighlightRef.current = null; // Destroy the whole thing\n      throttleHighlightText?.cancel?.();\n      return;\n    }\n    let start;\n    let end;\n    if (slowHighlightPerf.current <= 3) start = Date.now();\n    composeHighlightRef.current.innerHTML =\n      highlightText(text, {\n        maxCharacters,\n      }) + '\\n';\n    if (slowHighlightPerf.current <= 3) end = Date.now();\n    console.debug('HIGHLIGHT PERF', { start, end, diff: end - start });\n    if (start && end && end - start > 50) {\n      // if slow, increment\n      slowHighlightPerf.current++;\n    }\n    // Newline to prevent multiple line breaks at the end from being collapsed, no idea why\n  }, 500);\n\n  return (\n    <text-expander\n      ref={textExpanderRef}\n      keys=\"@ # :\"\n      class=\"compose-field-container\"\n    >\n      <textarea\n        class=\"compose-field\"\n        autoCapitalize=\"sentences\"\n        autoComplete=\"on\"\n        autoCorrect=\"on\"\n        spellCheck=\"true\"\n        dir=\"auto\"\n        rows=\"6\"\n        cols=\"50\"\n        {...textareaProps}\n        ref={ref}\n        name=\"status\"\n        value={text}\n        onKeyDown={(e) => {\n          // Get line before cursor position after pressing 'Enter'\n          const { key, target } = e;\n          if (key === 'Enter') {\n            try {\n              const { value, selectionStart } = target;\n              const textBeforeCursor = value.slice(0, selectionStart);\n              const lastLine = textBeforeCursor.split('\\n').slice(-1)[0];\n              if (lastLine) {\n                // If line starts with \"- \" or \"12. \"\n                if (/^\\s*(-|\\d+\\.)\\s/.test(lastLine)) {\n                  // insert \"- \" at cursor position\n                  const [_, preSpaces, bullet, postSpaces, anything] =\n                    lastLine.match(/^(\\s*)(-|\\d+\\.)(\\s+)(.+)?/) || [];\n                  if (anything) {\n                    e.preventDefault();\n                    const [number] = bullet.match(/\\d+/) || [];\n                    const newBullet = number ? `${+number + 1}.` : '-';\n                    const text = `\\n${preSpaces}${newBullet}${postSpaces}`;\n                    target.setRangeText(text, selectionStart, selectionStart);\n                    const pos = selectionStart + text.length;\n                    target.setSelectionRange(pos, pos);\n                  } else {\n                    // trim the line before the cursor, then insert new line\n                    const pos = selectionStart - lastLine.length;\n                    target.setRangeText('', pos, selectionStart);\n                  }\n                  autoResizeTextarea(target);\n                  target.dispatchEvent(new Event('input'));\n                }\n              }\n            } catch (e) {\n              // silent fail\n              console.error(e);\n            }\n          }\n          if (composeHighlightRef.current) {\n            composeHighlightRef.current.scrollTop = target.scrollTop;\n          }\n        }}\n        onInput={(e) => {\n          const { target } = e;\n          const text = target.value;\n          setText(text);\n          autoResizeTextarea(target);\n          props.onInput?.(e);\n          throttleHighlightText(text);\n        }}\n        style={{\n          width: '100%',\n          height: '4em',\n          // '--text-weight': (1 + charCount / 140).toFixed(1) || 1,\n        }}\n        onScroll={(e) => {\n          if (composeHighlightRef.current) {\n            const { scrollTop } = e.target;\n            composeHighlightRef.current.scrollTop = scrollTop;\n          }\n        }}\n      />\n      <div\n        ref={composeHighlightRef}\n        class=\"compose-highlight\"\n        aria-hidden=\"true\"\n      />\n    </text-expander>\n  );\n});\n\nfunction CharCountMeter({ maxCharacters = 500, hidden }) {\n  const snapStates = useSnapshot(states);\n  const charCount = snapStates.composerCharacterCount;\n  const leftChars = maxCharacters - charCount;\n  if (hidden) {\n    return <meter class=\"donut\" hidden />;\n  }\n  return (\n    <meter\n      class={`donut ${\n        leftChars <= -10\n          ? 'explode'\n          : leftChars <= 0\n          ? 'danger'\n          : leftChars <= 20\n          ? 'warning'\n          : ''\n      }`}\n      value={charCount}\n      max={maxCharacters}\n      data-left={leftChars}\n      title={`${leftChars}/${maxCharacters}`}\n      style={{\n        '--percentage': (charCount / maxCharacters) * 100,\n      }}\n    />\n  );\n}\n\nfunction MediaAttachment({\n  attachment,\n  disabled,\n  lang,\n  onDescriptionChange = () => {},\n  onRemove = () => {},\n}) {\n  const [uiState, setUIState] = useState('default');\n  const supportsEdit = supports('@mastodon/edit-media-attributes');\n  const { type, id, file } = attachment;\n  const url = useMemo(\n    () => (file ? URL.createObjectURL(file) : attachment.url),\n    [file, attachment.url],\n  );\n  console.log({ attachment });\n  const [description, setDescription] = useState(attachment.description);\n  const [suffixType, subtype] = type.split('/');\n  const debouncedOnDescriptionChange = useDebouncedCallback(\n    onDescriptionChange,\n    250,\n  );\n\n  const [showModal, setShowModal] = useState(false);\n  const textareaRef = useRef(null);\n  useEffect(() => {\n    let timer;\n    if (showModal && textareaRef.current) {\n      timer = setTimeout(() => {\n        textareaRef.current.focus();\n      }, 100);\n    }\n    return () => {\n      clearTimeout(timer);\n    };\n  }, [showModal]);\n\n  const descTextarea = (\n    <>\n      {!!id && !supportsEdit ? (\n        <div class=\"media-desc\">\n          <span class=\"tag\">Uploaded</span>\n          <p title={description}>\n            {attachment.description || <i>No description</i>}\n          </p>\n        </div>\n      ) : (\n        <textarea\n          ref={textareaRef}\n          value={description || ''}\n          lang={lang}\n          placeholder={\n            {\n              image: 'Image description',\n              video: 'Video description',\n              audio: 'Audio description',\n            }[suffixType]\n          }\n          autoCapitalize=\"sentences\"\n          autoComplete=\"on\"\n          autoCorrect=\"on\"\n          spellCheck=\"true\"\n          dir=\"auto\"\n          disabled={disabled || uiState === 'loading'}\n          class={uiState === 'loading' ? 'loading' : ''}\n          maxlength=\"1500\" // Not unicode-aware :(\n          // TODO: Un-hard-code this maxlength, ref: https://github.com/mastodon/mastodon/blob/b59fb28e90bc21d6fd1a6bafd13cfbd81ab5be54/app/models/media_attachment.rb#L39\n          onInput={(e) => {\n            const { value } = e.target;\n            setDescription(value);\n            debouncedOnDescriptionChange(value);\n          }}\n        ></textarea>\n      )}\n    </>\n  );\n\n  const toastRef = useRef(null);\n  useEffect(() => {\n    return () => {\n      toastRef.current?.hideToast?.();\n    };\n  }, []);\n\n  return (\n    <>\n      <div class=\"media-attachment\">\n        <div\n          class=\"media-preview\"\n          tabIndex=\"0\"\n          onClick={() => {\n            setShowModal(true);\n          }}\n        >\n          {suffixType === 'image' ? (\n            <img src={url} alt=\"\" />\n          ) : suffixType === 'video' || suffixType === 'gifv' ? (\n            <video src={url} playsinline muted />\n          ) : suffixType === 'audio' ? (\n            <audio src={url} controls />\n          ) : null}\n        </div>\n        {descTextarea}\n        <div class=\"media-aside\">\n          <button\n            type=\"button\"\n            class=\"plain close-button\"\n            disabled={disabled}\n            onClick={onRemove}\n          >\n            <Icon icon=\"x\" />\n          </button>\n        </div>\n      </div>\n      {showModal && (\n        <Modal\n          class=\"light\"\n          onClick={(e) => {\n            if (e.target === e.currentTarget) {\n              setShowModal(false);\n            }\n          }}\n        >\n          <div id=\"media-sheet\" class=\"sheet sheet-max\">\n            <button\n              type=\"button\"\n              class=\"sheet-close\"\n              onClick={() => {\n                setShowModal(false);\n              }}\n            >\n              <Icon icon=\"x\" />\n            </button>\n            <header>\n              <h2>\n                {\n                  {\n                    image: 'Edit image description',\n                    video: 'Edit video description',\n                    audio: 'Edit audio description',\n                  }[suffixType]\n                }\n              </h2>\n            </header>\n            <main tabIndex=\"-1\">\n              <div class=\"media-preview\">\n                {suffixType === 'image' ? (\n                  <img src={url} alt=\"\" />\n                ) : suffixType === 'video' || suffixType === 'gifv' ? (\n                  <video src={url} playsinline controls />\n                ) : suffixType === 'audio' ? (\n                  <audio src={url} controls />\n                ) : null}\n              </div>\n              <div class=\"media-form\">\n                {descTextarea}\n                <footer>\n                  {suffixType === 'image' &&\n                    /^(png|jpe?g|gif|webp)$/i.test(subtype) &&\n                    !!states.settings.mediaAltGenerator &&\n                    !!IMG_ALT_API_URL && (\n                      <Menu2\n                        portal={{\n                          target: document.body,\n                        }}\n                        containerProps={{\n                          style: {\n                            zIndex: 1001,\n                          },\n                        }}\n                        align=\"center\"\n                        position=\"anchor\"\n                        overflow=\"auto\"\n                        menuButton={\n                          <button type=\"button\" title=\"More\" class=\"plain\">\n                            <Icon icon=\"more\" size=\"l\" alt=\"More\" />\n                          </button>\n                        }\n                      >\n                        <MenuItem\n                          disabled={uiState === 'loading'}\n                          onClick={() => {\n                            setUIState('loading');\n                            toastRef.current = showToast({\n                              text: 'Generating description. Please wait...',\n                              duration: -1,\n                            });\n                            // POST with multipart\n                            (async function () {\n                              try {\n                                const body = new FormData();\n                                body.append('image', file);\n                                const response = await fetch(IMG_ALT_API_URL, {\n                                  method: 'POST',\n                                  body,\n                                }).then((r) => r.json());\n                                setDescription(response.description);\n                              } catch (e) {\n                                console.error(e);\n                                showToast('Failed to generate description');\n                              } finally {\n                                setUIState('default');\n                                toastRef.current?.hideToast?.();\n                              }\n                            })();\n                          }}\n                        >\n                          <Icon icon=\"sparkles2\" />\n                          <span>Generate description…</span>\n                        </MenuItem>\n                      </Menu2>\n                    )}\n                  <button\n                    type=\"button\"\n                    class=\"light block\"\n                    onClick={() => {\n                      setShowModal(false);\n                    }}\n                    disabled={uiState === 'loading'}\n                  >\n                    Done\n                  </button>\n                </footer>\n              </div>\n            </main>\n          </div>\n        </Modal>\n      )}\n    </>\n  );\n}\n\nfunction Poll({\n  lang,\n  poll,\n  disabled,\n  onInput = () => {},\n  maxOptions,\n  maxExpiration,\n  minExpiration,\n  maxCharactersPerOption,\n}) {\n  const { options, expiresIn, multiple } = poll;\n\n  return (\n    <div class={`poll ${multiple ? 'multiple' : ''}`}>\n      <div class=\"poll-choices\">\n        {options.map((option, i) => (\n          <div class=\"poll-choice\" key={i}>\n            <input\n              required\n              type=\"text\"\n              value={option}\n              disabled={disabled}\n              maxlength={maxCharactersPerOption}\n              placeholder={`Choice ${i + 1}`}\n              lang={lang}\n              spellCheck=\"true\"\n              dir=\"auto\"\n              onInput={(e) => {\n                const { value } = e.target;\n                options[i] = value;\n                onInput(poll);\n              }}\n            />\n            <button\n              type=\"button\"\n              class=\"plain2 poll-button\"\n              disabled={disabled || options.length <= 1}\n              onClick={() => {\n                options.splice(i, 1);\n                onInput(poll);\n              }}\n            >\n              <Icon icon=\"x\" size=\"s\" />\n            </button>\n          </div>\n        ))}\n      </div>\n      <div class=\"poll-toolbar\">\n        <button\n          type=\"button\"\n          class=\"plain2 poll-button\"\n          disabled={disabled || options.length >= maxOptions}\n          onClick={() => {\n            options.push('');\n            onInput(poll);\n          }}\n        >\n          +\n        </button>{' '}\n        <label class=\"multiple-choices\">\n          <input\n            type=\"checkbox\"\n            checked={multiple}\n            disabled={disabled}\n            onChange={(e) => {\n              const { checked } = e.target;\n              poll.multiple = checked;\n              onInput(poll);\n            }}\n          />{' '}\n          Multiple choices\n        </label>\n        <label class=\"expires-in\">\n          Duration{' '}\n          <select\n            value={expiresIn}\n            disabled={disabled}\n            onChange={(e) => {\n              const { value } = e.target;\n              poll.expiresIn = value;\n              onInput(poll);\n            }}\n          >\n            {Object.entries(expiryOptions)\n              .filter(([label, value]) => {\n                return value >= minExpiration && value <= maxExpiration;\n              })\n              .map(([label, value]) => (\n                <option value={value} key={value}>\n                  {label}\n                </option>\n              ))}\n          </select>\n        </label>\n      </div>\n      <div class=\"poll-toolbar\">\n        <button\n          type=\"button\"\n          class=\"plain remove-poll-button\"\n          disabled={disabled}\n          onClick={() => {\n            onInput(null);\n          }}\n        >\n          Remove poll\n        </button>\n      </div>\n    </div>\n  );\n}\n\nfunction filterShortcodes(emojis, searchTerm) {\n  searchTerm = searchTerm.toLowerCase();\n\n  // Return an array of shortcodes that start with or contain the search term, sorted by relevance and limited to the first 5\n  return emojis\n    .sort((a, b) => {\n      let aLower = a.shortcode.toLowerCase();\n      let bLower = b.shortcode.toLowerCase();\n\n      let aStartsWith = aLower.startsWith(searchTerm);\n      let bStartsWith = bLower.startsWith(searchTerm);\n      let aContains = aLower.includes(searchTerm);\n      let bContains = bLower.includes(searchTerm);\n      let bothStartWith = aStartsWith && bStartsWith;\n      let bothContain = aContains && bContains;\n\n      return bothStartWith\n        ? a.length - b.length\n        : aStartsWith\n        ? -1\n        : bStartsWith\n        ? 1\n        : bothContain\n        ? a.length - b.length\n        : aContains\n        ? -1\n        : bContains\n        ? 1\n        : 0;\n    })\n    .slice(0, 5);\n}\n\nfunction encodeHTML(str) {\n  return str.replace(/[&<>\"']/g, function (char) {\n    return '&#' + char.charCodeAt(0) + ';';\n  });\n}\n\nfunction removeNullUndefined(obj) {\n  for (let key in obj) {\n    if (obj[key] === null || obj[key] === undefined) {\n      delete obj[key];\n    }\n  }\n  return obj;\n}\n\nfunction CustomEmojisModal({\n  masto,\n  instance,\n  onClose = () => {},\n  onSelect = () => {},\n}) {\n  const [uiState, setUIState] = useState('default');\n  const customEmojisList = useRef([]);\n  const [customEmojis, setCustomEmojis] = useState({});\n  const recentlyUsedCustomEmojis = useMemo(\n    () => store.account.get('recentlyUsedCustomEmojis') || [],\n  );\n  useEffect(() => {\n    setUIState('loading');\n    (async () => {\n      try {\n        const emojis = await masto.v1.customEmojis.list();\n        // Group emojis by category\n        const emojisCat = {\n          '--recent--': recentlyUsedCustomEmojis.filter((emoji) =>\n            emojis.find((e) => e.shortcode === emoji.shortcode),\n          ),\n        };\n        const othersCat = [];\n        emojis.forEach((emoji) => {\n          if (!emoji.visibleInPicker) return;\n          customEmojisList.current?.push?.(emoji);\n          if (!emoji.category) {\n            othersCat.push(emoji);\n            return;\n          }\n          if (!emojisCat[emoji.category]) {\n            emojisCat[emoji.category] = [];\n          }\n          emojisCat[emoji.category].push(emoji);\n        });\n        if (othersCat.length) {\n          emojisCat['--others--'] = othersCat;\n        }\n        setCustomEmojis(emojisCat);\n        setUIState('default');\n      } catch (e) {\n        setUIState('error');\n        console.error(e);\n      }\n    })();\n  }, []);\n\n  return (\n    <div id=\"custom-emojis-sheet\" class=\"sheet\">\n      {!!onClose && (\n        <button type=\"button\" class=\"sheet-close\" onClick={onClose}>\n          <Icon icon=\"x\" />\n        </button>\n      )}\n      <header>\n        <b>Custom emojis</b>{' '}\n        {uiState === 'loading' ? (\n          <Loader />\n        ) : (\n          <small class=\"insignificant\"> • {instance}</small>\n        )}\n      </header>\n      <main>\n        <div class=\"custom-emojis-list\">\n          {uiState === 'error' && (\n            <div class=\"ui-state\">\n              <p>Error loading custom emojis</p>\n            </div>\n          )}\n          {uiState === 'default' &&\n            Object.entries(customEmojis).map(\n              ([category, emojis]) =>\n                !!emojis?.length && (\n                  <>\n                    <div class=\"section-header\">\n                      {{\n                        '--recent--': 'Recently used',\n                        '--others--': 'Others',\n                      }[category] || category}\n                    </div>\n                    <section>\n                      {emojis.map((emoji) => (\n                        <button\n                          key={emoji}\n                          type=\"button\"\n                          class=\"plain4\"\n                          onClick={() => {\n                            onClose();\n                            requestAnimationFrame(() => {\n                              onSelect(`:${emoji.shortcode}:`);\n                            });\n                            let recentlyUsedCustomEmojis =\n                              store.account.get('recentlyUsedCustomEmojis') ||\n                              [];\n                            const recentlyUsedEmojiIndex =\n                              recentlyUsedCustomEmojis.findIndex(\n                                (e) => e.shortcode === emoji.shortcode,\n                              );\n                            if (recentlyUsedEmojiIndex !== -1) {\n                              // Move emoji to index 0\n                              recentlyUsedCustomEmojis.splice(\n                                recentlyUsedEmojiIndex,\n                                1,\n                              );\n                              recentlyUsedCustomEmojis.unshift(emoji);\n                            } else {\n                              recentlyUsedCustomEmojis.unshift(emoji);\n                              // Remove unavailable ones\n                              recentlyUsedCustomEmojis =\n                                recentlyUsedCustomEmojis.filter((e) =>\n                                  customEmojisList.current?.find?.(\n                                    (emoji) => emoji.shortcode === e.shortcode,\n                                  ),\n                                );\n                              // Limit to 10\n                              recentlyUsedCustomEmojis =\n                                recentlyUsedCustomEmojis.slice(0, 10);\n                            }\n\n                            // Store back\n                            store.account.set(\n                              'recentlyUsedCustomEmojis',\n                              recentlyUsedCustomEmojis,\n                            );\n                          }}\n                          title={`:${emoji.shortcode}:`}\n                        >\n                          <picture>\n                            {!!emoji.staticUrl && (\n                              <source\n                                srcset={emoji.staticUrl}\n                                media=\"(prefers-reduced-motion: reduce)\"\n                              />\n                            )}\n                            <img\n                              class=\"shortcode-emoji\"\n                              src={emoji.url || emoji.staticUrl}\n                              alt={emoji.shortcode}\n                              width=\"16\"\n                              height=\"16\"\n                              loading=\"lazy\"\n                              decoding=\"async\"\n                            />\n                          </picture>\n                        </button>\n                      ))}\n                    </section>\n                  </>\n                ),\n            )}\n        </div>\n      </main>\n    </div>\n  );\n}\n\nexport default Compose;\n","import { useLayoutEffect } from 'preact/hooks';\nimport { matchPath } from 'react-router-dom';\nimport { subscribeKey } from 'valtio/utils';\n\nimport states from './states';\n\nconst { PHANPY_CLIENT_NAME: CLIENT_NAME } = import.meta.env;\n\nexport default function useTitle(title, path) {\n  function setTitle() {\n    const { currentLocation } = states;\n    const hasPaths = Array.isArray(path);\n    let paths = hasPaths ? path : [];\n    // Workaround for matchPath not working for optional path segments\n    // https://github.com/remix-run/react-router/discussions/9862\n    if (!hasPaths && /:?\\w+\\?/.test(path)) {\n      paths.push(path.replace(/(:\\w+)\\?/g, '$1'));\n      paths.push(path.replace(/\\/?:\\w+\\?/g, ''));\n    }\n    let matched = false;\n    if (paths.length) {\n      matched = paths.some((p) => matchPath(p, currentLocation));\n    } else if (path) {\n      matched = matchPath(path, currentLocation);\n    }\n    console.debug('setTitle', { title, path, currentLocation, paths, matched });\n    if (matched) {\n      document.title = title ? `${title} / ${CLIENT_NAME}` : CLIENT_NAME;\n    }\n  }\n\n  useLayoutEffect(() => {\n    const unsub = subscribeKey(states, 'currentLocation', setTitle);\n    setTitle();\n    return unsub;\n  }, [title, path]);\n}\n"],"file":"assets/useTitle-zvzl1Hla.js"}