296ef9214b
SHA256:17ea8854a3504752a8ae4540ecb09e62f6f8aa260221d3619fda46f66b2ec43f
1 line
354 KiB
Plaintext
1 line
354 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","queueMicrotask","unfurlStatus","_threadifyStatus","propInstance","api","traverse","index","fetchIndex","inReplyToAccountId","inReplyToId","prevStatus","r","fetchStatus","fauxDiv","content","document","sKey","reblog","i","splice","useInterval","deps","immediate","savedCallback","useRef","useEffect","current","setInterval","tick","clearInterval","openCompose","width","screenWidth","height","screenHeight","screen","left","Math","max","top","min","winUID","uid","random","newWin","open","__COMPOSE__","alert","SIZES","s","m","l","xl","xxl","ICONS","x","__vitePreload","heart","bookmark","check-circle","x-circle","transfer","rocket","arrow-left","arrow-right","arrow-up","arrow-down","earth","lock","unlock","eye-close","eye-open","message","comment","comment2","notification","follow","follow-add","poll","pencil","quill","at","attachment","upload","gear","more","external","popout","popin","plus","chevron-left","chevron-right","chevron-down","reply","thread","group","bot","menu","list","search","hashtag","shortcut","user","following","pin","bus","link","history","share","sparkles","sparkles2","exit","translate","play","trash","mute","unmute","block","unblock","flag","time","refresh","emoji2","filter","chart","react","layout4","layout5","announce","round","arrow-up-circle","arrow-down-circle","clipboard","account-edit","account-warning","keyboard","cloud","month","media","speak","building","ICONDATA","Icon","icon","size","alt","title","class","style","iconSize","iconBlock","rotate","flip","Array","isArray","iconData","setIconData","useState","currentIcon","iconB","default","_jsx","children","viewBox","dangerouslySetInnerHTML","__html","body","transform","useCloseWatcher","watcher","CloseWatcher","addEventListener","destroy","useCloseWatcher$1","$modalContainer","getElementById","Modal","onClose","modalRef","timer","focusElement","querySelector","focus","clearTimeout","supportsCloseWatcher","escRef","useHotkeys","enabled","keydown","keyup","ref","node","target","currentTarget","tabIndex","onFocus","createPortal","Loader","abrupt","hidden","xxxl","alphaCache","canvas","OffscreenCanvas","createElement","ctx","getContext","willReadFrequently","Avatar","squircle","avatarRef","isMissing","src","loading","decoding","crossOrigin","undefined","onError","onLoad","dataset","loaded","drawImage","allPixels","getImageData","hasAlpha","pixel","classList","add","clearRect","Link","forwardRef","routerLocation","useLocation","trim","to","restProps","includes","parsedHash","origin","searchParams","searchParamsStr","from","entries","map","encodeURIComponent","join","isActive","decodeURIComponent","href","parentNode","closest","stopPropagation","root","documentElement","getComputedStyle","defaultBoundingBoxPadding","safeAreaInsets","getSafeAreaInsets","safeAreaInsetTop","getPropertyValue","safeAreaInsetRight","safeAreaInsetBottom","safeAreaInsetLeft","parseInt","requestAnimationFrame","safeBoundingBoxPadding","paddings","CSS","supports","passive","Menu2","containerProps","instanceRef","_instanceRef","useWindowSize","Menu","boundingBoxPadding","repositionFlag","unmountOnClose","closeMenu","MenuConfirm","subMenu","confirm","confirmLabel","menuItemClassName","menuFooter","MenuItem","cloneElement","Parent","SubMenu","menuRef","_jsxs","openTrigger","direction","overflow","gap","menuClassName","menuButton","label","itemProps","onPointerMove","pointerType","openMenu","onPointerLeave","EmojiText","emojis","indexOf","regex","shortcode","elements","split","word","emoji","staticUrl","srcset","nameCollator","Intl","Collator","sensitivity","NameText","showAvatar","showAcct","short","acct","avatar","avatarStatic","username","_","acct1","acct2","match","trimmedUsername","trimmedDisplayName","shortenedDisplayName","shortenedAlphaNumericDisplayName","compare","preventDefault","_Fragment","memo","dayjs","extend","dayjsTwitter","localizedFormat","relativeTime","dtf","DateTimeFormat","RelativeTime","datetime","format","date","dateStr","now","diff","year","twitter","toDate","fromNow","toISOString","_localeMatch","localeMatch","locales","resolvedOptions","locale","navigator","languages","localeTargetLanguages","translationTargetLanguages","code","getTranslateTargetLanguage","fromSettings","localeCode2Text","DisplayNames","of","emojifyText","forEach","whitelistLinkClasses","_enhanceContent","postEnhanceDOM","enhancedContent","dom","innerHTML","hasLink","hasCodeBlock","querySelectorAll","setAttribute","c","remove","textContent","links","usernames","innerText","hasChildren","some","u","d","slice","textNodes","extractTextNodes","html","nodeValue","replaceWith","childNodes","p","pre","br","nextParagraphs","currentBlock","nextElementSibling","next","tagName","codeText","replaceAll","rejectFilter","prevIndex","hashtagStuffedParagraphs","hashtagCount","nodeType","Node","TEXT_NODE","linkText","startsWith","enhanceContent","defaultRejectFilter","defaultRejectFilterMap","Object","fromEntries","nodeName","rejectFilterMap","assign","reduce","cur","walk","createTreeWalker","NodeFilter","SHOW_TEXT","acceptNode","FILTER_REJECT","FILTER_ACCEPT","nextNode","div","getHTMLText","handleContentLinks","mentions","previewMode","statusURL","metaKey","ctrlKey","shiftKey","altKey","which","prevText","previousSibling","textBeforeLinkIsAt","endsWith","textStartsWithAt","contains","targetText","getAttribute","mention","textBeforeLinkIsHash","tag","_DateTimeFormat","dateYear","hideTime","formatOpts","currentYear","Date","getFullYear","day","hour","minute","niceDateTime","dtfOpts","NumberFormat","shortenNumber","notation","AccountBlock","skeleton","avatarSize","useAvatarStatic","internal","showActivity","showStats","accountInstance","hideDisplayName","relationship","excludeRelationshipAttrs","statusesCount","lastStatusAt","fields","note","followersCount","verifiedField","f","verifiedAt","excludedRelationship","hasRelationship","followedBy","requested","supportsCache","feature","version","range","features","satisfies","includePrerelease","loose","define_import_meta_env_default","draftsStore","createStore","drafts","val","getMany","keys","delMany","clear","db","visibilityIconsMap","public","unlisted","private","direct","Poll","lang","readOnly","votePoll","uiState","setUIState","expired","expiresAt","multiple","options","ownVotes","voted","votersCount","votesCount","expiresAtDate","pollVotesCount","roundPrecision","showResults","setShowResults","optionsHaveVoteCounts","every","o","dir","option","optionVotesCount","percentage","toFixed","isLeading","disabled","onSubmit","form","formData","FormData","choices","name","marginLeft","FilterContext","createContext","_isFiltered","filtered","filterContext","appliedFilters","hasContext","context","isHidden","filterAction","action","filterTitles","titles","titlesStr","isFiltered","filteredItem","item","currentAccountID","isSelf","filterState","filteredItems","items","htmlContentLength","el","supportsTTS","speechSynthesis","speaking","cancel","utterance","SpeechSynthesisUtterance","statusPeek","spoilerText","mediaAttachments","image","gifv","video","audio","unknown","useTruncated","onResize","useThrottledCallback","scrollHeight","truncated","_height","maxHeight","computedHeight","toggle","useResizeObserver","box","isSafari","userAgent","dataAltLabel","AltBadge","MEDIA_CAPTION_LIMIT","isMediaCaptionLong","caption","Media","showOriginal","autoAnimate","showCaption","altIndex","blurhash","description","meta","previewRemoteUrl","previewUrl","remoteUrl","original","small","mediaURL","remoteMediaURL","orientation","rgbAverageColor","getBlurHashAverageColor","videoRef","focalPosition","y","mediaRef","onUpdate","useCallback","scale","make3dTransformValue","removeProperty","setProperty","touchAction","pinchZoomEnabled","setPinchZoomEnabled","quickPinchZoomProps","draggableUnZoomed","inertiaFriction","doubleTapZoomOutOnMaxScale","useMemo","isVideoMaybe","isImage","parentRef","imageSmallerThanParent","setImageSmallerThanParent","useLayoutEffect","offsetWidth","offsetHeight","smaller","maxAspectHeight","innerHeight","averageColorStyle","mediaStyles","aspectRatio","longDesc","showInlineDesc","Figure","Fragment","display","fetch","mode","backgroundImage","backgroundSize","QuickPinchZoom","objectPosition","shortDuration","duration","isGIF","loopable","formattedDuration","formatDuration","hoverAnimate","autoGIFAnimate","showProgress","videoHTML","pause","onMouseEnter","onMouseLeave","onBlur","poster","preload","playsinline","loop","muted","onTimeUpdate","container","currentTime","controls","autoplay","MenuLink","FocusableItem","detail","SHOW_COMMENT_COUNT_LIMIT","INLINE_TRANSLATE_LIMIT","fetchAccount","v1","$select","memFetchAccount","visibilityText","isIOS","ontouchstart","REACTIONS_LIMIT","getPollText","getPostText","Status","contentTextWeight","enableCommentHint","withinContext","enableTranslate","forceTranslate","_forceTranslate","onMediaClick","quoted","onStatusLinkClick","showFollowedTags","sameInstance","useSnapshot","accountId","accountURL","accountEmojis","repliesCount","reblogged","reblogsCount","favourited","favouritesCount","bookmarked","sensitive","visibility","language","editedAt","card","createdAt","tags","_deleted","useContext","filterInfo","debugHover","FilteredStatus","editedAtDate","inReplyToAccountRef","createdAtDate","setInReplyToAccount","inReplyToAccount","account2","catch","mentionSelf","readingExpandSpoilers","showSpoiler","readingExpandMedia","prefs","showSpoilerMedia","setForceTranslate","targetLanguage","contentLength","inlineTranslate","isSizeLarge","showEdited","setShowEdited","spoilerContentRef","statusRef","contentRef","mediaContainerRef","readMoreText","textWeight","editedDateText","_a","mediaNoDesc","replyToStatus","boostStatus","unauthInteractionErrorMessage","confirmText","yes","newStatus","favouriteStatus","differentLanguage","fetchBoostedLikedByAccounts","firstLoad","favouriteIterator","reblogResults","reblogIterator","favouriteResults","_types","done","MenuHeader","StatusMenuItems","createdDateText","MenuDivider","fetchAccounts","showReactions","replyStatus","postText","nicePostURL","editStatus","cachedStatus","setIsContextMenuOpen","contextMenuProps","setContextMenuProps","showContextMenu","bindLongPressContext","useLongPress","clientX","clientY","isContextMenuOpen","hotkeysEnabled","fRef","rRef","dRef","confirmBoostStatus","bRef","activeStatus","spoilerButton","spoilerMediaButton","canBoost","xRef","showMultipleMediaCaptions","displayedMediaAttachments","attachments","indices","showCommentHint","isThread","showCommentCount","nodeRef","containsQuestion","contextMenuRef","portal","QuoteStatuses","newPoll","pollResponse","TranslationBlock","mini","captionChildren","StatusButton","checked","bookmarkStatus","count","EditedAtModal","selfReferential","providerName","providerUrl","authorName","authorUrl","imageDescription","embedUrl","publishedAt","hasText","isLandscape","cardStatusURL","blurhashImage","setCardStatusURL","imageData","w","putImageData","videoID","fetchStatusHistory","editHistory","setEditHistory","editHistory2","setIconAlt","buttonTitle","iconAlt","host","minutes","restPath","seconds","toString","filterTitleStr","statusPeekText","bindLongPressPeek","isReblog","actualStatusID","isFollowedTags","setShowPeek","threshold","captureEvent","ssKey","showPeek","statusPeekRef","quotes","uniqueQuotes","arr","q2","level"],"sources":["../../src/utils/store-utils.js","../../src/utils/store.js","../../src/utils/api.js","../../src/utils/show-toast.js","../../src/utils/isMastodonLinkMaybe.jsx","../../src/utils/mem.js","../../src/utils/pmem.js","../../src/utils/ratelimit.js","../../src/utils/unfurl-link.jsx","../../src/utils/states.js","../../src/utils/useInterval.js","../../src/utils/open-compose.js","../../src/components/icon.jsx","../../src/utils/useCloseWatcher.js","../../src/components/modal.jsx","../../src/components/loader.jsx","../../src/components/avatar.jsx","../../src/components/link.jsx","../../src/utils/safe-bounding-box-padding.js","../../src/components/menu2.jsx","../../src/components/menu-confirm.jsx","../../src/components/emoji-text.jsx","../../src/components/name-text.jsx","../../src/components/relative-time.jsx","../../src/utils/locale-match.jsx","../../src/utils/get-translate-target-language.jsx","../../src/utils/localeCode2Text.jsx","../../src/utils/emojify-text.js","../../src/utils/enhance-content.js","../../src/utils/getHTMLText.jsx","../../src/utils/handle-content-links.js","../../src/utils/nice-date-time.js","../../src/utils/shorten-number.jsx","../../src/components/account-block.jsx","../../src/utils/supports.js","../../src/components/translation-block.jsx","../../src/utils/db.js","../../src/utils/visibility-icons-map.js","../../src/components/poll.jsx","../../src/utils/filter-context.js","../../src/utils/filters.jsx","../../src/utils/html-content-length.js","../../src/utils/speech.js","../../src/utils/status-peek.jsx","../../src/utils/useTruncated.js","../../src/components/media.jsx","../../src/components/menu-link.jsx","../../src/components/status.jsx","../../src/components/compose.jsx","../../src/utils/useTitle.js"],"sourcesContent":["import store from './store';\n\nexport function getAccount(id) {\n const accounts = store.local.getJSON('accounts') || [];\n return accounts.find((a) => a.info.id === id) || accounts[0];\n}\n\nexport function getAccountByAccessToken(accessToken) {\n const accounts = store.local.getJSON('accounts') || [];\n return accounts.find((a) => a.accessToken === accessToken);\n}\n\nexport function getAccountByInstance(instance) {\n const accounts = store.local.getJSON('accounts') || [];\n return accounts.find((a) => a.instanceURL === instance);\n}\n\nexport function getCurrentAccount() {\n if (!window.__IGNORE_GET_ACCOUNT_ERROR__) {\n // Track down getCurrentAccount() calls before account-based states are initialized\n console.error('getCurrentAccount() called before states are initialized');\n if (import.meta.env.DEV) console.trace();\n }\n const currentAccount = store.session.get('currentAccount');\n const account = getAccount(currentAccount);\n return account;\n}\n\nexport function getCurrentAccountNS() {\n const account = getCurrentAccount();\n const {\n instanceURL,\n info: { id },\n } = account;\n return `${id}@${instanceURL}`;\n}\n\nexport function saveAccount(account) {\n const accounts = store.local.getJSON('accounts') || [];\n const acc = accounts.find((a) => a.info.id === account.info.id);\n if (acc) {\n acc.info = account.info;\n acc.instanceURL = account.instanceURL;\n acc.accessToken = account.accessToken;\n acc.vapidKey = account.vapidKey;\n } else {\n accounts.push(account);\n }\n store.local.setJSON('accounts', accounts);\n store.session.set('currentAccount', account.info.id);\n}\n\nexport function updateAccount(accountInfo) {\n // Only update if displayName or avatar or avatar_static is different\n const accounts = store.local.getJSON('accounts') || [];\n const acc = accounts.find((a) => a.info.id === accountInfo.id);\n if (acc) {\n if (\n acc.info.displayName !== accountInfo.displayName ||\n acc.info.avatar !== accountInfo.avatar ||\n acc.info.avatar_static !== accountInfo.avatar_static\n ) {\n acc.info = {\n ...acc.info,\n ...accountInfo,\n };\n store.local.setJSON('accounts', accounts);\n }\n }\n}\n\nlet currentInstance = null;\nexport function getCurrentInstance() {\n if (currentInstance) return currentInstance;\n try {\n const account = getCurrentAccount();\n const instances = store.local.getJSON('instances');\n const instance = account.instanceURL.toLowerCase();\n return (currentInstance = instances[instance]);\n } catch (e) {\n console.error(e);\n alert(`Failed to load instance configuration. Please try again.\\n\\n${e}`);\n // Temporary fix for corrupted data\n store.local.del('instances');\n location.reload();\n return {};\n }\n}\n\n// Massage these instance configurations to match the Mastodon API\n// - Pleroma\nfunction getInstanceConfiguration(instance) {\n const {\n configuration,\n maxMediaAttachments,\n maxTootChars,\n pleroma,\n pollLimits,\n } = instance;\n\n const statuses = configuration?.statuses || {};\n if (maxMediaAttachments) {\n statuses.maxMediaAttachments ??= maxMediaAttachments;\n }\n if (maxTootChars) {\n statuses.maxCharacters ??= maxTootChars;\n }\n\n const polls = configuration?.polls || {};\n if (pollLimits) {\n polls.maxCharactersPerOption ??= pollLimits.maxOptionChars;\n polls.maxExpiration ??= pollLimits.maxExpiration;\n polls.maxOptions ??= pollLimits.maxOptions;\n polls.minExpiration ??= pollLimits.minExpiration;\n }\n\n return {\n ...configuration,\n statuses,\n polls,\n };\n}\n\nexport function getCurrentInstanceConfiguration() {\n const instance = getCurrentInstance();\n return getInstanceConfiguration(instance);\n}\n","import { getCurrentAccountNS } from './store-utils';\n\nconst local = {\n get: (key) => {\n try {\n return localStorage.getItem(key);\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n getJSON: (key) => {\n try {\n return JSON.parse(local.get(key));\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n set: (key, value) => {\n try {\n return localStorage.setItem(key, value);\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n setJSON: (key, value) => {\n try {\n return local.set(key, JSON.stringify(value));\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n del: (key) => {\n try {\n return localStorage.removeItem(key);\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n};\n\nconst session = {\n get: (key) => {\n try {\n return sessionStorage.getItem(key);\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n getJSON: (key) => {\n try {\n return JSON.parse(session.get(key));\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n set: (key, value) => {\n try {\n return sessionStorage.setItem(key, value);\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n setJSON: (key, value) => {\n try {\n return session.set(key, JSON.stringify(value));\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n del: (key) => {\n try {\n return sessionStorage.removeItem(key);\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n};\n\n// Store with account namespace (id@domain.tld) <- uses id, not username\nconst account = {\n get: (key) => {\n try {\n return local.getJSON(key)[getCurrentAccountNS()];\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n set: (key, value) => {\n try {\n const data = local.getJSON(key) || {};\n data[getCurrentAccountNS()] = value;\n return local.setJSON(key, data);\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n del: (key) => {\n try {\n const data = local.getJSON(key) || {};\n delete data[getCurrentAccountNS()];\n return local.setJSON(key, data);\n } catch (e) {\n console.warn(e);\n return null;\n }\n },\n};\n\nexport default { local, session, account };\n","import { createRestAPIClient, createStreamingAPIClient } from 'masto';\n\nimport store from './store';\nimport {\n getAccount,\n getAccountByAccessToken,\n getAccountByInstance,\n getCurrentAccount,\n saveAccount,\n} from './store-utils';\n\n// Default *fallback* instance\nconst DEFAULT_INSTANCE = 'mastodon.social';\n\n// Per-instance masto instance\n// Useful when only one account is logged in\n// I'm not sure if I'll ever allow multiple logged-in accounts but oh well...\n// E.g. apis['mastodon.social']\nconst apis = {};\n\n// Per-account masto instance\n// Note: There can be many accounts per instance\n// Useful when multiple accounts are logged in or when certain actions require a specific account\n// Just in case if I need this one day.\n// E.g. accountApis['mastodon.social']['ACCESS_TOKEN']\nconst accountApis = {};\nwindow.__ACCOUNT_APIS__ = accountApis;\n\n// Current account masto instance\nlet currentAccountApi;\n\nexport function initClient({ instance, accessToken }) {\n if (/^https?:\\/\\//.test(instance)) {\n instance = instance\n .replace(/^https?:\\/\\//, '')\n .replace(/\\/+$/, '')\n .toLowerCase();\n }\n const url = instance ? `https://${instance}` : `https://${DEFAULT_INSTANCE}`;\n\n const masto = createRestAPIClient({\n url,\n accessToken, // Can be null\n timeout: 30_000, // Unfortunatly this is global instead of per-request\n });\n\n const client = {\n masto,\n instance,\n accessToken,\n };\n apis[instance] = client;\n if (!accountApis[instance]) accountApis[instance] = {};\n if (accessToken) accountApis[instance][accessToken] = client;\n\n return client;\n}\n\n// Get the instance information\n// The config is needed for composing\nexport async function initInstance(client, instance) {\n console.log('INIT INSTANCE', client, instance);\n const { masto, accessToken } = client;\n // Request v2, fallback to v1 if fail\n let info;\n try {\n info = await masto.v2.instance.fetch();\n } catch (e) {}\n if (!info) {\n try {\n info = await masto.v1.instance.fetch();\n } catch (e) {}\n }\n if (!info) return;\n console.log(info);\n const {\n // v1\n uri,\n urls: { streamingApi } = {},\n // v2\n domain,\n configuration: { urls: { streaming } = {} } = {},\n } = info;\n const instances = store.local.getJSON('instances') || {};\n if (uri || domain) {\n instances[\n (domain || uri)\n .replace(/^https?:\\/\\//, '')\n .replace(/\\/+$/, '')\n .toLowerCase()\n ] = info;\n }\n if (instance) {\n instances[instance.toLowerCase()] = info;\n }\n store.local.setJSON('instances', instances);\n // This is a weird place to put this but here's updating the masto instance with the streaming API URL set in the configuration\n // Reason: Streaming WebSocket URL may change, unlike the standard API REST URLs\n const supportsWebSocket = 'WebSocket' in window;\n if (supportsWebSocket && (streamingApi || streaming)) {\n console.log('🎏 Streaming API URL:', streaming || streamingApi);\n // masto.config.props.streamingApiUrl = streaming || streamingApi;\n // Legacy masto.ws\n const streamClient = createStreamingAPIClient({\n streamingApiUrl: streaming || streamingApi,\n accessToken,\n implementation: WebSocket,\n });\n client.streaming = streamClient;\n // masto.ws = streamClient;\n console.log('🎏 Streaming API client:', client);\n }\n}\n\n// Get the account information and store it\nexport async function initAccount(client, instance, accessToken, vapidKey) {\n const { masto } = client;\n const mastoAccount = await masto.v1.accounts.verifyCredentials();\n\n console.log('CURRENTACCOUNT SET', mastoAccount.id);\n store.session.set('currentAccount', mastoAccount.id);\n\n saveAccount({\n info: mastoAccount,\n instanceURL: instance.toLowerCase(),\n accessToken,\n vapidKey,\n });\n}\n\n// Get preferences\nexport async function initPreferences(client) {\n try {\n const { masto } = client;\n const preferences = await masto.v1.preferences.fetch();\n store.account.set('preferences', preferences);\n } catch (e) {\n // silently fail\n console.error(e);\n }\n}\n\n// Get the masto instance\n// If accountID is provided, get the masto instance for that account\nexport function api({ instance, accessToken, accountID, account } = {}) {\n // Always lowercase and trim the instance\n if (instance) {\n instance = instance.toLowerCase().trim();\n }\n\n // If instance and accessToken are provided, get the masto instance for that account\n if (instance && accessToken) {\n const client =\n accountApis[instance]?.[accessToken] ||\n initClient({ instance, accessToken });\n const { masto, streaming } = client;\n return {\n masto,\n streaming,\n client,\n authenticated: true,\n instance,\n };\n }\n\n if (accessToken) {\n // If only accessToken is provided, get the masto instance for that accessToken\n console.log('X 1', accountApis);\n for (const instance in accountApis) {\n if (accountApis[instance][accessToken]) {\n console.log('X 2', accountApis, instance, accessToken);\n const client = accountApis[instance][accessToken];\n const { masto, streaming } = client;\n return {\n masto,\n streaming,\n client,\n authenticated: true,\n instance,\n };\n } else {\n console.log('X 3', accountApis, instance, accessToken);\n const account = getAccountByAccessToken(accessToken);\n if (account) {\n const accessToken = account.accessToken;\n const instance = account.instanceURL.toLowerCase().trim();\n const client = initClient({ instance, accessToken });\n const { masto, streaming } = client;\n return {\n masto,\n streaming,\n client,\n authenticated: true,\n instance,\n };\n } else {\n throw new Error(`Access token not found`);\n }\n }\n }\n }\n\n // If account is provided, get the masto instance for that account\n if (account || accountID) {\n account = account || getAccount(accountID);\n if (account) {\n const accessToken = account.accessToken;\n const instance = account.instanceURL.toLowerCase().trim();\n const client =\n accountApis[instance]?.[accessToken] ||\n initClient({ instance, accessToken });\n const { masto, streaming } = client;\n return {\n masto,\n streaming,\n client,\n authenticated: true,\n instance,\n };\n } else {\n throw new Error(`Account ${accountID} not found`);\n }\n }\n\n const currentAccount = getCurrentAccount();\n\n // If only instance is provided, get the masto instance for that instance\n if (instance) {\n if (currentAccountApi?.instance === instance) {\n return {\n masto: currentAccountApi.masto,\n streaming: currentAccountApi.streaming,\n client: currentAccountApi,\n authenticated: true,\n instance,\n };\n }\n\n if (currentAccount?.instanceURL === instance) {\n const { accessToken } = currentAccount;\n currentAccountApi =\n accountApis[instance]?.[accessToken] ||\n initClient({ instance, accessToken });\n return {\n masto: currentAccountApi.masto,\n streaming: currentAccountApi.streaming,\n client: currentAccountApi,\n authenticated: true,\n instance,\n };\n }\n\n const instanceAccount = getAccountByInstance(instance);\n if (instanceAccount) {\n const accessToken = instanceAccount.accessToken;\n const client =\n accountApis[instance]?.[accessToken] ||\n initClient({ instance, accessToken });\n const { masto, streaming } = client;\n return {\n masto,\n streaming,\n client,\n authenticated: true,\n instance,\n };\n }\n\n const client = apis[instance] || initClient({ instance });\n const { masto, streaming, accessToken } = client;\n return {\n masto,\n streaming,\n client,\n authenticated: !!accessToken,\n instance,\n };\n }\n\n // If no instance is provided, get the masto instance for the current account\n if (currentAccountApi) {\n return {\n masto: currentAccountApi.masto,\n streaming: currentAccountApi.streaming,\n client: currentAccountApi,\n authenticated: true,\n instance: currentAccountApi.instance,\n };\n }\n if (currentAccount) {\n const { accessToken, instanceURL: instance } = currentAccount;\n currentAccountApi =\n accountApis[instance]?.[accessToken] ||\n initClient({ instance, accessToken });\n return {\n masto: currentAccountApi.masto,\n streaming: currentAccountApi.streaming,\n client: currentAccountApi,\n authenticated: true,\n instance,\n };\n }\n\n // If no instance is provided and no account is logged in, get the masto instance for DEFAULT_INSTANCE\n const client =\n apis[DEFAULT_INSTANCE] || initClient({ instance: DEFAULT_INSTANCE });\n const { masto, streaming } = client;\n return {\n masto,\n streaming,\n client,\n authenticated: false,\n instance: DEFAULT_INSTANCE,\n };\n}\n\nwindow.__API__ = {\n currentAccountApi,\n apis,\n accountApis,\n};\n","import Toastify from 'toastify-js';\n\nwindow._showToast = showToast;\n\nfunction showToast(props) {\n if (typeof props === 'string') {\n props = { text: props };\n }\n const { onClick, delay, ...rest } = props;\n const toast = Toastify({\n className: `${onClick || props.destination ? 'shiny-pill' : ''}`,\n gravity: 'bottom',\n position: 'center',\n ...rest,\n onClick: () => {\n onClick?.(toast); // Pass in the object itself!\n },\n });\n if (delay) {\n setTimeout(() => {\n toast.showToast();\n }, delay);\n } else {\n toast.showToast();\n }\n return toast;\n}\n\nexport default showToast;\n","export default function isMastodonLinkMaybe(url) {\n try {\n const { pathname, hash } = new URL(url);\n return (\n /^\\/.*\\/\\d+$/i.test(pathname) ||\n /^\\/(@[^/]+|users\\/[^/]+)\\/(statuses|posts)\\/\\w+\\/?$/i.test(pathname) || // GoToSocial, Takahe\n /^\\/notes\\/[a-z0-9]+$/i.test(pathname) || // Misskey, Firefish\n /^\\/(notice|objects)\\/[a-z0-9-]+$/i.test(pathname) || // Pleroma\n /#\\/[^\\/]+\\.[^\\/]+\\/s\\/.+/i.test(hash) // Phanpy 🫣\n );\n } catch (e) {\n return false;\n }\n}\n","import moize from 'moize';\n\nwindow._moize = moize;\n\nexport default function mem(fn, opts = {}) {\n return moize(fn, { ...opts, maxSize: 50, isDeepEqual: true });\n}\n","import mem from './mem';\n\nexport default function pmem(fn, opts = {}) {\n return mem(fn, { isPromise: true, ...opts });\n}\n","// Rate limit repeated function calls and queue them to set interval\nexport default function rateLimit(fn, interval) {\n let queue = [];\n let isRunning = false;\n\n function executeNext() {\n if (queue.length === 0) {\n isRunning = false;\n return;\n }\n\n const nextFn = queue.shift();\n nextFn();\n setTimeout(executeNext, interval);\n }\n\n return function (...args) {\n const callFn = () => fn.apply(this, args);\n queue.push(callFn);\n\n if (!isRunning) {\n isRunning = true;\n setTimeout(executeNext, interval);\n }\n };\n}\n","import pThrottle from 'p-throttle';\nimport { snapshot } from 'valtio/vanilla';\n\nimport { api } from './api';\nimport states, { saveStatus } from './states';\n\nexport const throttle = pThrottle({\n limit: 1,\n interval: 1000,\n});\n\nconst denylistDomains = /(twitter|github)\\.com/i;\nconst failedUnfurls = {};\nfunction _unfurlMastodonLink(instance, url) {\n const snapStates = snapshot(states);\n if (denylistDomains.test(url)) {\n return;\n }\n if (failedUnfurls[url]) {\n return;\n }\n const instanceRegex = new RegExp(instance + '/');\n if (instanceRegex.test(snapStates.unfurledLinks[url]?.url)) {\n return Promise.resolve(snapStates.unfurledLinks[url]);\n }\n console.debug('🦦 Unfurling URL', url);\n\n let remoteInstanceFetch;\n let theURL = url;\n\n // https://elk.zone/domain.com/@stest/123 -> https://domain.com/@stest/123\n if (/\\/\\/elk\\.[^\\/]+\\/[^\\/]+\\.[^\\/]+/i.test(theURL)) {\n theURL = theURL.replace(/elk\\.[^\\/]+\\//i, '');\n }\n\n // https://trunks.social/status/domain.com/@stest/123 -> https://domain.com/@stest/123\n if (/\\/\\/trunks\\.[^\\/]+\\/status\\/[^\\/]+\\.[^\\/]+/i.test(theURL)) {\n theURL = theURL.replace(/trunks\\.[^\\/]+\\/status\\//i, '');\n }\n\n // https://phanpy.social/#/domain.com/s/123 -> https://domain.com/statuses/123\n if (/\\/#\\/[^\\/]+\\.[^\\/]+\\/s\\/.+/i.test(theURL)) {\n const urlAfterHash = theURL.split('/#/')[1];\n const finalURL = urlAfterHash.replace(/\\/s\\//i, '/@fakeUsername/');\n theURL = `https://${finalURL}`;\n }\n\n let urlObj;\n try {\n urlObj = new URL(theURL);\n } catch (e) {\n return;\n }\n const domain = urlObj.hostname;\n const path = urlObj.pathname;\n // Regex /:username/:id, where username = @username or @username@domain, id = number\n const statusRegex = /\\/@([^@\\/]+)@?([^\\/]+)?\\/(\\d+)$/i;\n const statusMatch = statusRegex.exec(path);\n if (statusMatch) {\n const id = statusMatch[3];\n const { masto } = api({ instance: domain });\n remoteInstanceFetch = masto.v1.statuses\n .$select(id)\n .fetch()\n .then((status) => {\n if (status?.id) {\n return {\n status,\n instance: domain,\n };\n } else {\n throw new Error('No results');\n }\n });\n }\n\n const { masto } = api({ instance });\n const mastoSearchFetch = masto.v2.search\n .fetch({\n q: theURL,\n type: 'statuses',\n resolve: true,\n limit: 1,\n })\n .then((results) => {\n if (results.statuses.length > 0) {\n const status = results.statuses[0];\n return {\n status,\n instance,\n };\n } else {\n throw new Error('No results');\n }\n });\n\n function handleFulfill(result) {\n const { status, instance } = result;\n const { id } = status;\n const selfURL = `/${instance}/s/${id}`;\n console.debug('🦦 Unfurled URL', url, id, selfURL);\n const data = {\n id,\n instance,\n url: selfURL,\n };\n states.unfurledLinks[url] = data;\n saveStatus(status, instance, {\n skipThreading: true,\n });\n return data;\n }\n function handleCatch(e) {\n failedUnfurls[url] = true;\n }\n\n if (remoteInstanceFetch) {\n // return Promise.any([remoteInstanceFetch, mastoSearchFetch])\n // .then(handleFulfill)\n // .catch(handleCatch);\n // If mastoSearchFetch is fulfilled within 3s, return it, else return remoteInstanceFetch\n const finalPromise = Promise.race([\n mastoSearchFetch,\n new Promise((resolve, reject) => setTimeout(reject, 3000)),\n ]).catch(() => {\n // If remoteInstanceFetch is fullfilled, return it, else return mastoSearchFetch\n return remoteInstanceFetch.catch(() => mastoSearchFetch);\n });\n return finalPromise.then(handleFulfill).catch(handleCatch);\n } else {\n return mastoSearchFetch.then(handleFulfill).catch(handleCatch);\n }\n}\n\nconst unfurlMastodonLink = throttle(_unfurlMastodonLink);\nexport default unfurlMastodonLink;\n","import { proxy, subscribe } from 'valtio';\nimport { subscribeKey } from 'valtio/utils';\n\nimport { api } from './api';\nimport isMastodonLinkMaybe from './isMastodonLinkMaybe';\nimport pmem from './pmem';\nimport rateLimit from './ratelimit';\nimport store from './store';\nimport unfurlMastodonLink from './unfurl-link';\n\nconst states = proxy({\n appVersion: {},\n // history: [],\n prevLocation: null,\n currentLocation: null,\n statuses: {},\n statusThreadNumber: {},\n home: [],\n // specialHome: [],\n homeNew: [],\n homeLast: null, // Last item in 'home' list\n homeLastFetchTime: null,\n notifications: [],\n notificationsLast: null, // Last read notification\n notificationsNew: [],\n notificationsShowNew: false,\n notificationsLastFetchTime: null,\n reloadStatusPage: 0,\n reloadGenericAccounts: {\n id: null,\n counter: 0,\n },\n spoilers: {},\n spoilersMedia: {},\n scrollPositions: {},\n unfurledLinks: {},\n statusQuotes: {},\n statusFollowedTags: {},\n accounts: {},\n routeNotification: null,\n // Modals\n showCompose: false,\n showSettings: false,\n showAccount: false,\n showAccounts: false,\n showDrafts: false,\n showMediaModal: false,\n showShortcutsSettings: false,\n showKeyboardShortcutsHelp: false,\n showGenericAccounts: false,\n showMediaAlt: false,\n // Shortcuts\n shortcuts: [],\n // Settings\n settings: {\n autoRefresh: false,\n shortcutsViewMode: null,\n shortcutsColumnsMode: false,\n boostsCarousel: true,\n contentTranslation: true,\n contentTranslationTargetLanguage: null,\n contentTranslationHideLanguages: [],\n contentTranslationAutoInline: false,\n mediaAltGenerator: false,\n cloakMode: false,\n },\n});\n\nexport default states;\n\nexport function initStates() {\n // init all account based states\n // all keys that uses store.account.get() should be initialized here\n states.notificationsLast = store.account.get('notificationsLast') || null;\n states.shortcuts = store.account.get('shortcuts') ?? [];\n states.settings.autoRefresh =\n store.account.get('settings-autoRefresh') ?? false;\n states.settings.shortcutsViewMode =\n store.account.get('settings-shortcutsViewMode') ?? null;\n if (store.account.get('settings-shortcutsColumnsMode')) {\n states.settings.shortcutsColumnsMode = true;\n }\n states.settings.boostsCarousel =\n store.account.get('settings-boostsCarousel') ?? true;\n states.settings.contentTranslation =\n store.account.get('settings-contentTranslation') ?? true;\n states.settings.contentTranslationTargetLanguage =\n store.account.get('settings-contentTranslationTargetLanguage') || null;\n states.settings.contentTranslationHideLanguages =\n store.account.get('settings-contentTranslationHideLanguages') || [];\n states.settings.contentTranslationAutoInline =\n store.account.get('settings-contentTranslationAutoInline') ?? false;\n states.settings.mediaAltGenerator =\n store.account.get('settings-mediaAltGenerator') ?? false;\n states.settings.cloakMode = store.account.get('settings-cloakMode') ?? false;\n}\n\nsubscribeKey(states, 'notificationsLast', (v) => {\n console.log('CHANGE', v);\n store.account.set('notificationsLast', states.notificationsLast);\n});\nsubscribe(states, (changes) => {\n console.debug('STATES change', changes);\n for (const [action, path, value, prevValue] of changes) {\n if (path.join('.') === 'settings.autoRefresh') {\n store.account.set('settings-autoRefresh', !!value);\n }\n if (path.join('.') === 'settings.boostsCarousel') {\n store.account.set('settings-boostsCarousel', !!value);\n }\n if (path.join('.') === 'settings.shortcutsViewMode') {\n store.account.set('settings-shortcutsViewMode', value);\n }\n if (path.join('.') === 'settings.contentTranslation') {\n store.account.set('settings-contentTranslation', !!value);\n }\n if (path.join('.') === 'settings.contentTranslationAutoInline') {\n store.account.set('settings-contentTranslationAutoInline', !!value);\n }\n if (path.join('.') === 'settings.contentTranslationTargetLanguage') {\n console.log('SET', value);\n store.account.set('settings-contentTranslationTargetLanguage', value);\n }\n if (/^settings\\.contentTranslationHideLanguages/i.test(path.join('.'))) {\n store.account.set(\n 'settings-contentTranslationHideLanguages',\n states.settings.contentTranslationHideLanguages,\n );\n }\n if (path.join('.') === 'settings.mediaAltGenerator') {\n store.account.set('settings-mediaAltGenerator', !!value);\n }\n if (path?.[0] === 'shortcuts') {\n store.account.set('shortcuts', states.shortcuts);\n }\n if (path.join('.') === 'settings.cloakMode') {\n store.account.set('settings-cloakMode', !!value);\n }\n }\n});\n\nexport function hideAllModals() {\n states.showCompose = false;\n states.showSettings = false;\n states.showAccount = false;\n states.showAccounts = false;\n states.showDrafts = false;\n states.showMediaModal = false;\n states.showShortcutsSettings = false;\n states.showKeyboardShortcutsHelp = false;\n states.showGenericAccounts = false;\n states.showMediaAlt = false;\n}\n\nexport function statusKey(id, instance) {\n if (!id) return;\n return instance ? `${instance}/${id}` : id;\n}\n\nexport function getStatus(statusID, instance) {\n if (instance) {\n const key = statusKey(statusID, instance);\n return states.statuses[key];\n }\n return states.statuses[statusID];\n}\n\nexport function saveStatus(status, instance, opts) {\n if (typeof instance === 'object') {\n opts = instance;\n instance = null;\n }\n const {\n override = true,\n skipThreading = false,\n skipUnfurling = false,\n } = opts || {};\n if (!status) return;\n const oldStatus = getStatus(status.id, instance);\n if (!override && oldStatus) return;\n queueMicrotask(() => {\n const key = statusKey(status.id, instance);\n if (oldStatus?._pinned) status._pinned = oldStatus._pinned;\n // if (oldStatus?._filtered) status._filtered = oldStatus._filtered;\n states.statuses[key] = status;\n if (status.reblog) {\n const key = statusKey(status.reblog.id, instance);\n states.statuses[key] = status.reblog;\n }\n });\n\n // THREAD TRAVERSER\n if (!skipThreading) {\n queueMicrotask(() => {\n threadifyStatus(status, instance);\n if (status.reblog) {\n queueMicrotask(() => {\n threadifyStatus(status.reblog, instance);\n });\n }\n });\n }\n\n // UNFURLER\n if (!skipUnfurling) {\n queueMicrotask(() => {\n unfurlStatus(status, instance);\n });\n }\n}\n\nfunction _threadifyStatus(status, propInstance) {\n const { masto, instance } = api({ instance: propInstance });\n // Return all statuses in the thread, via inReplyToId, if inReplyToAccountId === account.id\n let fetchIndex = 0;\n async function traverse(status, index = 0) {\n const { inReplyToId, inReplyToAccountId } = status;\n if (!inReplyToId || inReplyToAccountId !== status.account.id) {\n return [status];\n }\n if (inReplyToId && inReplyToAccountId !== status.account.id) {\n throw 'Not a thread';\n // Possibly thread of replies by multiple people?\n }\n const key = statusKey(inReplyToId, instance);\n let prevStatus = states.statuses[key];\n if (!prevStatus) {\n if (fetchIndex++ > 3) throw 'Too many fetches for thread'; // Some people revive old threads\n await new Promise((r) => setTimeout(r, 500 * fetchIndex)); // Be nice to rate limits\n // prevStatus = await masto.v1.statuses.$.select(inReplyToId).fetch();\n prevStatus = await fetchStatus(inReplyToId, masto);\n saveStatus(prevStatus, instance, { skipThreading: true });\n }\n // Prepend so that first status in thread will be index 0\n return [...(await traverse(prevStatus, ++index)), status];\n }\n return traverse(status)\n .then((statuses) => {\n if (statuses.length > 1) {\n console.debug('THREAD', statuses);\n statuses.forEach((status, index) => {\n const key = statusKey(status.id, instance);\n states.statusThreadNumber[key] = index + 1;\n });\n }\n })\n .catch((e) => {\n console.error(e, status);\n });\n}\nexport const threadifyStatus = rateLimit(_threadifyStatus, 100);\n\nconst fauxDiv = document.createElement('div');\nexport function unfurlStatus(status, instance) {\n const { instance: currentInstance } = api();\n const content = status.reblog?.content || status.content;\n const hasLink = /<a/i.test(content);\n if (hasLink) {\n const sKey = statusKey(status?.reblog?.id || status?.id, instance);\n fauxDiv.innerHTML = content;\n const links = fauxDiv.querySelectorAll(\n 'a[href]:not(.u-url):not(.mention):not(.hashtag)',\n );\n [...links]\n .filter((a) => {\n const url = a.href;\n const isPostItself = url === status.url || url === status.uri;\n return !isPostItself && isMastodonLinkMaybe(url);\n })\n .forEach((a, i) => {\n unfurlMastodonLink(currentInstance, a.href).then((result) => {\n if (!result) return;\n if (!sKey) return;\n if (!Array.isArray(states.statusQuotes[sKey])) {\n states.statusQuotes[sKey] = [];\n }\n if (!states.statusQuotes[sKey][i]) {\n states.statusQuotes[sKey].splice(i, 0, result);\n }\n });\n });\n }\n}\n\nconst fetchStatus = pmem((statusID, masto) => {\n return masto.v1.statuses.$select(statusID).fetch();\n});\n","import { useEffect, useRef } from 'preact/hooks';\n\nfunction useInterval(fn, delay, deps, immediate) {\n const savedCallback = useRef(fn);\n useEffect(() => {\n savedCallback.current = fn;\n }, [fn, deps]);\n\n useEffect(() => {\n if (!immediate || delay === null || delay === false) return;\n savedCallback.current();\n }, [immediate]);\n\n useEffect(() => {\n if (delay === null || delay === false) return;\n const tick = () => savedCallback.current();\n const id = setInterval(tick, delay);\n return () => clearInterval(id);\n }, [delay]);\n}\n\nexport default useInterval;\n","export default function openCompose(opts) {\n const url = new URL('/compose/', window.location);\n const { width: screenWidth, height: screenHeight } = window.screen;\n const left = Math.max(0, (screenWidth - 600) / 2);\n const top = Math.max(0, (screenHeight - 450) / 2);\n const width = Math.min(screenWidth, 600);\n const height = Math.min(screenHeight, 450);\n const winUID = opts?.uid || Math.random();\n const newWin = window.open(\n url,\n 'compose' + winUID,\n `width=${width},height=${height},left=${left},top=${top}`,\n );\n\n if (newWin) {\n // if (masto) {\n // newWin.masto = masto;\n // }\n\n newWin.__COMPOSE__ = opts;\n } else {\n alert('Looks like your browser is blocking popups.');\n }\n\n return newWin;\n}\n","import { useEffect, useRef, useState } from 'preact/hooks';\n\nconst SIZES = {\n s: 12,\n m: 16,\n l: 20,\n xl: 24,\n xxl: 32,\n};\n\nexport const ICONS = {\n x: () => import('@iconify-icons/mingcute/close-line'),\n heart: () => import('@iconify-icons/mingcute/heart-line'),\n bookmark: () => import('@iconify-icons/mingcute/bookmark-line'),\n 'check-circle': () => import('@iconify-icons/mingcute/check-circle-line'),\n 'x-circle': () => import('@iconify-icons/mingcute/close-circle-line'),\n transfer: () => import('@iconify-icons/mingcute/transfer-4-line'),\n rocket: () => import('@iconify-icons/mingcute/rocket-line'),\n 'arrow-left': () => import('@iconify-icons/mingcute/arrow-left-line'),\n 'arrow-right': () => import('@iconify-icons/mingcute/arrow-right-line'),\n 'arrow-up': () => import('@iconify-icons/mingcute/arrow-up-line'),\n 'arrow-down': () => import('@iconify-icons/mingcute/arrow-down-line'),\n earth: () => import('@iconify-icons/mingcute/earth-line'),\n lock: () => import('@iconify-icons/mingcute/lock-line'),\n unlock: () => import('@iconify-icons/mingcute/unlock-line'),\n 'eye-close': () => import('@iconify-icons/mingcute/eye-close-line'),\n 'eye-open': () => import('@iconify-icons/mingcute/eye-2-line'),\n message: () => import('@iconify-icons/mingcute/mail-line'),\n comment: () => import('@iconify-icons/mingcute/chat-3-line'),\n comment2: () => import('@iconify-icons/mingcute/comment-2-line'),\n home: () => import('@iconify-icons/mingcute/home-3-line'),\n notification: () => import('@iconify-icons/mingcute/notification-line'),\n follow: () => import('@iconify-icons/mingcute/user-follow-line'),\n 'follow-add': () => import('@iconify-icons/mingcute/user-add-line'),\n poll: [() => import('@iconify-icons/mingcute/chart-bar-line'), '90deg'],\n pencil: () => import('@iconify-icons/mingcute/pencil-line'),\n quill: () => import('@iconify-icons/mingcute/quill-pen-line'),\n at: () => import('@iconify-icons/mingcute/at-line'),\n attachment: () => import('@iconify-icons/mingcute/attachment-line'),\n upload: () => import('@iconify-icons/mingcute/upload-3-line'),\n gear: () => import('@iconify-icons/mingcute/settings-3-line'),\n more: () => import('@iconify-icons/mingcute/more-3-line'),\n external: () => import('@iconify-icons/mingcute/external-link-line'),\n popout: () => import('@iconify-icons/mingcute/external-link-line'),\n popin: [() => import('@iconify-icons/mingcute/external-link-line'), '180deg'],\n plus: () => import('@iconify-icons/mingcute/add-circle-line'),\n 'chevron-left': () => import('@iconify-icons/mingcute/left-line'),\n 'chevron-right': () => import('@iconify-icons/mingcute/right-line'),\n 'chevron-down': () => import('@iconify-icons/mingcute/down-line'),\n reply: [\n () => import('@iconify-icons/mingcute/share-forward-line'),\n '180deg',\n 'horizontal',\n ],\n thread: () => import('@iconify-icons/mingcute/route-line'),\n group: () => import('@iconify-icons/mingcute/group-line'),\n bot: () => import('@iconify-icons/mingcute/android-2-line'),\n menu: () => import('@iconify-icons/mingcute/rows-4-line'),\n list: () => import('@iconify-icons/mingcute/list-check-line'),\n search: () => import('@iconify-icons/mingcute/search-2-line'),\n hashtag: () => import('@iconify-icons/mingcute/hashtag-line'),\n info: () => import('@iconify-icons/mingcute/information-line'),\n shortcut: () => import('@iconify-icons/mingcute/lightning-line'),\n user: () => import('@iconify-icons/mingcute/user-4-line'),\n following: () => import('@iconify-icons/mingcute/walk-line'),\n pin: () => import('@iconify-icons/mingcute/pin-line'),\n bus: () => import('@iconify-icons/mingcute/bus-2-line'),\n link: () => import('@iconify-icons/mingcute/link-2-line'),\n history: () => import('@iconify-icons/mingcute/history-line'),\n share: () => import('@iconify-icons/mingcute/share-2-line'),\n sparkles: () => import('@iconify-icons/mingcute/sparkles-line'),\n sparkles2: () => import('@iconify-icons/mingcute/sparkles-2-line'),\n exit: () => import('@iconify-icons/mingcute/exit-line'),\n translate: () => import('@iconify-icons/mingcute/translate-line'),\n play: () => import('@iconify-icons/mingcute/play-fill'),\n trash: () => import('@iconify-icons/mingcute/delete-2-line'),\n mute: () => import('@iconify-icons/mingcute/volume-mute-line'),\n unmute: () => import('@iconify-icons/mingcute/volume-line'),\n block: () => import('@iconify-icons/mingcute/forbid-circle-line'),\n unblock: [\n () => import('@iconify-icons/mingcute/forbid-circle-line'),\n '180deg',\n ],\n flag: () => import('@iconify-icons/mingcute/flag-4-line'),\n time: () => import('@iconify-icons/mingcute/time-line'),\n refresh: () => import('@iconify-icons/mingcute/refresh-2-line'),\n emoji2: () => import('@iconify-icons/mingcute/emoji-2-line'),\n filter: () => import('@iconify-icons/mingcute/filter-2-line'),\n chart: () => import('@iconify-icons/mingcute/chart-line-line'),\n react: () => import('@iconify-icons/mingcute/react-line'),\n layout4: () => import('@iconify-icons/mingcute/layout-4-line'),\n layout5: () => import('@iconify-icons/mingcute/layout-5-line'),\n announce: () => import('@iconify-icons/mingcute/announcement-line'),\n alert: () => import('@iconify-icons/mingcute/alert-line'),\n round: () => import('@iconify-icons/mingcute/round-fill'),\n 'arrow-up-circle': () =>\n import('@iconify-icons/mingcute/arrow-up-circle-line'),\n 'arrow-down-circle': () =>\n import('@iconify-icons/mingcute/arrow-down-circle-line'),\n clipboard: () => import('@iconify-icons/mingcute/clipboard-line'),\n 'account-edit': () => import('@iconify-icons/mingcute/user-edit-line'),\n 'account-warning': () => import('@iconify-icons/mingcute/user-warning-line'),\n keyboard: () => import('@iconify-icons/mingcute/keyboard-line'),\n cloud: () => import('@iconify-icons/mingcute/cloud-line'),\n month: () => import('@iconify-icons/mingcute/calendar-month-line'),\n media: () => import('@iconify-icons/mingcute/photo-album-line'),\n speak: () => import('@iconify-icons/mingcute/radar-line'),\n building: () => import('@iconify-icons/mingcute/building-5-line'),\n};\n\nconst ICONDATA = {};\n\nfunction Icon({\n icon,\n size = 'm',\n alt,\n title,\n class: className = '',\n style = {},\n}) {\n if (!icon) return null;\n\n const iconSize = SIZES[size];\n let iconBlock = ICONS[icon];\n let rotate, flip;\n if (Array.isArray(iconBlock)) {\n [iconBlock, rotate, flip] = iconBlock;\n }\n\n const [iconData, setIconData] = useState(ICONDATA[icon]);\n const currentIcon = useRef(icon);\n useEffect(() => {\n if (iconData && currentIcon.current === icon) return;\n (async () => {\n const iconB = await iconBlock();\n setIconData(iconB.default);\n ICONDATA[icon] = iconB.default;\n })();\n currentIcon.current = icon;\n }, [icon]);\n\n return (\n <span\n class={`icon ${className}`}\n title={title || alt}\n style={{\n width: `${iconSize}px`,\n height: `${iconSize}px`,\n ...style,\n }}\n >\n {iconData && (\n <svg\n width={iconSize}\n height={iconSize}\n viewBox={`0 0 ${iconData.width} ${iconData.height}`}\n dangerouslySetInnerHTML={{ __html: iconData.body }}\n style={{\n transform: `${rotate ? `rotate(${rotate})` : ''} ${\n flip ? `scaleX(-1)` : ''\n }`,\n }}\n />\n )}\n </span>\n );\n}\n\nexport default Icon;\n","import { useEffect } from 'preact/hooks';\n\nfunction useCloseWatcher(fn, deps = []) {\n if (!fn || typeof fn !== 'function') return;\n useEffect(() => {\n const watcher = new CloseWatcher();\n watcher.addEventListener('close', fn);\n return () => {\n watcher.destroy();\n };\n }, deps);\n}\n\nexport default window.CloseWatcher ? useCloseWatcher : () => {};\n","import './modal.css';\n\nimport { createPortal } from 'preact/compat';\nimport { useEffect, useRef } from 'preact/hooks';\nimport { useHotkeys } from 'react-hotkeys-hook';\n\nimport useCloseWatcher from '../utils/useCloseWatcher';\n\nconst $modalContainer = document.getElementById('modal-container');\n\nfunction Modal({ children, onClose, onClick, class: className }) {\n if (!children) return null;\n\n const modalRef = useRef();\n useEffect(() => {\n let timer = setTimeout(() => {\n const focusElement = modalRef.current?.querySelector('[tabindex=\"-1\"]');\n if (focusElement) {\n focusElement.focus();\n }\n }, 100);\n return () => clearTimeout(timer);\n }, []);\n\n const supportsCloseWatcher = window.CloseWatcher;\n const escRef = useHotkeys(\n 'esc',\n () => {\n setTimeout(() => {\n onClose?.();\n }, 0);\n },\n {\n enabled: !supportsCloseWatcher && !!onClose,\n // Using keyup and setTimeout above\n // This will run \"later\" to prevent clash with esc handlers from other components\n keydown: false,\n keyup: true,\n },\n [onClose],\n );\n useCloseWatcher(onClose, [onClose]);\n\n const Modal = (\n <div\n ref={(node) => {\n modalRef.current = node;\n escRef.current = node?.querySelector?.('[tabindex=\"-1\"]') || node;\n }}\n className={className}\n onClick={(e) => {\n onClick?.(e);\n if (e.target === e.currentTarget) {\n onClose?.(e);\n }\n }}\n tabIndex=\"-1\"\n onFocus={(e) => {\n if (e.target === e.currentTarget) {\n modalRef.current?.querySelector?.('[tabindex=\"-1\"]')?.focus?.();\n }\n }}\n >\n {children}\n </div>\n );\n\n return createPortal(Modal, $modalContainer);\n\n // return createPortal(children, $modalContainer);\n}\n\nexport default Modal;\n","import './loader.css';\n\nfunction Loader({ abrupt, hidden, ...props }) {\n return (\n <span\n {...props}\n class={`loader-container ${abrupt ? 'abrupt' : ''} ${\n hidden ? 'hidden' : ''\n }`}\n >\n <span class=\"loader\" />\n </span>\n );\n}\n\nexport default Loader;\n","import './avatar.css';\n\nimport { useRef } from 'preact/hooks';\n\nimport mem from '../utils/mem';\n\nconst SIZES = {\n s: 16,\n m: 20,\n l: 24,\n xl: 32,\n xxl: 50,\n xxxl: 64,\n};\n\nconst alphaCache = {};\n\nconst canvas = window.OffscreenCanvas\n ? new OffscreenCanvas(1, 1)\n : document.createElement('canvas');\nconst ctx = canvas.getContext('2d', {\n willReadFrequently: true,\n});\n\nfunction Avatar({ url, size, alt = '', squircle, ...props }) {\n size = SIZES[size] || size || SIZES.m;\n const avatarRef = useRef();\n const isMissing = /missing\\.png$/.test(url);\n return (\n <span\n ref={avatarRef}\n class={`avatar ${squircle ? 'squircle' : ''} ${\n alphaCache[url] ? 'has-alpha' : ''\n }`}\n style={{\n width: size,\n height: size,\n }}\n title={alt}\n {...props}\n >\n {!!url && (\n <img\n src={url}\n width={size}\n height={size}\n alt={alt}\n loading=\"lazy\"\n decoding=\"async\"\n crossOrigin={\n alphaCache[url] === undefined && !isMissing\n ? 'anonymous'\n : undefined\n }\n onError={(e) => {\n if (e.target.crossOrigin) {\n e.target.crossOrigin = null;\n e.target.src = url;\n }\n }}\n onLoad={(e) => {\n if (avatarRef.current) avatarRef.current.dataset.loaded = true;\n if (alphaCache[url] !== undefined) return;\n if (isMissing) return;\n queueMicrotask(() => {\n try {\n // Check if image has alpha channel\n const { width, height } = e.target;\n if (canvas.width !== width) canvas.width = width;\n if (canvas.height !== height) canvas.height = height;\n ctx.drawImage(e.target, 0, 0);\n const allPixels = ctx.getImageData(0, 0, width, height);\n // At least 10% of pixels have alpha <= 128\n const hasAlpha =\n allPixels.data.filter(\n (pixel, i) => i % 4 === 3 && pixel <= 128,\n ).length /\n (allPixels.data.length / 4) >\n 0.1;\n if (hasAlpha) {\n // console.log('hasAlpha', hasAlpha, allPixels.data);\n avatarRef.current.classList.add('has-alpha');\n }\n alphaCache[url] = hasAlpha;\n ctx.clearRect(0, 0, width, height);\n } catch (e) {\n // Silent fail\n alphaCache[url] = false;\n }\n });\n }}\n />\n )}\n </span>\n );\n}\n\nexport default mem(Avatar);\n","import { forwardRef } from 'preact/compat';\nimport { useLocation } from 'react-router-dom';\n\nimport states from '../utils/states';\n\n/* NOTES\n =====\n Initially this uses <NavLink> from react-router-dom, but it doesn't work:\n 1. It interferes with nested <a> inside <a> and it's difficult to preventDefault/stopPropagation from the nested <a>\n 2. isActive doesn't work properly with the weird routes that's set up in this app, due to the faux \"location\" to make the modals work and prevent unmounting\n 3. Not using <Link state/> because it modifies history.state that *persists* across page reloads. I don't need that, so using valtio's states instead.\n*/\n\nconst Link = forwardRef((props, ref) => {\n let routerLocation;\n try {\n routerLocation = useLocation();\n } catch (e) {}\n let hash = (location.hash || '').replace(/^#/, '').trim();\n if (hash === '') hash = '/';\n const { to, ...restProps } = props;\n\n // Handle encodeURIComponent of searchParams values\n if (!!hash && hash !== '/' && hash.includes('?')) {\n try {\n const parsedHash = new URL(hash, location.origin); // Fake base URL\n if (parsedHash.searchParams.size) {\n const searchParamsStr = Array.from(parsedHash.searchParams.entries())\n .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)\n .join('&');\n hash = parsedHash.pathname + '?' + searchParamsStr;\n }\n } catch (e) {}\n }\n\n const isActive = hash === to || decodeURIComponent(hash) === to;\n return (\n <a\n ref={ref}\n href={`#${to}`}\n {...restProps}\n class={`${props.class || ''} ${isActive ? 'is-active' : ''}`}\n onClick={(e) => {\n if (e.currentTarget?.parentNode?.closest('a')) {\n // If this <a> is nested inside another <a>\n e.stopPropagation();\n }\n if (routerLocation) states.prevLocation = routerLocation;\n props.onClick?.(e);\n }}\n />\n );\n});\n\nexport default Link;\n","const root = document.documentElement;\nconst style = getComputedStyle(root);\nconst defaultBoundingBoxPadding = 8;\n\nlet safeAreaInsets = [0, 0, 0, 0];\nfunction getSafeAreaInsets() {\n // Get safe area inset variables from root\n const safeAreaInsetTop = style.getPropertyValue('--sai-top');\n const safeAreaInsetRight = style.getPropertyValue('--sai-right');\n const safeAreaInsetBottom = style.getPropertyValue('--sai-bottom');\n const safeAreaInsetLeft = style.getPropertyValue('--sai-left');\n safeAreaInsets = [\n // top, right, bottom, left (clockwise)\n Math.max(0, parseInt(safeAreaInsetTop, 10)),\n Math.max(0, parseInt(safeAreaInsetRight, 10)),\n Math.max(0, parseInt(safeAreaInsetBottom, 10)),\n Math.max(0, parseInt(safeAreaInsetLeft, 10)),\n ];\n}\nrequestAnimationFrame(getSafeAreaInsets);\n\nfunction safeBoundingBoxPadding(paddings = []) {\n const str = safeAreaInsets\n .map((v, i) => (v || defaultBoundingBoxPadding) + (paddings[i] || 0))\n .join(' ');\n // console.log(str);\n return str;\n}\n\n// Update safe area insets when orientation or resize\nif (CSS.supports('top: env(safe-area-inset-top)')) {\n window.addEventListener('resize', getSafeAreaInsets, { passive: true });\n}\n\nexport default safeBoundingBoxPadding;\n","import { Menu } from '@szhsin/react-menu';\nimport { useWindowSize } from '@uidotdev/usehooks';\nimport { useRef } from 'preact/hooks';\n\nimport safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';\n\n// It's like Menu but with sensible defaults, bug fixes and improvements.\nfunction Menu2(props) {\n const { containerProps, instanceRef: _instanceRef } = props;\n const size = useWindowSize();\n const instanceRef = _instanceRef?.current ? _instanceRef : useRef();\n\n return (\n <Menu\n boundingBoxPadding={safeBoundingBoxPadding()}\n repositionFlag={`${size.width}x${size.height}`}\n unmountOnClose\n {...props}\n instanceRef={instanceRef}\n containerProps={{\n onClick: (e) => {\n if (e.target === e.currentTarget) {\n instanceRef.current?.closeMenu?.();\n }\n containerProps?.onClick?.(e);\n },\n ...containerProps,\n }}\n />\n );\n}\n\nexport default Menu2;\n","import { Menu, MenuItem, SubMenu } from '@szhsin/react-menu';\nimport { cloneElement } from 'preact';\nimport { useRef } from 'preact/hooks';\n\nimport Menu2 from './menu2';\n\nfunction MenuConfirm({\n subMenu = false,\n confirm = true,\n confirmLabel,\n menuItemClassName,\n menuFooter,\n ...props\n}) {\n const { children, onClick, ...restProps } = props;\n if (!confirm) {\n if (subMenu) return <MenuItem {...props} />;\n if (onClick) {\n return cloneElement(children, {\n onClick,\n });\n }\n return children;\n }\n const Parent = subMenu ? SubMenu : Menu2;\n const menuRef = useRef();\n return (\n <Parent\n instanceRef={menuRef}\n openTrigger=\"clickOnly\"\n direction=\"bottom\"\n overflow=\"auto\"\n gap={-8}\n shift={8}\n menuClassName=\"menu-emphasized\"\n {...restProps}\n menuButton={subMenu ? undefined : children}\n label={subMenu ? children : undefined}\n // Test fix for bug; submenus not opening on Android\n itemProps={{\n onPointerMove: (e) => {\n if (e.pointerType === 'touch') {\n menuRef.current?.openMenu?.();\n }\n },\n onPointerLeave: (e) => {\n if (e.pointerType === 'touch') {\n menuRef.current?.openMenu?.();\n }\n },\n }}\n >\n <MenuItem className={menuItemClassName} onClick={onClick}>\n {confirmLabel}\n </MenuItem>\n {menuFooter}\n </Parent>\n );\n}\n\nexport default MenuConfirm;\n","function EmojiText({ text, emojis }) {\n if (!text) return '';\n if (!emojis?.length) return text;\n if (text.indexOf(':') === -1) return text;\n const regex = new RegExp(\n `:(${emojis.map((e) => e.shortcode).join('|')}):`,\n 'g',\n );\n const elements = text.split(regex).map((word) => {\n const emoji = emojis.find((e) => e.shortcode === word);\n if (emoji) {\n const { url, staticUrl } = emoji;\n return (\n <picture>\n <source srcset={staticUrl} media=\"(prefers-reduced-motion: reduce)\" />\n <img\n key={word}\n src={url}\n alt={word}\n class=\"shortcode-emoji emoji\"\n width=\"16\"\n height=\"16\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n </picture>\n );\n }\n return word;\n });\n return elements;\n}\n\nexport default EmojiText;\n","import './name-text.css';\n\nimport { memo } from 'preact/compat';\n\nimport states from '../utils/states';\n\nimport Avatar from './avatar';\nimport EmojiText from './emoji-text';\n\nconst nameCollator = new Intl.Collator('en', {\n sensitivity: 'base',\n});\n\nfunction NameText({\n account,\n instance,\n showAvatar,\n showAcct,\n short,\n external,\n onClick,\n}) {\n const { acct, avatar, avatarStatic, id, url, displayName, emojis, bot } =\n account;\n let { username } = account;\n const [_, acct1, acct2] = acct.match(/([^@]+)(@.+)/i) || [, acct];\n\n const trimmedUsername = username.toLowerCase().trim();\n const trimmedDisplayName = (displayName || '').toLowerCase().trim();\n const shortenedDisplayName = trimmedDisplayName\n .replace(/(\\:(\\w|\\+|\\-)+\\:)(?=|[\\!\\.\\?]|$)/g, '') // Remove shortcodes, regex from https://regex101.com/r/iE9uV0/1\n .replace(/\\s+/g, ''); // E.g. \"My name\" === \"myname\"\n const shortenedAlphaNumericDisplayName = shortenedDisplayName.replace(\n /[^a-z0-9]/gi,\n '',\n ); // Remove non-alphanumeric characters\n\n if (\n !short &&\n (trimmedUsername === trimmedDisplayName ||\n trimmedUsername === shortenedDisplayName ||\n trimmedUsername === shortenedAlphaNumericDisplayName ||\n nameCollator.compare(trimmedUsername, shortenedDisplayName) === 0)\n ) {\n username = null;\n }\n\n return (\n <a\n class={`name-text ${showAcct ? 'show-acct' : ''} ${short ? 'short' : ''}`}\n href={url}\n target={external ? '_blank' : null}\n title={`${displayName ? `${displayName} ` : ''}@${acct}`}\n onClick={(e) => {\n if (external) return;\n e.preventDefault();\n e.stopPropagation();\n if (onClick) return onClick(e);\n states.showAccount = {\n account,\n instance,\n };\n }}\n >\n {showAvatar && (\n <>\n <Avatar url={avatarStatic || avatar} squircle={bot} />{' '}\n </>\n )}\n {displayName && !short ? (\n <>\n <b>\n <EmojiText text={displayName} emojis={emojis} />\n </b>\n {!showAcct && username && (\n <>\n {' '}\n <i>@{username}</i>\n </>\n )}\n </>\n ) : short ? (\n <i>{username}</i>\n ) : (\n <b>{username}</b>\n )}\n {showAcct && (\n <>\n <br />\n <i>\n @{acct1}\n <span class=\"ib\">{acct2}</span>\n </i>\n </>\n )}\n </a>\n );\n}\n\nexport default memo(NameText);\n","// Twitter-style relative time component\n// Seconds = 1s\n// Minutes = 1m\n// Hours = 1h\n// Days = 1d\n// After 7 days, use DD/MM/YYYY or MM/DD/YYYY\nimport dayjs from 'dayjs';\nimport dayjsTwitter from 'dayjs-twitter';\nimport localizedFormat from 'dayjs/plugin/localizedFormat';\nimport relativeTime from 'dayjs/plugin/relativeTime';\n\ndayjs.extend(dayjsTwitter);\ndayjs.extend(localizedFormat);\ndayjs.extend(relativeTime);\n\nconst dtf = new Intl.DateTimeFormat();\n\nexport default function RelativeTime({ datetime, format }) {\n if (!datetime) return null;\n const date = dayjs(datetime);\n let dateStr;\n if (format === 'micro') {\n // If date <= 1 day ago or day is within this year\n const now = dayjs();\n const dayDiff = now.diff(date, 'day');\n if (dayDiff <= 1 || now.year() === date.year()) {\n dateStr = date.twitter();\n } else {\n dateStr = dtf.format(date.toDate());\n }\n } else {\n dateStr = date.fromNow();\n }\n\n return (\n <time datetime={date.toISOString()} title={date.format('LLLL')}>\n {dateStr}\n </time>\n );\n}\n","import { match } from '@formatjs/intl-localematcher';\n\nimport mem from './mem';\n\nfunction _localeMatch(...args) {\n // Wrap in try/catch because localeMatcher throws on invalid locales\n try {\n return match(...args);\n } catch (e) {\n const defaultLocale = args[2];\n return defaultLocale || false;\n }\n}\nconst localeMatch = mem(_localeMatch);\n\nexport default localeMatch;\n","import translationTargetLanguages from '../data/lingva-target-languages';\n\nimport localeMatch from './locale-match';\nimport states from './states';\n\nconst locales = [\n new Intl.DateTimeFormat().resolvedOptions().locale,\n ...navigator.languages,\n];\n\nconst localeTargetLanguages = localeMatch(\n locales,\n translationTargetLanguages.map((l) => l.code.replace('_', '-')), // The underscore will fail Intl.Locale inside `match`\n 'en',\n);\n\nfunction getTranslateTargetLanguage(fromSettings = false) {\n if (fromSettings) {\n const { contentTranslationTargetLanguage } = states.settings;\n if (contentTranslationTargetLanguage) {\n return contentTranslationTargetLanguage;\n }\n }\n return localeTargetLanguages;\n}\n\nexport default getTranslateTargetLanguage;\n","export default function localeCode2Text(code) {\n try {\n return new Intl.DisplayNames(navigator.languages, {\n type: 'language',\n }).of(code);\n } catch (e) {\n console.error(e);\n return null;\n }\n}\n","function emojifyText(text, emojis = []) {\n if (!text) return '';\n if (!emojis.length) return text;\n if (text.indexOf(':') === -1) return text;\n // Replace shortcodes in text with emoji\n // emojis = [{ shortcode: 'smile', url: 'https://example.com/emoji.png' }]\n emojis.forEach((emoji) => {\n const { shortcode, staticUrl, url } = emoji;\n text = text.replace(\n new RegExp(`:${shortcode}:`, 'g'),\n `<picture><source srcset=\"${staticUrl}\" media=\"(prefers-reduced-motion: reduce)\"></source><img class=\"shortcode-emoji emoji\" src=\"${url}\" alt=\":${shortcode}:\" width=\"16\" height=\"16\" loading=\"lazy\" decoding=\"async\" /></picture>`,\n );\n });\n // console.log(text, emojis);\n return text;\n}\n\nexport default emojifyText;\n","import emojifyText from './emojify-text';\nimport mem from './mem';\n\nconst fauxDiv = document.createElement('div');\nconst whitelistLinkClasses = ['u-url', 'mention', 'hashtag'];\n\nfunction _enhanceContent(content, opts = {}) {\n const { emojis, postEnhanceDOM = () => {} } = opts;\n let enhancedContent = content;\n const dom = document.createElement('div');\n dom.innerHTML = enhancedContent;\n const hasLink = /<a/i.test(enhancedContent);\n const hasCodeBlock = enhancedContent.includes('```');\n\n if (hasLink) {\n // Add target=\"_blank\" to all links with no target=\"_blank\"\n // E.g. `note` in `account`\n const noTargetBlankLinks = dom.querySelectorAll('a:not([target=\"_blank\"])');\n noTargetBlankLinks.forEach((link) => {\n link.setAttribute('target', '_blank');\n });\n\n // Remove all classes except `u-url`, `mention`, `hashtag`\n const links = dom.querySelectorAll('a[class]');\n links.forEach((link) => {\n link.classList.forEach((c) => {\n if (!whitelistLinkClasses.includes(c)) {\n link.classList.remove(c);\n }\n });\n });\n }\n\n // Add 'has-url-text' to all links that contains a url\n if (hasLink) {\n const links = dom.querySelectorAll('a[href]');\n links.forEach((link) => {\n if (/^https?:\\/\\//i.test(link.textContent.trim())) {\n link.classList.add('has-url-text');\n }\n });\n }\n\n // Spanify un-spanned mentions\n if (hasLink) {\n const links = dom.querySelectorAll('a[href]');\n const usernames = [];\n links.forEach((link) => {\n const text = link.innerText.trim();\n const hasChildren = link.querySelector('*');\n // If text looks like @username@domain, then it's a mention\n if (/^@[^@]+(@[^@]+)?$/g.test(text)) {\n // Only show @username\n const [_, username, domain] = text.split('@');\n if (!hasChildren) {\n if (\n !usernames.some(([u]) => u === username) ||\n usernames.some(([u, d]) => u === username && d === domain)\n ) {\n link.innerHTML = `@<span>${username}</span>`;\n usernames.push([username, domain]);\n } else {\n link.innerHTML = `@<span>${username}@${domain}</span>`;\n }\n }\n link.classList.add('mention');\n }\n // If text looks like #hashtag, then it's a hashtag\n if (/^#[^#]+$/g.test(text)) {\n if (!hasChildren) link.innerHTML = `#<span>${text.slice(1)}</span>`;\n link.classList.add('mention', 'hashtag');\n }\n });\n }\n\n // EMOJIS\n // ======\n // Convert :shortcode: to <img />\n let textNodes;\n if (enhancedContent.includes(':')) {\n textNodes = extractTextNodes(dom);\n textNodes.forEach((node) => {\n let html = node.nodeValue\n .replace(/&/g, '&')\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-zvzl1Hla.js"} |