72efd76cea
SHA256:73b875c33e9e3a93fbdc31ad135730ebc8f2bf61e06f5185b0f831caa18f1b04
1 line
355 KiB
Plaintext
1 line
355 KiB
Plaintext
{"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","unfurlStatus","_threadifyStatus","propInstance","api","traverse","index","fetchIndex","inReplyToAccountId","inReplyToId","prevStatus","r","fetchStatus","fauxDiv","content","document","sKey","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","SVGICon","size","body","rotate","flip","_jsx","viewBox","dangerouslySetInnerHTML","__html","style","transform","isShallowEqual","Object","keys","Icon","icon","alt","title","class","iconSize","iconBlock","Array","iconData","setIconData","useState","currentIcon","iconB","useCloseWatcher","watcher","CloseWatcher","addEventListener","destroy","useCloseWatcher$1","$modalContainer","getElementById","Modal","children","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","queueMicrotask","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","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","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","reblog","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.reblog || status, instance);\n });\n }\n\n // UNFURLER\n if (!skipUnfurling) {\n queueMicrotask(() => {\n unfurlStatus(status.reblog || 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?.content;\n const hasLink = /<a/i.test(content);\n if (hasLink) {\n const sKey = statusKey(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 moize from 'moize';\nimport { 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\n// Memoize the dangerouslySetInnerHTML of the SVGs\nconst SVGICon = moize(\n function ({ size, width, height, body, rotate, flip }) {\n return (\n <svg\n width={size}\n height={size}\n viewBox={`0 0 ${width} ${height}`}\n dangerouslySetInnerHTML={{ __html: body }}\n style={{\n transform: `${rotate ? `rotate(${rotate})` : ''} ${\n flip ? `scaleX(-1)` : ''\n }`,\n }}\n />\n );\n },\n {\n isShallowEqual: true,\n maxSize: Object.keys(ICONS).length,\n },\n);\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 if (!iconBlock) {\n console.warn(`Icon ${icon} not found`);\n return null;\n }\n\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 <SVGICon\n size={iconSize}\n width={iconData.width}\n height={iconData.height}\n body={iconData.body}\n rotate={rotate}\n flip={flip}\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, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\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, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\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, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\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 · 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 • <span title={votersCount}>\n {shortenNumber(votersCount)}\n </span>{' '}\n voter\n {votersCount === 1 ? '' : 's'}\n </>\n )}{' '}\n • {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 • <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…\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 ’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-60DQEO3r.js"} |