diff --git a/404.html b/404.html index 313e879..3fc3468 100644 --- a/404.html +++ b/404.html @@ -1,4 +1,4 @@ - + @@ -11,8 +11,16 @@ +
+ + + + + + + + +
+ `,this.domRefFrame=e.querySelector(`#frame`),this.domRefImg={fallback:e.querySelector(`#fallbackPlaceholder`),webp:e.querySelector(`#webpPlaceholder`),jpeg:e.querySelector(`#jpegPlaceholder`)},this.domRefPlayButton=e.querySelector(`#playButton`)}setupComponent(){this.shadowRoot.querySelector(`slot[name=image]`).assignedNodes().length===0&&this.initImagePlaceholder(),this.domRefPlayButton.setAttribute(`aria-label`,`${this.videoPlay}: ${this.videoTitle}`),this.setAttribute(`title`,`${this.videoPlay}: ${this.videoTitle}`),(this.autoLoad||this.isYouTubeShort()||this.autoPause)&&this.initIntersectionObserver(),this.disableNoscript||this.injectSearchNoScript()}attributeChangedCallback(e,t,n){t!==n&&(e===`playlistid`&&t!==null&&t!==n&&(this.isPlaylistThumbnailLoaded=!1),this.setupComponent(),this.domRefFrame.classList.contains(`activated`)&&(this.domRefFrame.classList.remove(`activated`),this.shadowRoot.querySelector(`iframe`).remove(),this.isIframeLoaded=!1))}injectSearchNoScript(){let e=document.createElement(`noscript`);this.prepend(e),e.innerHTML=this.generateIframe()}generateIframe(e=!1){let t=+!e,n=this.autoPause?`&enablejsapi=1`:``,r=this.noCookie?`-nocookie`:``,i;return i=this.playlistId?`?listType=playlist&list=${this.playlistId}&`:`${this.videoId}?`,this.isYouTubeShort()&&(this.params=`loop=1&mute=1&modestbranding=1&playsinline=1&rel=0&enablejsapi=1&playlist=${this.videoId}`,t=1),` +`}addIframe(e=!1){if(!this.isIframeLoaded){let t=this.generateIframe(e);this.domRefFrame.insertAdjacentHTML(`beforeend`,t),this.domRefFrame.classList.add(`activated`),this.isIframeLoaded=!0,this.attemptShortAutoPlay(),this.dispatchEvent(new CustomEvent(`liteYoutubeIframeLoaded`,{detail:{videoId:this.videoId},bubbles:!0,cancelable:!0}))}}initImagePlaceholder(){this.playlistId&&!this.videoId?this.loadPlaylistThumbnail():this.testPosterImage(),this.domRefImg.fallback.setAttribute(`aria-label`,`${this.videoPlay}: ${this.videoTitle}`),this.domRefImg?.fallback?.setAttribute(`alt`,`${this.videoPlay}: ${this.videoTitle}`)}async loadPlaylistThumbnail(){if(!this.isPlaylistThumbnailLoaded){this.isPlaylistThumbnailLoaded=!0;try{let e=`https://www.youtube.com/oembed?url=https://www.youtube.com/playlist?list=${this.playlistId}&format=json`,t=await fetch(e);if(!t.ok)throw Error(`Failed to fetch playlist thumbnail: ${t.status}`);let n=await t.json();if(n.thumbnail_url){let e=n.thumbnail_url,t=e.match(/\/vi\/([^\/]+)\//);if(t){let e=t[1];this.loadThumbnailImages(e)}else this.domRefImg.fallback.src=e,this.domRefImg.fallback.loading=this.posterLoading}}catch(e){console.warn(`Failed to load playlist thumbnail:`,e)}}}loadThumbnailImages(e){let t=`https://i.ytimg.com/vi_webp/${e}/${this.posterQuality}.webp`;this.domRefImg.webp.srcset=t;let n=`https://i.ytimg.com/vi/${e}/${this.posterQuality}.jpg`;this.domRefImg.jpeg.srcset=n,this.domRefImg.fallback.src=n,this.domRefImg.fallback.loading=this.posterLoading}async testPosterImage(){setTimeout(()=>{let e=`https://i.ytimg.com/vi_webp/${this.videoId}/${this.posterQuality}.webp`,t=new Image;t.fetchPriority=`low`,t.referrerPolicy=`origin`,t.src=e,t.onload=async e=>{let t=e.target;t?.naturalHeight==90&&t?.naturalWidth==120&&(this.posterQuality=`hqdefault`),this.loadThumbnailImages(this.videoId)}},100)}initIntersectionObserver(){new IntersectionObserver((t,n)=>{t.forEach(t=>{t.isIntersecting&&!this.isIframeLoaded&&(e.warmConnections(this),this.addIframe(!0),n.unobserve(this))})},{root:null,rootMargin:`0px`,threshold:0}).observe(this),this.autoPause&&new IntersectionObserver((e,t)=>{e.forEach(e=>{e.intersectionRatio!==1&&this.shadowRoot.querySelector(`iframe`)?.contentWindow?.postMessage(`{"event":"command","func":"pauseVideo","args":""}`,`*`)})},{threshold:1}).observe(this)}attemptShortAutoPlay(){this.isYouTubeShort()&&setTimeout(()=>{this.shadowRoot.querySelector(`iframe`)?.contentWindow?.postMessage(`{"event":"command","func":"playVideo","args":""}`,`*`)},2e3)}isYouTubeShort(){return this.getAttribute(`short`)===``&&window.matchMedia(`(max-width: 40em)`).matches}static addPrefetch(e,t){let n=document.createElement(`link`);n.rel=e,n.href=t,n.crossOrigin=`true`,document.head.append(n)}static warmConnections(t){e.isPreconnected||window.liteYouTubeIsPreconnected||(e.addPrefetch(`preconnect`,`https://i.ytimg.com/`),e.addPrefetch(`preconnect`,`https://s.ytimg.com`),t.noCookie?e.addPrefetch(`preconnect`,`https://www.youtube-nocookie.com`):(e.addPrefetch(`preconnect`,`https://www.youtube.com`),e.addPrefetch(`preconnect`,`https://www.google.com`),e.addPrefetch(`preconnect`,`https://googleads.g.doubleclick.net`),e.addPrefetch(`preconnect`,`https://static.doubleclick.net`)),e.isPreconnected=!0,window.liteYouTubeIsPreconnected=!0)}};Fc.isPreconnected=!1,customElements.define(`lite-youtube`,Fc);function Ic({authors:e,hidden:t,children:n}){if(t||!e?.[0]?.account?.id)return n;let r=e[0].account;return M(`div`,{class:`card-byline`,children:[n,M(`div`,{class:`card-byline-author`,children:[M(Q,{icon:`link`,size:`s`}),` `,M(`small`,{children:M(D,{id:`4LHHK6`,components:{0:M(si,{account:r,showAvatar:!0})}})})]})]})}function Lc(e){return[`x.com`,`twitter.com`,`threads.net`,`bsky.app`,`bsky.brid.gy`,`fed.brid.gy`].includes(e)}function Rc({card:t,selfReferential:n,selfAuthor:r,instance:o}){let c=z(i),{blurhash:l,title:u,description:d,html:f,providerName:p,providerUrl:m,authorName:h,authorUrl:v,width:y,height:b,image:x,imageDescription:S,url:C,type:w,embedUrl:T,language:E,publishedAt:D,authors:O}=t,k=u||p||h,ee=y/b>=1.2?`large`:``,[te,j]=A(null);if(g(()=>{if(!k||!x||n||!ae(C))return;let e=new AbortController;return s(o,C,e.signal).then(e=>{if(!e)return;let{id:t,url:n}=e;j(`#`+n)}),()=>{e.abort()}},[k,x,n]),c.unfurledLinks[C])return null;let N=/`;\n }\n addIframe(isIntersectionObserver = false) {\n if (!this.isIframeLoaded) {\n const iframeHTML = this.generateIframe(isIntersectionObserver);\n this.domRefFrame.insertAdjacentHTML('beforeend', iframeHTML);\n this.domRefFrame.classList.add('activated');\n this.isIframeLoaded = true;\n this.attemptShortAutoPlay();\n this.dispatchEvent(new CustomEvent('liteYoutubeIframeLoaded', {\n detail: {\n videoId: this.videoId,\n },\n bubbles: true,\n cancelable: true,\n }));\n }\n }\n initImagePlaceholder() {\n if (this.playlistId && !this.videoId) {\n this.loadPlaylistThumbnail();\n }\n else {\n this.testPosterImage();\n }\n this.domRefImg.fallback.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`);\n this.domRefImg?.fallback?.setAttribute('alt', `${this.videoPlay}: ${this.videoTitle}`);\n }\n async loadPlaylistThumbnail() {\n if (this.isPlaylistThumbnailLoaded) {\n return;\n }\n this.isPlaylistThumbnailLoaded = true;\n try {\n const oEmbedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/playlist?list=${this.playlistId}&format=json`;\n const response = await fetch(oEmbedUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch playlist thumbnail: ${response.status}`);\n }\n const data = await response.json();\n if (data.thumbnail_url) {\n const thumbnailUrl = data.thumbnail_url;\n const videoIdMatch = thumbnailUrl.match(/\\/vi\\/([^\\/]+)\\//);\n if (videoIdMatch) {\n const extractedVideoId = videoIdMatch[1];\n this.loadThumbnailImages(extractedVideoId);\n }\n else {\n this.domRefImg.fallback.src = thumbnailUrl;\n this.domRefImg.fallback.loading = this.posterLoading;\n }\n }\n }\n catch (error) {\n console.warn('Failed to load playlist thumbnail:', error);\n }\n }\n loadThumbnailImages(videoId) {\n const posterUrlWebp = `https://i.ytimg.com/vi_webp/${videoId}/${this.posterQuality}.webp`;\n this.domRefImg.webp.srcset = posterUrlWebp;\n const posterUrlJpeg = `https://i.ytimg.com/vi/${videoId}/${this.posterQuality}.jpg`;\n this.domRefImg.jpeg.srcset = posterUrlJpeg;\n this.domRefImg.fallback.src = posterUrlJpeg;\n this.domRefImg.fallback.loading = this.posterLoading;\n }\n async testPosterImage() {\n setTimeout(() => {\n const webpUrl = `https://i.ytimg.com/vi_webp/${this.videoId}/${this.posterQuality}.webp`;\n const img = new Image();\n img.fetchPriority = 'low';\n img.referrerPolicy = 'origin';\n img.src = webpUrl;\n img.onload = async (e) => {\n const target = e.target;\n const noPoster = target?.naturalHeight == 90 && target?.naturalWidth == 120;\n if (noPoster) {\n this.posterQuality = 'hqdefault';\n }\n this.loadThumbnailImages(this.videoId);\n };\n }, 100);\n }\n initIntersectionObserver() {\n const options = {\n root: null,\n rootMargin: '0px',\n threshold: 0,\n };\n const observer = new IntersectionObserver((entries, observer) => {\n entries.forEach(entry => {\n if (entry.isIntersecting && !this.isIframeLoaded) {\n LiteYTEmbed.warmConnections(this);\n this.addIframe(true);\n observer.unobserve(this);\n }\n });\n }, options);\n observer.observe(this);\n if (this.autoPause) {\n const windowPause = new IntersectionObserver((e, o) => {\n e.forEach(entry => {\n if (entry.intersectionRatio !== 1) {\n this.shadowRoot\n .querySelector('iframe')\n ?.contentWindow?.postMessage('{\"event\":\"command\",\"func\":\"pauseVideo\",\"args\":\"\"}', '*');\n }\n });\n }, { threshold: 1 });\n windowPause.observe(this);\n }\n }\n attemptShortAutoPlay() {\n if (this.isYouTubeShort()) {\n setTimeout(() => {\n this.shadowRoot\n .querySelector('iframe')\n ?.contentWindow?.postMessage('{\"event\":\"command\",\"func\":\"' + 'playVideo' + '\",\"args\":\"\"}', '*');\n }, 2000);\n }\n }\n isYouTubeShort() {\n return (this.getAttribute('short') === '' &&\n window.matchMedia('(max-width: 40em)').matches);\n }\n static addPrefetch(kind, url) {\n const linkElem = document.createElement('link');\n linkElem.rel = kind;\n linkElem.href = url;\n linkElem.crossOrigin = 'true';\n document.head.append(linkElem);\n }\n static warmConnections(context) {\n if (LiteYTEmbed.isPreconnected || window.liteYouTubeIsPreconnected)\n return;\n LiteYTEmbed.addPrefetch('preconnect', 'https://i.ytimg.com/');\n LiteYTEmbed.addPrefetch('preconnect', 'https://s.ytimg.com');\n if (!context.noCookie) {\n LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube.com');\n LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com');\n LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net');\n LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net');\n }\n else {\n LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com');\n }\n LiteYTEmbed.isPreconnected = true;\n window.liteYouTubeIsPreconnected = true;\n }\n}\nLiteYTEmbed.isPreconnected = false;\ncustomElements.define('lite-youtube', LiteYTEmbed);\n//# sourceMappingURL=lite-youtube.js.map","import { Trans } from '@lingui/react/macro';\n\nimport Icon from './icon';\nimport NameText from './name-text';\n\nfunction Byline({ authors, hidden, children }) {\n if (hidden) return children;\n if (!authors?.[0]?.account?.id) return children;\n const author = authors[0].account;\n\n return (\n
\n {children}\n
\n {' '}\n \n \n More from \n \n \n
\n
\n );\n}\n\nexport default Byline;\n","import '@justinribeiro/lite-youtube';\n\nimport { decodeBlurHash, getBlurHashAverageColor } from 'fast-blurhash';\nimport { useCallback, useEffect, useState } from 'preact/hooks';\nimport { useSnapshot } from 'valtio';\n\nimport getDomain from '../utils/get-domain';\nimport isMastodonLinkMaybe from '../utils/isMastodonLinkMaybe';\nimport states from '../utils/states';\nimport unfurlMastodonLink from '../utils/unfurl-link';\n\nimport Byline from './byline';\nimport Icon from './icon';\nimport RelativeTime from './relative-time';\n\n// \"Post\": Quote post + card link preview combo\n// Assume all links from these domains are \"posts\"\n// Mastodon links are \"posts\" too but they are converted to real quote posts and there's too many domains to check\n// This is just \"Progressive Enhancement\"\nfunction isCardPost(domain) {\n return [\n 'x.com',\n 'twitter.com',\n 'threads.net',\n 'bsky.app',\n 'bsky.brid.gy',\n 'fed.brid.gy',\n ].includes(domain);\n}\n\nfunction StatusCard({ card, selfReferential, selfAuthor, 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 authors,\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 return;\n }\n\n const abortController = new AbortController();\n unfurlMastodonLink(instance, url, abortController.signal).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 return () => {\n abortController.abort();\n };\n }, [hasText, image, selfReferential]);\n\n // if (cardStatusID) {\n // return (\n // \n // );\n // }\n\n if (snapStates.unfurledLinks[url]) return null;\n\n const hasIframeHTML = /