custom element for embedding Bluesky posts and feeds mary-ext.github.io/bluesky-embed
typescript npm bluesky atcute

Compare changes

Choose any two refs to compare.

Changed files
+10008 -2899
.changeset
.vscode
lib
packages
bluesky-post-embed
bluesky-profile-card-embed
bluesky-profile-feed-embed
internal
svelte-site
patches
src
themes
+9
.changeset/README.md
··· 1 + # Changesets 2 + 3 + Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool 4 + that works with multi-package repos, or single-package repos to help you version and publish your 5 + code. You can find the full documentation for it 6 + [in our repository](https://github.com/changesets/changesets) 7 + 8 + We have a quick list of common questions to get you started engaging with this project in 9 + [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
+11
.changeset/config.json
··· 1 + { 2 + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", 3 + "changelog": "@changesets/cli/changelog", 4 + "commit": false, 5 + "fixed": [], 6 + "linked": [], 7 + "access": "restricted", 8 + "baseBranch": "trunk", 9 + "updateInternalDependencies": "patch", 10 + "ignore": ["svelte-site"] 11 + }
+8
.changeset/six-rabbits-admire.md
··· 1 + --- 2 + 'bluesky-profile-card-embed': patch 3 + 'bluesky-profile-feed-embed': patch 4 + 'bluesky-post-embed': patch 5 + 'internal': patch 6 + --- 7 + 8 + upgrade atcute dependencies
-13
.editorconfig
··· 1 - [*] 2 - charset = utf-8 3 - end_of_line = lf 4 - insert_final_newline = true 5 - indent_style = tab 6 - trim_trailing_whitespace = true 7 - 8 - [*.yaml] 9 - indent_style = space 10 - 11 - [*.md] 12 - indent_style = space 13 - trim_trailing_whitespace = false
+1 -2
.gitignore
··· 1 1 node_modules/ 2 2 dist/ 3 - docs/ 4 3 5 4 .npm-*.log 6 5 .pnpm-*.log ··· 11 10 12 11 *.local 13 12 14 - tsconfig.tsbuildinfo 13 + *.tsbuildinfo
-1
.mailmap
··· 1 - Mary <git@mary.my.id> <pineapplecreamcheese@skiff.com>
-2
.npmrc
··· 1 - publish-branch=trunk 2 - auto-install-peers=false
+7
.prettierignore
··· 1 1 pnpm-lock.yaml 2 + 3 + dist/ 4 + 5 + /packages/svelte-site/pages/ 6 + /packages/bluesky-post-embed/themes/ 7 + /packages/bluesky-profile-card-embed/themes/ 8 + /packages/bluesky-profile-feed-embed/themes/
+9 -2
.prettierrc
··· 6 6 "semi": true, 7 7 "singleQuote": true, 8 8 "bracketSpacing": true, 9 - "plugins": ["prettier-plugin-css-order"], 9 + "plugins": ["prettier-plugin-svelte", "prettier-plugin-css-order"], 10 10 "overrides": [ 11 11 { 12 - "files": ["tsconfig.json", "jsconfig.json"], 12 + "files": ["tsconfig.json", "jsconfig.json", "tsconfig.*.json"], 13 13 "options": { 14 14 "parser": "jsonc" 15 + } 16 + }, 17 + { 18 + "files": ["*.md"], 19 + "options": { 20 + "printWidth": 100, 21 + "proseWrap": "always" 15 22 } 16 23 } 17 24 ]
+1 -1
.vscode/extensions.json
··· 1 1 { 2 - "recommendations": ["esbenp.prettier-vscode"] 2 + "recommendations": ["svelte.svelte-vscode"] 3 3 }
+1 -1
.vscode/settings.json
··· 1 1 { 2 - "editor.defaultFormatter": "esbenp.prettier-vscode", 2 + "editor.defaultFormatter": "prettier.prettier-vscode", 3 3 "typescript.tsdk": "node_modules/typescript/lib" 4 4 }
+11 -73
README.md
··· 1 - # &lt;bluesky-post> 2 - 3 - A custom element for embedding Bluesky posts. [Live demo](https://mary-ext.github.io/bluesky-post-embed) 4 - 5 - - **Lightweight**, the entire package + dependencies is only 24 KB (7 KB gzipped) 6 - - **Standalone**, no additional middleman involved, connects straight to Bluesky's API 7 - 8 - ## Installation 9 - 10 - ### via package manager 11 - 12 - This custom element is available on npm. 13 - 14 - ``` 15 - npm install bluesky-post-embed 16 - ``` 17 - 18 - Then, import the package on your app. 19 - 20 - ```js 21 - import 'bluesky-post-embed'; 22 - ``` 23 - 24 - ### via CDN 25 - 26 - If you like, you can also rely on CDN services like esm.sh or JSDelivr. 27 - 28 - ```html 29 - <script type="module" src="https://esm.sh/bluesky-post-embed@~0.1.0"></script> 30 - ``` 31 - 32 - ## Usage 33 - 34 - Bluesky posts can be embedded like so: 35 - 36 - ```html 37 - <bluesky-post src="https://bsky.app/profile/pfrazee.com/post/3kj2umze7zj2n" theme="light"> 38 - <blockquote class="bluesky-post-fallback"> 39 - <p dir="auto">angel mode</p> 40 - &mdash; Paul Frazee ๐Ÿฆ‹ (@pfrazee.com) 41 - <a href="https://bsky.app/profile/pfrazee.com/post/3kj2umze7zj2n">January 16, 2024</a> 42 - </blockquote> 43 - </bluesky-post> 44 - ``` 45 - 46 - Adding a fallback content like above is heavily recommended for progressive enhancement. 47 - 48 - ### Attributes 49 - 50 - - `src` **Required** 51 - A `bsky.app` URL of the post. 52 - Heavily recommended that the URL points to a DID instead of a handle, otherwise, it would have to 53 - resolve the handle into DID first (and suffer from issues like users changing their handles, etc) 54 - An example of such URL would be: https://bsky.app/profile/did:plc:ragtjsm2j2vknwkz3zp4oxrd/post/3kj2umze7zj2n 55 - - `contextless` **Optional** 56 - Prevent displaying of context when `src` points to a reply. 57 - - `theme` **Semi-required** 58 - The color palette that it should use, either `light` or `dark`. 59 - Set this to blank if you're setting a custom color palette. 60 - - `service-uri` **Optional** 61 - URL to an AppView service, defaults to `https://public.api.bsky.app` 62 - 63 - ### Events 1 + # &lt;bluesky-embed> 64 2 65 - - `loaded` 66 - Fired when the embed has successfully loaded the post 67 - - `error` 68 - Fired when the embed fails to load the post 3 + A custom element for embedding Bluesky posts. 69 4 70 - ## SSR usage 5 + - **Lightweight**, the entire package + dependencies is only ~20 KB (~6 KB gzipped) 6 + - **Standalone**, no middleman involved, directly calls Bluesky's API 7 + - **Server-side rendering possible**, allows for no-JavaScript usage 71 8 72 - The core of this package is a static HTML renderer, so long as your framework of 73 - choice has the means to deal with declarative shadow DOM, then it's possible to 74 - generate a completely static embed without any JavaScript in the client. 9 + | Packages | 10 + | ------------------------------------------------------------------------------------------------ | 11 + | [`bluesky-post-embed`](./packages/bluesky-post-embed): displays a post embed | 12 + | [`bluesky-profile-card-embed`](./packages/bluesky-profile-card-embed): displays a user's profile | 13 + | [`bluesky-profile-feed-embed`](./packages/bluesky-profile-feed-embed): displays a user's feed | 75 14 76 - See [this repository](https://github.com/mary-ext/astro-bluesky-post) for an 77 - SSR demo made in Astro. 15 + ![image](https://github.com/user-attachments/assets/fbe19e25-bcc8-4fd5-ac9d-568badbbb238)
-33
index.html
··· 1 - <!doctype html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="utf-8" /> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - <title>Bluesky post embeds</title> 7 - <script type="module" src="./src/index.ts"></script> 8 - </head> 9 - <body> 10 - <h1>&lt;bluesky-post&gt;</h1> 11 - <p>A custom element for embedding Bluesky posts.</p> 12 - <p>See <a href="https://github.com/mary-ext/bluesky-post-embed">GitHub repository</a> for details.</p> 13 - 14 - <bluesky-post src="https://bsky.app/profile/pfrazee.com/post/3kj2umze7zj2n" theme="light"> 15 - <blockquote class="bluesky-post-fallback"> 16 - <p dir="auto">angel mode</p> 17 - &mdash; Paul Frazee ๐Ÿฆ‹ (@pfrazee.com) 18 - <a href="https://bsky.app/profile/pfrazee.com/post/3kj2umze7zj2n">January 16, 2024</a> 19 - </blockquote> 20 - </bluesky-post> 21 - 22 - <pre> 23 - &#60;bluesky-post src="https://bsky.app/profile/pfrazee.com/post/3kj2umze7zj2n" theme="light"> 24 - &#60;blockquote class="bluesky-post-fallback"> 25 - &#60;p dir="auto">angel mode&#60;/p> 26 - &#38;mdash; Paul Frazee ๐Ÿฆ‹ (@pfrazee.com) 27 - &#60;a href="https://bsky.app/profile/pfrazee.com/post/3kj2umze7zj2n">January 16, 2024&#60;/a> 28 - &#60;/blockquote> 29 - &#60;/bluesky-post> 30 - &#60;script type="module" src="https://esm.sh/bluesky-post-embed@~0.1.0">&#60;/script></pre 31 - > 32 - </body> 33 - </html>
-686
lib/core.tsx
··· 1 - import type { TrustedHTML } from '@intrnl/jsx-to-string'; 2 - 3 - import { BskyXRPC } from '@externdefs/bluesky-client'; 4 - import type { 5 - AppBskyEmbedExternal, 6 - AppBskyEmbedImages, 7 - AppBskyEmbedRecord, 8 - AppBskyFeedDefs, 9 - AppBskyFeedGetPostThread, 10 - AppBskyFeedPost, 11 - AppBskyGraphDefs, 12 - } from '@externdefs/bluesky-client/lexicons'; 13 - 14 - import './style.css'; 15 - 16 - import { segment_richtext } from './utils/richtext/segmentize.ts'; 17 - import type { Facet } from './utils/richtext/types.ts'; 18 - 19 - import { abs_long, abs_short, format_date } from './utils/date.ts'; 20 - import { format_compact } from './utils/number.ts'; 21 - 22 - type ThreadResponse = AppBskyFeedGetPostThread.Output; 23 - type PostData = AppBskyFeedDefs.PostView; 24 - type PostRecord = AppBskyFeedPost.Record; 25 - 26 - /// Fetch post 27 - const parse_src = (src: string): [actor: string, rkey: string] => { 28 - const { protocol, host, pathname } = new URL(src); 29 - 30 - if (protocol === 'https:' || protocol === 'http:') { 31 - if (host === 'bsky.app' || host === 'staging.bsky.app') { 32 - const match = /\/profile\/([^/]+)\/post\/([^/]+)\/?$/.exec(pathname); 33 - 34 - if (match) { 35 - return [match[1], match[2]]; 36 - } 37 - } 38 - } 39 - 40 - throw new RangeError(`Invalid src: ${src}`); 41 - }; 42 - 43 - export const get = async ( 44 - src: string, 45 - contextless: boolean, 46 - service: string = 'https://public.api.bsky.app', 47 - ): Promise<ThreadResponse> => { 48 - const rpc = new BskyXRPC({ service }); 49 - 50 - const [actor, rkey] = parse_src(src); 51 - 52 - let did: string; 53 - if (actor.startsWith('did:')) { 54 - did = actor; 55 - } else { 56 - const response = await rpc.get('com.atproto.identity.resolveHandle', { 57 - params: { 58 - handle: actor, 59 - }, 60 - }); 61 - 62 - did = response.data.did; 63 - } 64 - 65 - const response = await rpc.get('app.bsky.feed.getPostThread', { 66 - params: { 67 - uri: `at://${did}/app.bsky.feed.post/${rkey}`, 68 - parentHeight: !contextless ? 2 : 1, 69 - depth: 0, 70 - }, 71 - }); 72 - 73 - const data = response.data; 74 - 75 - return data; 76 - }; 77 - 78 - /// Renderer 79 - const get_record_key = (uri: string) => { 80 - const idx = uri.lastIndexOf('/'); 81 - return uri.slice(idx + 1); 82 - }; 83 - 84 - const get_collection_ns = (uri: string) => { 85 - const first = uri.indexOf('/', 5); 86 - const second = uri.indexOf('/', first + 1); 87 - 88 - return uri.slice(first + 1, second); 89 - }; 90 - 91 - const get_profile_url = (author: string) => { 92 - return `https://bsky.app/profile/${author}`; 93 - }; 94 - 95 - const get_post_url = (author: string, rkey: string) => { 96 - return `https://bsky.app/profile/${author}/post/${rkey}`; 97 - }; 98 - 99 - export const render = (resp: ThreadResponse, contextless: boolean): TrustedHTML => { 100 - const posts = unwrap_thread(resp, contextless); 101 - const len = posts.length; 102 - 103 - if (len === 0) { 104 - return ( 105 - <div class="root"> 106 - <p class="not-available">This post is unavailable</p> 107 - </div> 108 - ); 109 - } 110 - 111 - const reply_to_icon = ( 112 - <svg viewBox="0 0 24 24" class="icon"> 113 - <path fill="currentColor" d="M10 9V5l-7 7l7 7v-4.1c5 0 8.5 1.6 11 5.1c-1-5-4-10-11-11" /> 114 - </svg> 115 - ); 116 - 117 - return ( 118 - <div class="root"> 119 - <div class="timeline"> 120 - {posts.map(({ post, parent }, i) => { 121 - const main = i === len - 1; 122 - 123 - const record = post.record as PostRecord; 124 - const author = post.author; 125 - 126 - const author_url = get_profile_url(author.did); 127 - const post_url = get_post_url(author.did, get_record_key(post.uri)); 128 - 129 - if (main) { 130 - return ( 131 - <div class="main-post"> 132 - <div class="main-post__header"> 133 - <a href={author_url} target="_blank" class="main-post__avatar-wrapper"> 134 - {author.avatar ? ( 135 - <img loading="lazy" src={author.avatar} class="main-post__avatar" /> 136 - ) : null} 137 - </a> 138 - 139 - <a href={author_url} target="_blank" class="main-post__name-wrapper"> 140 - <bdi class="main-post__display-name-wrapper"> 141 - <span class="main-post__display-name">{author.displayName}</span> 142 - </bdi> 143 - <span class="main-post__handle">@{author.handle}</span> 144 - </a> 145 - </div> 146 - 147 - {i === 0 && record.reply ? ( 148 - <p class="main-post__context"> 149 - {reply_to_icon} 150 - {parent ? ( 151 - <span> 152 - Reply to{' '} 153 - <a 154 - href={`https://bsky.app/profile/${parent.author.handle}`} 155 - target="_blank" 156 - class="main-post__context-link" 157 - > 158 - {parent.author.displayName || '@' + parent.author.handle} 159 - </a> 160 - </span> 161 - ) : ( 162 - <span>Reply to an unknown post</span> 163 - )} 164 - </p> 165 - ) : null} 166 - 167 - <div class="main-post__body"> 168 - <RichTextRenderer text={record.text} facets={record.facets} /> 169 - </div> 170 - 171 - {post.embed ? <Embeds embed={post.embed} large={true} /> : null} 172 - 173 - {record.tags ? ( 174 - <div class="main-post__tags"> 175 - {record.tags.map((tag) => ( 176 - <div class="main-post__tag"> 177 - <span>#</span> 178 - <span class="main-post__tag-text">{tag}</span> 179 - </div> 180 - ))} 181 - </div> 182 - ) : null} 183 - 184 - <time datetime={record.createdAt} class="main-post__date"> 185 - {format_date(abs_long, record.createdAt)} 186 - </time> 187 - 188 - <div class="main-post__stats"> 189 - <span class="main-post__stat" title={`${post.likeCount || 0} likes`}> 190 - <svg viewBox="0 0 24 24" class="icon"> 191 - <path 192 - fill="currentColor" 193 - d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3C4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5C22 5.42 19.58 3 16.5 3m-4.4 15.55l-.1.1l-.1-.1C7.14 14.24 4 11.39 4 8.5C4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5c0 2.89-3.14 5.74-7.9 10.05" 194 - ></path> 195 - </svg> 196 - <span>{format_compact(post.likeCount || 0)}</span> 197 - </span> 198 - 199 - <span class="main-post__stat" title={`${post.repostCount || 0} reposts`}> 200 - <svg viewBox="0 0 24 24" class="icon"> 201 - <path 202 - fill="currentColor" 203 - d="M7 7h10v3l4-4l-4-4v3H5v6h2zm10 10H7v-3l-4 4l4 4v-3h12v-6h-2z" 204 - ></path> 205 - </svg> 206 - <span>{format_compact(post.repostCount || 0)}</span> 207 - </span> 208 - 209 - <div class="gap"></div> 210 - 211 - <a href={post_url} target="_blank" class="permalink"> 212 - <span>Read {format_compact(post.replyCount || 0)} replies on Bluesky</span> 213 - </a> 214 - </div> 215 - </div> 216 - ); 217 - } else { 218 - return ( 219 - <div class="reply-post"> 220 - <div class="reply-post__aside"> 221 - <a href={author_url} target="_blank" class="reply-post__avatar-wrapper"> 222 - {author.avatar ? ( 223 - <img loading="lazy" src={author.avatar} class="reply-post__avatar" /> 224 - ) : null} 225 - </a> 226 - 227 - <div class="reply-post__line"></div> 228 - </div> 229 - 230 - <div class="reply-post__main"> 231 - <div class="reply-post__header"> 232 - <a href={author_url} target="_blank" class="reply-post__name-wrapper"> 233 - <bdi class="reply-post__display-name-wrapper"> 234 - <span class="reply-post__display-name">{author.displayName}</span> 235 - </bdi> 236 - 237 - <span class="reply-post__handle">@{author.handle}</span> 238 - </a> 239 - 240 - <span aria-hidden="true" class="dot"> 241 - ยท 242 - </span> 243 - 244 - <a 245 - href={post_url} 246 - target="_blank" 247 - title={format_date(abs_long, record.createdAt)} 248 - class="reply-post__date" 249 - > 250 - <time datetime={record.createdAt}>{format_date(abs_short, record.createdAt)}</time> 251 - </a> 252 - </div> 253 - 254 - {i === 0 && record.reply ? ( 255 - <p class="reply-post__context"> 256 - {reply_to_icon} 257 - {parent ? ( 258 - <span> 259 - Reply to{' '} 260 - <a 261 - href={`https://bsky.app/profile/${parent.author.handle}`} 262 - target="_blank" 263 - class="reply-post__context-link" 264 - > 265 - {parent.author.displayName || '@' + parent.author.handle} 266 - </a> 267 - </span> 268 - ) : ( 269 - <span>Reply to an unknown post</span> 270 - )} 271 - </p> 272 - ) : null} 273 - 274 - <div class="reply-post__body"> 275 - <RichTextRenderer text={record.text} facets={record.facets} /> 276 - </div> 277 - 278 - {post.embed ? <Embeds embed={post.embed} large={false} /> : null} 279 - </div> 280 - </div> 281 - ); 282 - } 283 - })} 284 - </div> 285 - </div> 286 - ); 287 - }; 288 - 289 - const unwrap_thread = (resp: ThreadResponse, contextless: boolean) => { 290 - const items: { post: PostData; parent: PostData | undefined }[] = []; 291 - 292 - let i = 0; 293 - let il = contextless ? 1 : 2; 294 - 295 - let curr: typeof resp.thread | undefined = resp.thread; 296 - while (curr) { 297 - if (curr.$type === 'app.bsky.feed.defs#notFoundPost' || curr.$type === 'app.bsky.feed.defs#blockedPost') { 298 - break; 299 - } 300 - 301 - const post = curr.post; 302 - 303 - if (i !== 0) { 304 - items[i - 1].parent = post; 305 - } 306 - 307 - if (++i > il) { 308 - break; 309 - } 310 - 311 - const author = post.author; 312 - if (author.labels?.some((def) => def.val === '!no-unauthenticated')) { 313 - break; 314 - } 315 - 316 - items.push({ post: post, parent: undefined }); 317 - curr = curr.parent; 318 - } 319 - 320 - return items.reverse(); 321 - }; 322 - 323 - /// <RichTextRenderer /> 324 - const RichTextRenderer = ({ text, facets }: { text: string; facets?: Facet[] }) => { 325 - const segments = segment_richtext(text, facets); 326 - 327 - return ( 328 - <> 329 - {segments.map((segment) => { 330 - const text = segment.text; 331 - 332 - const link = segment.link; 333 - const mention = segment.mention; 334 - const tag = segment.tag; 335 - 336 - if (link) { 337 - return ( 338 - <a href={link.uri} target="_blank" class="link"> 339 - {text} 340 - </a> 341 - ); 342 - } else if (mention) { 343 - return ( 344 - <a href={`https://bsky.app/profile/${mention.did}`} target="_blank" class="mention"> 345 - {text} 346 - </a> 347 - ); 348 - } else if (tag) { 349 - return <span class="hashtag">{text}</span>; 350 - } 351 - 352 - return text; 353 - })} 354 - </> 355 - ); 356 - }; 357 - 358 - /// <Embeds /> 359 - type EmbeddedImage = AppBskyEmbedImages.ViewImage; 360 - type EmbeddedLink = AppBskyEmbedExternal.ViewExternal; 361 - type EmbeddedRecord = AppBskyEmbedRecord.View['record']; 362 - 363 - const Embeds = ({ embed, large }: { embed: NonNullable<PostData['embed']>; large: boolean }) => { 364 - let images: EmbeddedImage[] | undefined; 365 - let link: EmbeddedLink | undefined; 366 - let record: EmbeddedRecord | undefined; 367 - 368 - { 369 - const $type = embed.$type; 370 - 371 - if ($type === 'app.bsky.embed.images#view') { 372 - images = embed.images; 373 - } else if ($type === 'app.bsky.embed.external#view') { 374 - link = embed.external; 375 - } else if ($type === 'app.bsky.embed.record#view') { 376 - record = embed.record; 377 - } else if ($type === 'app.bsky.embed.recordWithMedia#view') { 378 - const rec = embed.record.record; 379 - 380 - const media = embed.media; 381 - const mediatype = media.$type; 382 - 383 - record = rec; 384 - 385 - if (mediatype === 'app.bsky.embed.images#view') { 386 - images = media.images; 387 - } else if (mediatype === 'app.bsky.embed.external#view') { 388 - link = media.external; 389 - } 390 - } 391 - } 392 - 393 - return ( 394 - <div class="embeds"> 395 - {link ? <EmbedLink link={link} /> : null} 396 - {images ? <EmbedImage images={images} is_bordered={true} allow_standalone_ratio={true} /> : null} 397 - {record ? render_record(record, large) : null} 398 - </div> 399 - ); 400 - }; 401 - 402 - const render_record = (record: EmbeddedRecord, large: boolean) => { 403 - const $type = record.$type; 404 - 405 - if ($type === 'app.bsky.embed.record#viewNotFound' || $type === 'app.bsky.embed.record#viewBlocked') { 406 - return <EmbedNotFound uri={record.uri} />; 407 - } 408 - 409 - if ($type === 'app.bsky.embed.record#viewRecord') { 410 - const author = record.author; 411 - if (author.labels?.some((def) => def.val === '!no-unauthenticated')) { 412 - return <EmbedNotFound uri={record.uri} />; 413 - } 414 - 415 - return <EmbedPost post={record} large={large} />; 416 - } 417 - 418 - if ($type === 'app.bsky.feed.defs#generatorView') { 419 - return <EmbedFeed feed={record} />; 420 - } 421 - 422 - if ($type === 'app.bsky.graph.defs#listView') { 423 - return <EmbedList list={record} />; 424 - } 425 - 426 - return null; 427 - }; 428 - 429 - /// <EmbedNotFound /> 430 - const EmbedNotFound = ({ uri }: { uri: string }) => { 431 - const ns = get_collection_ns(uri); 432 - const resource = 433 - ns === 'app.bsky.feed.post' 434 - ? 'post' 435 - : ns === 'app.bsky.feed.generator' 436 - ? 'feed' 437 - : ns === 'app.bsky.graph.list' 438 - ? 'list' 439 - : 'record'; 440 - 441 - return <p class="embed-not-found">This {resource} is unavailable</p>; 442 - }; 443 - 444 - /// <EmbedLink /> 445 - const EmbedLink = ({ link }: { link: EmbeddedLink }) => { 446 - return ( 447 - <a href={link.uri} target="_blank" rel="noopener noreferrer nofollow" class="embed-link interactive"> 448 - {link.thumb ? <img loading="lazy" src={link.thumb} class="embed-link__thumb" /> : null} 449 - 450 - <div class="embed-link__main"> 451 - <p class="embed-link__domain">{get_domain(link.uri)}</p> 452 - <p class="embed-link__title">{link.title}</p> 453 - 454 - <div class="embed-link__desktop-only"> 455 - <p class="embed-link__summary">{link.description}</p> 456 - </div> 457 - </div> 458 - </a> 459 - ); 460 - }; 461 - 462 - const get_domain = (url: string) => { 463 - try { 464 - const host = new URL(url).host; 465 - return host.startsWith('www.') ? host.slice(4) : host; 466 - } catch { 467 - return url; 468 - } 469 - }; 470 - 471 - /// <EmbedImage /> 472 - const enum RenderMode { 473 - MULTIPLE, 474 - STANDALONE, 475 - STANDALONE_RATIO, 476 - } 477 - 478 - const EmbedImage = ({ 479 - images, 480 - is_bordered, 481 - allow_standalone_ratio, 482 - }: { 483 - images: EmbeddedImage[]; 484 - is_bordered: boolean; 485 - allow_standalone_ratio: boolean; 486 - }) => { 487 - const length = images.length; 488 - const is_standalone_image = allow_standalone_ratio && length === 1 && 'aspectRatio' in images[0]; 489 - 490 - return ( 491 - <div 492 - class={ 493 - 'embed-image' + 494 - (is_bordered ? ' embed-image--bordered' : '') + 495 - (is_standalone_image ? ' embed-image--standalone' : '') 496 - } 497 - > 498 - {is_standalone_image ? ( 499 - render_img(images[0], RenderMode.STANDALONE_RATIO) 500 - ) : length === 1 ? ( 501 - render_img(images[0], RenderMode.STANDALONE) 502 - ) : length === 2 ? ( 503 - <div class="embed-image__grid"> 504 - <div class="embed-image__col">{render_img(images[0], RenderMode.MULTIPLE)}</div> 505 - <div class="embed-image__col">{render_img(images[1], RenderMode.MULTIPLE)}</div> 506 - </div> 507 - ) : length === 3 ? ( 508 - <div class="embed-image__grid"> 509 - <div class="embed-image__col"> 510 - {render_img(images[0], RenderMode.MULTIPLE)} 511 - {render_img(images[1], RenderMode.MULTIPLE)} 512 - </div> 513 - 514 - <div class="embed-image__col">{render_img(images[2], RenderMode.MULTIPLE)}</div> 515 - </div> 516 - ) : length === 4 ? ( 517 - <div class="embed-image__grid"> 518 - <div class="embed-image__col"> 519 - {render_img(images[0], RenderMode.MULTIPLE)} 520 - {render_img(images[2], RenderMode.MULTIPLE)} 521 - </div> 522 - 523 - <div class="embed-image__col"> 524 - {render_img(images[1], RenderMode.MULTIPLE)} 525 - {render_img(images[3], RenderMode.MULTIPLE)} 526 - </div> 527 - </div> 528 - ) : null} 529 - </div> 530 - ); 531 - }; 532 - 533 - const render_img = (img: EmbeddedImage, mode: RenderMode) => { 534 - // FIXME: with STANDALONE_RATIO, we are resizing the image to make it fit 535 - // the container with our given constraints, but this doesn't work when the 536 - // image hasn't had its metadata loaded yet, the browser will snap to the 537 - // smallest possible size for our layout. 538 - 539 - const alt = img.alt; 540 - const aspectRatio = img.aspectRatio; 541 - 542 - let cn: string | undefined; 543 - let ratio: string | undefined; 544 - 545 - if (mode === RenderMode.MULTIPLE) { 546 - cn = `embed-image__image-wrapper--multiple`; 547 - } else if (mode === RenderMode.STANDALONE) { 548 - cn = `embed-image__image-wrapper--standalone`; 549 - } else if (mode === RenderMode.STANDALONE_RATIO) { 550 - cn = `embed-image__image-wrapper--standalone-ratio`; 551 - ratio = `${aspectRatio!.width}/${aspectRatio!.height}`; 552 - } 553 - 554 - return ( 555 - <div class={'embed-image__image-wrapper ' + cn} style={{ 'aspect-ratio': ratio }}> 556 - <img loading="lazy" src={img.thumb} alt={alt} class="embed-image__image" /> 557 - {mode === RenderMode.STANDALONE_RATIO ? <div class="embed-image__placeholder"></div> : null} 558 - </div> 559 - ); 560 - }; 561 - 562 - /// <EmbedPost /> 563 - const EmbedPost = ({ post, large }: { post: AppBskyEmbedRecord.ViewRecord; large: boolean }) => { 564 - const author = post.author; 565 - 566 - const record = post.value as PostRecord; 567 - const text = record.text; 568 - const images = get_post_images(post); 569 - 570 - const show_large_images = images !== undefined && (large || !text); 571 - 572 - const post_url = get_post_url(author.did, get_record_key(post.uri)); 573 - 574 - return ( 575 - <a href={post_url} target="_blank" class="embed-post interactive"> 576 - <div class="embed-post__header"> 577 - <div class="embed-post__avatar-wrapper"> 578 - {author.avatar ? <img loading="lazy" src={author.avatar} class="embed-post__avatar" /> : null} 579 - </div> 580 - 581 - <span class="embed-post__name-wrapper"> 582 - {author.displayName ? ( 583 - <bdi class="embed-post__display-name-wrapper"> 584 - <span class="embed-post__display-name">{author.displayName}</span> 585 - </bdi> 586 - ) : null} 587 - 588 - <span class="embed-post__handle">@{author.handle}</span> 589 - </span> 590 - 591 - <span aria-hidden="true" class="dot"> 592 - ยท 593 - </span> 594 - 595 - <time datetime={record.createdAt} class="embed-post__date"> 596 - {format_date(abs_short, record.createdAt)} 597 - </time> 598 - </div> 599 - 600 - {text ? ( 601 - <div class="embed-post__body"> 602 - {images && !large ? ( 603 - <div class="embed-post__image-aside"> 604 - <EmbedImage images={images} is_bordered={true} allow_standalone_ratio={false} /> 605 - </div> 606 - ) : null} 607 - 608 - <div class="embed-post__text">{text}</div> 609 - </div> 610 - ) : null} 611 - 612 - {show_large_images ? ( 613 - <> 614 - {text ? <div class="embed-post__divider"></div> : null} 615 - <EmbedImage images={images} is_bordered={false} allow_standalone_ratio={false} /> 616 - </> 617 - ) : null} 618 - </a> 619 - ); 620 - }; 621 - 622 - const get_post_images = (post: AppBskyEmbedRecord.ViewRecord) => { 623 - const embeds = post.embeds; 624 - 625 - if (embeds && embeds.length > 0) { 626 - const val = embeds[0]; 627 - 628 - if (val.$type === 'app.bsky.embed.images#view') { 629 - return val.images; 630 - } else if (val.$type === 'app.bsky.embed.recordWithMedia#view') { 631 - const media = val.media; 632 - 633 - if (media.$type === 'app.bsky.embed.images#view') { 634 - return media.images; 635 - } 636 - } 637 - } 638 - }; 639 - 640 - /// <EmbedFeed /> 641 - const EmbedFeed = ({ feed }: { feed: AppBskyFeedDefs.GeneratorView }) => { 642 - const creator = feed.creator; 643 - 644 - const feed_url = `https://bsky.app/profile/${creator.handle}/feed/${get_record_key(feed.uri)}`; 645 - 646 - return ( 647 - <a href={feed_url} target="_blank" class="embed-feed interactive"> 648 - <div class="embed-feed__avatar-wrapper"> 649 - {feed.avatar ? <img loading="lazy" src={feed.avatar} class="embed-feed__avatar" /> : null} 650 - </div> 651 - 652 - <div class="embed-feed__main"> 653 - <p class="embed-feed__name">{feed.displayName}</p> 654 - <p class="embed-feed__type">{`Feed by @${creator.handle}`}</p> 655 - </div> 656 - </a> 657 - ); 658 - }; 659 - 660 - /// <EmbedList /> 661 - const EmbedList = ({ list }: { list: AppBskyGraphDefs.ListView }) => { 662 - const creator = list.creator; 663 - 664 - const raw_purpose = list.purpose; 665 - const purpose = 666 - raw_purpose === 'app.bsky.graph.defs#curatelist' 667 - ? `Curation list` 668 - : raw_purpose === 'app.bsky.graph.defs#modlist' 669 - ? `Moderation list` 670 - : `Unknown list`; 671 - 672 - const list_url = `https://bsky.app/profile/${creator.handle}/list/${get_record_key(list.uri)}`; 673 - 674 - return ( 675 - <a href={list_url} target="_blank" class="embed-list interactive"> 676 - <div class="embed-list__avatar-wrapper"> 677 - {list.avatar ? <img loading="lazy" src={list.avatar} class="embed-list__avatar" /> : null} 678 - </div> 679 - 680 - <div class="embed-list__main"> 681 - <p class="embed-list__name">{list.name}</p> 682 - <p class="embed-list__type">{`${purpose} by ${creator.handle}`}</p> 683 - </div> 684 - </a> 685 - ); 686 - };
-1
lib/env.d.ts
··· 1 - /// <reference types="vite/client" />
-35
lib/index.ts
··· 1 - import rawStyles from './style.css?inline'; 2 - import { get, render } from './core.tsx'; 3 - 4 - let _style: CSSStyleSheet; 5 - const get_style = () => { 6 - if (!_style) { 7 - _style = new CSSStyleSheet(); 8 - _style.replaceSync(rawStyles); 9 - } 10 - 11 - return _style; 12 - }; 13 - 14 - export class BlueskyPost extends HTMLElement { 15 - connectedCallback() { 16 - this.load().then( 17 - () => this.dispatchEvent(new CustomEvent('loaded')), 18 - (err) => this.dispatchEvent(new CustomEvent('error', { detail: err })), 19 - ); 20 - } 21 - 22 - async load() { 23 - const src = this.getAttribute('src')!; 24 - const service = this.getAttribute('service-uri') || undefined; 25 - const contextless = this.getAttribute('contextless') !== null; 26 - 27 - const data = await get(src, contextless, service); 28 - 29 - const root = this.attachShadow({ mode: 'open' }); 30 - root.adoptedStyleSheets = [get_style()]; 31 - root.innerHTML = render(data, contextless).value; 32 - } 33 - } 34 - 35 - customElements.define('bluesky-post', BlueskyPost);
-677
lib/style.css
··· 1 - :host { 2 - --font-size: 1rem; 3 - display: block; 4 - } 5 - 6 - :host([theme='light']) { 7 - --text-primary: #000000; 8 - --text-secondary: #455668; 9 - --text-link: #1083fe; 10 - --background-primary: #ffffff; 11 - --background-secondary: #455668; 12 - --divider-hover: #c0ccd6; 13 - --divider: #c2ccd6; 14 - } 15 - :host([theme='dark']) { 16 - --text-primary: #ffffff; 17 - --text-secondary: #9aaabc; 18 - --text-link: #1083fe; 19 - --background-primary: #161e27; 20 - --background-secondary: #212d3b; 21 - --divider-hover: #42566c; 22 - --divider: #324458; 23 - } 24 - 25 - *, 26 - *::before, 27 - *::after { 28 - box-sizing: border-box; 29 - } 30 - 31 - p { 32 - margin: 0; 33 - } 34 - 35 - .root { 36 - margin: 0 auto; 37 - border: 1px solid var(--divider); 38 - border-radius: 8px; 39 - background: var(--background-primary); 40 - min-width: 250px; 41 - max-width: 550px; 42 - overflow: hidden; 43 - color: var(--text-primary); 44 - font-size: calc(var(--font-size) * 0.875); 45 - line-height: calc(var(--font-size) * 1.25); 46 - } 47 - 48 - .link, 49 - .mention { 50 - color: var(--text-link); 51 - text-decoration: none; 52 - 53 - &:hover { 54 - text-decoration: underline; 55 - } 56 - } 57 - 58 - .dot { 59 - margin: 0 4px; 60 - user-select: none; 61 - } 62 - 63 - .icon { 64 - flex-shrink: 0; 65 - width: 1em; 66 - height: 1em; 67 - } 68 - 69 - .interactive { 70 - cursor: pointer; 71 - color: inherit; 72 - text-decoration: none; 73 - } 74 - 75 - .not-available { 76 - padding: 12px; 77 - color: var(--text-secondary); 78 - } 79 - 80 - /** main-post */ 81 - .main-post { 82 - padding: 16px; 83 - } 84 - 85 - .main-post__header { 86 - display: flex; 87 - align-items: center; 88 - margin: 0 0 12px 0; 89 - color: var(--text-secondary); 90 - } 91 - 92 - .main-post__avatar-wrapper { 93 - display: block; 94 - flex-shrink: 0; 95 - margin: 0 12px 0 0; 96 - border-radius: 9999px; 97 - background: var(--background-secondary); 98 - width: 40px; 99 - height: 40px; 100 - overflow: hidden; 101 - 102 - &:hover { 103 - filter: brightness(0.85); 104 - } 105 - } 106 - .main-post__avatar { 107 - width: 100%; 108 - height: 100%; 109 - object-fit: cover; 110 - } 111 - 112 - .main-post__name-wrapper { 113 - display: block; 114 - max-width: 100%; 115 - overflow: hidden; 116 - color: inherit; 117 - text-decoration: none; 118 - text-overflow: ellipsis; 119 - white-space: nowrap; 120 - } 121 - .main-post__display-name-wrapper { 122 - overflow: hidden; 123 - text-overflow: ellipsis; 124 - 125 - .main-post__name-wrapper:hover & { 126 - text-decoration: underline; 127 - } 128 - } 129 - .main-post__display-name { 130 - color: var(--text-primary); 131 - font-weight: 700; 132 - } 133 - .main-post__handle { 134 - display: block; 135 - overflow: hidden; 136 - text-overflow: ellipsis; 137 - white-space: nowrap; 138 - } 139 - 140 - .main-post__context { 141 - display: flex; 142 - align-items: center; 143 - gap: 6px; 144 - margin: 0 0 4px 0; 145 - color: var(--text-secondary); 146 - 147 - span { 148 - overflow: hidden; 149 - text-overflow: ellipsis; 150 - white-space: nowrap; 151 - } 152 - 153 - a { 154 - color: inherit; 155 - text-decoration: none; 156 - 157 - &:hover { 158 - text-decoration: underline; 159 - } 160 - } 161 - } 162 - .main-post__body { 163 - overflow: hidden; 164 - font-size: calc(var(--font-size) * 1); 165 - line-height: calc(var(--font-size) * 1.5); 166 - white-space: pre-wrap; 167 - overflow-wrap: break-word; 168 - 169 - &:empty { 170 - display: none; 171 - } 172 - } 173 - 174 - .main-post__tags { 175 - display: flex; 176 - flex-wrap: wrap; 177 - gap: 6px; 178 - margin: 12px 0 0 0; 179 - } 180 - 181 - .main-post__tag { 182 - display: flex; 183 - align-items: center; 184 - gap: 4px; 185 - border-radius: 9999px; 186 - background: var(--text-secondary); 187 - padding: 0 8px; 188 - min-width: 0px; 189 - line-height: calc(var(--font-size) * 1.5); 190 - } 191 - 192 - .main-post__tag-text { 193 - overflow: hidden; 194 - text-overflow: ellipsis; 195 - white-space: nowrap; 196 - } 197 - 198 - .main-post__date { 199 - display: flex; 200 - flex-wrap: wrap; 201 - align-items: center; 202 - gap: 8px; 203 - margin: 12px 0 0; 204 - border-bottom: 1px solid var(--divider); 205 - padding: 0 0 12px 0; 206 - color: var(--text-secondary); 207 - } 208 - .main-post__stats { 209 - display: flex; 210 - flex-wrap: wrap; 211 - align-items: center; 212 - gap: 8px 16px; 213 - margin: 0 0 -16px 0; 214 - padding: 12px 0; 215 - color: var(--text-secondary); 216 - 217 - .gap { 218 - flex: 1 1 auto; 219 - } 220 - 221 - .permalink { 222 - display: flex; 223 - align-items: center; 224 - gap: 4px; 225 - color: var(--text-link); 226 - font-weight: 700; 227 - text-decoration: none; 228 - 229 - &:hover { 230 - text-decoration: underline; 231 - } 232 - } 233 - } 234 - .main-post__stat { 235 - display: flex; 236 - align-items: center; 237 - gap: 8px; 238 - } 239 - 240 - /** reply-post */ 241 - .reply-post { 242 - display: flex; 243 - position: relative; 244 - gap: 12px; 245 - padding: 12px 16px 0 16px; 246 - } 247 - 248 - .reply-post__aside { 249 - flex-shrink: 0; 250 - } 251 - 252 - .reply-post__avatar-wrapper { 253 - display: block; 254 - border-radius: 9999px; 255 - background: var(--background-secondary); 256 - width: 40px; 257 - height: 40px; 258 - overflow: hidden; 259 - 260 - &:hover { 261 - filter: brightness(0.85); 262 - } 263 - } 264 - .reply-post__avatar { 265 - width: 100%; 266 - height: 100%; 267 - object-fit: cover; 268 - } 269 - .reply-post__line { 270 - position: absolute; 271 - top: 56px; 272 - bottom: -12px; 273 - left: 35px; 274 - border-left: 2px solid var(--divider); 275 - } 276 - 277 - .reply-post__main { 278 - display: flex; 279 - flex-grow: 1; 280 - flex-direction: column; 281 - min-width: 0px; 282 - } 283 - 284 - .reply-post__header { 285 - display: flex; 286 - align-items: center; 287 - margin: 0 0 2px 0; 288 - color: var(--text-secondary); 289 - } 290 - .reply-post__name-wrapper { 291 - display: flex; 292 - gap: 4px; 293 - max-width: 100%; 294 - overflow: hidden; 295 - color: inherit; 296 - text-decoration: none; 297 - text-overflow: ellipsis; 298 - white-space: nowrap; 299 - } 300 - .reply-post__display-name-wrapper { 301 - overflow: hidden; 302 - text-overflow: ellipsis; 303 - 304 - .reply-post__name-wrapper:hover & { 305 - text-decoration: underline; 306 - } 307 - } 308 - .reply-post__display-name { 309 - color: var(--text-primary); 310 - font-weight: 700; 311 - } 312 - .reply-post__handle { 313 - display: block; 314 - overflow: hidden; 315 - text-overflow: ellipsis; 316 - white-space: nowrap; 317 - } 318 - .reply-post__date { 319 - color: inherit; 320 - text-decoration: none; 321 - white-space: nowrap; 322 - 323 - &:hover { 324 - text-decoration: underline; 325 - } 326 - } 327 - 328 - .reply-post__context { 329 - display: flex; 330 - align-items: center; 331 - gap: 4px; 332 - margin: 0 0 4px 0; 333 - color: var(--text-secondary); 334 - 335 - span { 336 - overflow: hidden; 337 - text-overflow: ellipsis; 338 - white-space: nowrap; 339 - } 340 - 341 - a { 342 - color: inherit; 343 - text-decoration: none; 344 - 345 - &:hover { 346 - text-decoration: underline; 347 - } 348 - } 349 - } 350 - .reply-post__body { 351 - white-space: pre-wrap; 352 - overflow-wrap: break-word; 353 - } 354 - 355 - /** embeds */ 356 - .embeds { 357 - display: flex; 358 - flex-direction: column; 359 - gap: 12px; 360 - margin: 12px 0 0 0; 361 - } 362 - 363 - .embeds:empty { 364 - display: none; 365 - } 366 - 367 - /** embed-feed */ 368 - .embed-feed { 369 - display: flex; 370 - gap: 12px; 371 - border: 1px solid var(--divider); 372 - border-radius: 6px; 373 - padding: 12px; 374 - 375 - &:hover { 376 - border-color: var(--divider-hover); 377 - } 378 - } 379 - 380 - .embed-feed__avatar-wrapper { 381 - margin: 2px 0 0 0; 382 - border-radius: 6px; 383 - background: var(--background-secondary); 384 - width: 36px; 385 - height: 36px; 386 - overflow: hidden; 387 - } 388 - 389 - .embed-feed__avatar { 390 - width: 100%; 391 - height: 100%; 392 - object-fit: cover; 393 - } 394 - 395 - .embed-feed__main { 396 - } 397 - 398 - .embed-feed__name { 399 - font-weight: 700; 400 - } 401 - 402 - .embed-feed__type { 403 - color: var(--text-secondary); 404 - } 405 - 406 - /** embed-image */ 407 - .embed-image { 408 - } 409 - 410 - .embed-image--bordered { 411 - border: 1px solid var(--divider); 412 - border-radius: 6px; 413 - overflow: hidden; 414 - } 415 - 416 - .embed-image--standalone { 417 - align-self: baseline; 418 - max-width: 100%; 419 - } 420 - 421 - .embed-image__grid { 422 - display: flex; 423 - gap: 2px; 424 - aspect-ratio: 16 / 9; 425 - } 426 - 427 - .embed-image__col { 428 - display: flex; 429 - flex-grow: 1; 430 - flex-basis: 0px; 431 - flex-direction: column; 432 - gap: 2px; 433 - } 434 - 435 - .embed-image__image-wrapper { 436 - position: relative; 437 - } 438 - 439 - .embed-image__image-wrapper--multiple { 440 - flex-grow: 1; 441 - flex-basis: 0px; 442 - min-height: 0px; 443 - overflow: hidden; 444 - } 445 - 446 - .embed-image__image-wrapper--standalone { 447 - aspect-ratio: 16 / 9; 448 - overflow: hidden; 449 - } 450 - 451 - .embed-image__image-wrapper--standalone-ratio { 452 - min-width: 64px; 453 - max-width: 100%; 454 - min-height: 64px; 455 - max-height: 320px; 456 - overflow: hidden; 457 - } 458 - 459 - .embed-image__image { 460 - width: 100%; 461 - height: 100%; 462 - object-fit: cover; 463 - font-size: 0px; 464 - } 465 - 466 - .embed-image__placeholder { 467 - width: 100vw; 468 - height: 100vh; 469 - } 470 - 471 - /** embed-link */ 472 - .embed-link { 473 - display: flex; 474 - border: 1px solid var(--divider); 475 - border-radius: 6px; 476 - overflow: hidden; 477 - 478 - &:hover { 479 - border-color: var(--divider-hover); 480 - } 481 - } 482 - 483 - .embed-link__thumb { 484 - flex-shrink: 0; 485 - box-sizing: content-box; 486 - border-right-width: 1px; 487 - aspect-ratio: 1 / 1; 488 - width: 86px; 489 - object-fit: cover; 490 - } 491 - 492 - .embed-link__main { 493 - display: flex; 494 - flex-direction: column; 495 - justify-content: center; 496 - gap: 2px; 497 - padding: 12px; 498 - min-width: 0px; 499 - } 500 - 501 - .embed-link__domain { 502 - overflow: hidden; 503 - color: var(--text-secondary); 504 - text-overflow: ellipsis; 505 - } 506 - 507 - .embed-link__title { 508 - display: -webkit-box; 509 - overflow: hidden; 510 - overflow-wrap: break-word; 511 - -webkit-box-orient: vertical; 512 - -webkit-line-clamp: 2; 513 - } 514 - 515 - .embed-link__title:empty { 516 - display: none; 517 - } 518 - 519 - .embed-link__desktop-only { 520 - display: none; 521 - 522 - @media (min-width: 640px) { 523 - display: block; 524 - } 525 - } 526 - 527 - .embed-link__summary { 528 - display: -webkit-box; 529 - overflow: hidden; 530 - -webkit-box-orient: vertical; 531 - -webkit-line-clamp: 2; 532 - color: var(--text-secondary); 533 - } 534 - 535 - .embed-link__summary:empty { 536 - display: none; 537 - } 538 - 539 - /** embed-list */ 540 - .embed-list { 541 - display: flex; 542 - gap: 12px; 543 - border: 1px solid var(--divider); 544 - border-radius: 6px; 545 - padding: 12px; 546 - 547 - &:hover { 548 - border-color: var(--divider-hover); 549 - } 550 - } 551 - 552 - .embed-list__avatar-wrapper { 553 - margin: 2px 0 0 0; 554 - border-radius: 6px; 555 - background: var(--background-secondary); 556 - width: 36px; 557 - height: 36px; 558 - overflow: hidden; 559 - } 560 - 561 - .embed-list__avatar { 562 - width: 100%; 563 - height: 100%; 564 - object-fit: cover; 565 - } 566 - 567 - .embed-list__main { 568 - } 569 - 570 - .embed-list__name { 571 - font-weight: 700; 572 - } 573 - 574 - .embed-list__type { 575 - color: var(--text-secondary); 576 - } 577 - 578 - /** embed-not-found */ 579 - .embed-not-found { 580 - border: 1px solid var(--divider); 581 - border-radius: 6px; 582 - padding: 12px; 583 - color: var(--text-secondary); 584 - } 585 - 586 - /** embed-post */ 587 - .embed-post { 588 - border: 1px solid var(--divider); 589 - border-radius: 6px; 590 - overflow: hidden; 591 - 592 - &:hover { 593 - border-color: var(--divider-hover); 594 - } 595 - } 596 - 597 - .embed-post__header { 598 - display: flex; 599 - margin: 12px 12px 0 12px; 600 - color: var(--text-secondary); 601 - } 602 - .embed-post__header + .embed-image { 603 - margin: 12px 0 0 0; 604 - } 605 - 606 - .embed-post__avatar-wrapper { 607 - flex-shrink: 0; 608 - margin: 0 8px 0 0; 609 - border-radius: 9999px; 610 - background: var(--background-secondary); 611 - width: 20px; 612 - height: 20px; 613 - overflow: hidden; 614 - } 615 - .embed-post__avatar { 616 - width: 100%; 617 - height: 100%; 618 - } 619 - 620 - .embed-post__name-wrapper { 621 - display: flex; 622 - gap: 4px; 623 - max-width: 100%; 624 - overflow: hidden; 625 - text-overflow: ellipsis; 626 - white-space: nowrap; 627 - } 628 - .embed-post__display-name-wrapper { 629 - overflow: hidden; 630 - text-overflow: ellipsis; 631 - } 632 - .embed-post__display-name { 633 - color: var(--text-primary); 634 - font-weight: 700; 635 - } 636 - .embed-post__handle { 637 - display: block; 638 - overflow: hidden; 639 - text-overflow: ellipsis; 640 - white-space: nowrap; 641 - } 642 - 643 - .embed-post__date { 644 - white-space: nowrap; 645 - } 646 - 647 - .embed-post__body { 648 - display: flex; 649 - align-items: flex-start; 650 - } 651 - 652 - .embed-post__image-aside { 653 - flex-grow: 1; 654 - flex-basis: 0px; 655 - margin: 8px 0 12px 12px; 656 - } 657 - 658 - .embed-post__text { 659 - display: -webkit-box; 660 - margin: 4px 12px 12px 12px; 661 - overflow: hidden; 662 - -webkit-box-orient: vertical; 663 - -webkit-line-clamp: 6; 664 - flex-grow: 4; 665 - flex-basis: 0px; 666 - min-width: 0px; 667 - white-space: pre-wrap; 668 - overflow-wrap: break-word; 669 - } 670 - 671 - .embed-post__text:empty { 672 - display: none; 673 - } 674 - 675 - .embed-post__divider { 676 - margin: 12px 0 0 0; 677 - }
-12
lib/utils/date.ts
··· 1 - export const abs_long = new Intl.DateTimeFormat('en-US', { dateStyle: 'long', timeStyle: 'short' }); 2 - export const abs_short = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' }); 3 - 4 - export const format_date = (formatter: Intl.DateTimeFormat, date: string | number) => { 5 - const inst = new Date(date); 6 - 7 - if (isNaN(inst.getTime())) { 8 - return 'N/A'; 9 - } 10 - 11 - return formatter.format(inst); 12 - };
-14
lib/utils/number.ts
··· 1 - const long = new Intl.NumberFormat('en-US'); 2 - const compact = new Intl.NumberFormat('en-US', { notation: 'compact' }); 3 - 4 - export const format_compact = (value: number) => { 5 - if (value < 1_000) { 6 - return '' + value; 7 - } 8 - 9 - if (value < 100_000) { 10 - return long.format(value); 11 - } 12 - 13 - return compact.format(value); 14 - };
-82
lib/utils/richtext/segmentize.ts
··· 1 - import { create_utf_string, get_utf8_length, slice_utf8 } from './unicode.ts'; 2 - import type { Facet, LinkFeature, MentionFeature, TagFeature } from './types.ts'; 3 - 4 - export interface RichTextSegment { 5 - text: string; 6 - link?: LinkFeature; 7 - mention?: MentionFeature; 8 - tag?: TagFeature; 9 - } 10 - 11 - const create_segment = (text: string, facet?: Facet): RichTextSegment => { 12 - let link: LinkFeature | undefined; 13 - let mention: MentionFeature | undefined; 14 - let tag: TagFeature | undefined; 15 - 16 - if (facet) { 17 - const features = facet.features; 18 - 19 - for (let idx = 0, len = features.length; idx < len; idx++) { 20 - const feature = features[idx]; 21 - const type = feature.$type; 22 - 23 - if (type === 'app.bsky.richtext.facet#link') { 24 - link = feature; 25 - } else if (type === 'app.bsky.richtext.facet#mention') { 26 - mention = feature; 27 - } else if (type === 'app.bsky.richtext.facet#tag') { 28 - tag = feature; 29 - } 30 - } 31 - } 32 - 33 - return { text, link, mention, tag }; 34 - }; 35 - 36 - export const segment_richtext = (text: string, facets: Facet[] | undefined) => { 37 - if (!facets || facets.length < 1) { 38 - return [create_segment(text)]; 39 - } 40 - 41 - const ustr = create_utf_string(text); 42 - 43 - const segments: RichTextSegment[] = []; 44 - const length = get_utf8_length(ustr); 45 - 46 - const facets_length = facets.length; 47 - 48 - let text_cursor = 0; 49 - let facet_cursor = 0; 50 - 51 - do { 52 - const facet = facets[facet_cursor]; 53 - const { byteStart, byteEnd } = facet.index; 54 - 55 - if (text_cursor < byteStart) { 56 - segments.push(create_segment(slice_utf8(ustr, text_cursor, byteStart))); 57 - } else if (text_cursor > byteStart) { 58 - facet_cursor++; 59 - continue; 60 - } 61 - 62 - if (byteStart < byteEnd) { 63 - const subtext = slice_utf8(ustr, byteStart, byteEnd); 64 - 65 - if (!subtext.trim()) { 66 - // dont empty string entities 67 - segments.push(create_segment(subtext)); 68 - } else { 69 - segments.push(create_segment(subtext, facet)); 70 - } 71 - } 72 - 73 - text_cursor = byteEnd; 74 - facet_cursor++; 75 - } while (facet_cursor < facets_length); 76 - 77 - if (text_cursor < length) { 78 - segments.push(create_segment(slice_utf8(ustr, text_cursor, length))); 79 - } 80 - 81 - return segments; 82 - };
-6
lib/utils/richtext/types.ts
··· 1 - import type { AppBskyRichtextFacet } from '@externdefs/bluesky-client/lexicons'; 2 - 3 - export type Facet = AppBskyRichtextFacet.Main; 4 - export type LinkFeature = AppBskyRichtextFacet.Link; 5 - export type MentionFeature = AppBskyRichtextFacet.Mention; 6 - export type TagFeature = AppBskyRichtextFacet.Tag;
-22
lib/utils/richtext/unicode.ts
··· 1 - const encoder = new TextEncoder(); 2 - const decoder = new TextDecoder(); 3 - 4 - export interface UtfString { 5 - u16: string; 6 - u8: Uint8Array; 7 - } 8 - 9 - export const create_utf_string = (utf16: string): UtfString => { 10 - return { 11 - u16: utf16, 12 - u8: encoder.encode(utf16), 13 - }; 14 - }; 15 - 16 - export const get_utf8_length = (utf: UtfString) => { 17 - return utf.u8.byteLength; 18 - }; 19 - 20 - export const slice_utf8 = (utf: UtfString, start?: number, end?: number) => { 21 - return decoder.decode(utf.u8.slice(start, end)); 22 - };
+3
mise.toml
··· 1 + [tools] 2 + node = "latest" 3 + pnpm = "latest"
+11 -33
package.json
··· 1 1 { 2 - "type": "module", 3 - "name": "bluesky-post-embed", 4 - "description": "Custom element for embedding Bluesky posts", 5 - "version": "0.1.6", 6 - "author": "externdefs", 7 - "license": "MIT", 8 - "repository": { 9 - "type": "git", 10 - "url": "git+https://codeberg.org/mary-ext/bluesky-post-embed.git" 11 - }, 12 - "files": [ 13 - "dist/" 14 - ], 2 + "private": true, 15 3 "scripts": { 16 - "dev": "vite", 17 - "build": "tsc && vite build", 18 - "fmt": "prettier --cache --write .", 19 - "prepublishOnly": "pnpm run build" 20 - }, 21 - "exports": { 22 - ".": "./dist/element.js", 23 - "./core": "./dist/core.js", 24 - "./style.css": "./dist/style.css" 25 - }, 26 - "dependencies": { 27 - "@externdefs/bluesky-client": "^0.5.8" 4 + "fmt": "prettier --cache --write ." 28 5 }, 29 6 "devDependencies": { 30 - "@babel/core": "^7.24.4", 31 - "@babel/plugin-syntax-typescript": "^7.24.1", 32 - "@intrnl/jsx-to-string": "^0.1.6", 33 - "@rollup/plugin-babel": "^6.0.4", 34 - "prettier": "^3.2.5", 7 + "@changesets/cli": "^2.29.8", 8 + "prettier": "^3.7.4", 35 9 "prettier-plugin-css-order": "^2.1.2", 36 - "typescript": "~5.4.5", 37 - "vite": "^5.2.8", 38 - "vite-plugin-dts": "^3.8.1" 10 + "prettier-plugin-svelte": "^3.4.0", 11 + "typescript": "~5.8.3" 12 + }, 13 + "pnpm": { 14 + "patchedDependencies": { 15 + "svelte": "patches/svelte.patch" 16 + } 39 17 } 40 18 }
+1
packages/bluesky-post-embed/.gitignore
··· 1 + themes/
+83
packages/bluesky-post-embed/README.md
··· 1 + # &lt;bluesky-post-embed> 2 + 3 + A custom element for embedding Bluesky posts. 4 + 5 + ## Installation 6 + 7 + ### via npm 8 + 9 + ``` 10 + npm install bluesky-post-embed 11 + ``` 12 + 13 + then, import the package on your app. 14 + 15 + ```js 16 + import 'bluesky-post-embed'; 17 + 18 + import 'bluesky-post-embed/style.css'; 19 + import 'bluesky-post-embed/themes/light.css'; 20 + ``` 21 + 22 + ## Usage 23 + 24 + ```html 25 + <bluesky-post src="at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.feed.post/3kj2umze7zj2n"> 26 + <blockquote class="bluesky-post-fallback"> 27 + <p dir="auto">angel mode</p> 28 + โ€” Paul Frazee (@pfrazee.com) 29 + <a href="https://bsky.app/profile/did:plc:ragtjsm2j2vknwkz3zp4oxrd/post/3kj2umze7zj2n" 30 + >January 16, 2024 at 9:11 AM</a 31 + > 32 + </blockquote> 33 + </bluesky-post> 34 + ``` 35 + 36 + ### Attributes 37 + 38 + - `src` **Required** 39 + AT-URI of the post record 40 + - `contextless` **Optional** 41 + Whether to show the post without any context (no parent reply) 42 + - `allow-unauthenticated` **Optional** 43 + Whether to allow unauthenticated viewing 44 + - `service-uri` **Optional** 45 + URL to an AppView service, defaults to `https://public.api.bsky.app` 46 + 47 + ### Events 48 + 49 + - `loaded` 50 + Fired when the embed has successfully loaded the post 51 + - `error` 52 + Fired when the embed fails to load the post 53 + 54 + ## SSR usage 55 + 56 + The embeds are powered by a static HTML renderer, this renderer can be used directly in your 57 + server-rendering framework of choice for a zero-JS experience. 58 + 59 + ```tsx 60 + import { fetchPost, renderPost } from 'bluesky-post-embed/core'; 61 + 62 + import 'bluesky-post-embed/style.css'; 63 + import 'bluesky-post-embed/themes/light.css'; 64 + 65 + // fetch the post 66 + const controller = new AbortController(); 67 + const data = await fetchPost({ 68 + src: `at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.feed.post/3kj2umze7zj2n`, 69 + signal: controller.signal, 70 + }); 71 + 72 + // render the post 73 + const html = renderPost(data); 74 + return ( 75 + <bluesky-post 76 + src={data.thread?.post.uri} 77 + dangerouslySetInnerHTML={{ __html: html }} 78 + ></bluesky-post> 79 + ); 80 + ``` 81 + 82 + Check out examples for [Astro](https://github.com/mary-ext/bluesky-embed-astro) and 83 + [SvelteKit](https://github.com/mary-ext/bluesky-embed-sveltekit).
+11
packages/bluesky-post-embed/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>Bluesky post embed</title> 7 + </head> 8 + <body> 9 + <script type="module" src="./src/main.tsx"></script> 10 + </body> 11 + </html>
+95
packages/bluesky-post-embed/lib/bluesky-post.svelte
··· 1 + <script lang="ts" module> 2 + import type { AppBskyFeedDefs, AppBskyFeedGetPostThread } from '@atcute/client/lexicons'; 3 + 4 + type ThreadData = AppBskyFeedGetPostThread.Output['thread']; 5 + type PostView = AppBskyFeedDefs.PostView; 6 + 7 + const unwrapPostThread = (data: ThreadData, contextless: boolean, allowUnauthenticated: boolean) => { 8 + const items: { post: PostView; parent: PostView | null }[] = []; 9 + 10 + let i = 0; 11 + let il = contextless ? 1 : 2; 12 + 13 + let curr: typeof data | undefined = data; 14 + while (curr) { 15 + if ( 16 + curr.$type === 'app.bsky.feed.defs#notFoundPost' || 17 + curr.$type === 'app.bsky.feed.defs#blockedPost' 18 + ) { 19 + break; 20 + } 21 + 22 + const post = curr.post; 23 + 24 + if (i !== 0) { 25 + items[i - 1].parent = post; 26 + } 27 + 28 + if (++i > il) { 29 + break; 30 + } 31 + 32 + const author = post.author; 33 + if (!allowUnauthenticated && author.labels?.some((def) => def.val === '!no-unauthenticated')) { 34 + break; 35 + } 36 + 37 + items.push({ post: post, parent: null }); 38 + curr = curr.parent; 39 + } 40 + return items.reverse(); 41 + }; 42 + </script> 43 + 44 + <script lang="ts"> 45 + import EmbedFrame from 'internal/components/embed-frame.svelte'; 46 + import HighlightedPost from 'internal/components/highlighted-post.svelte'; 47 + import Post from 'internal/components/post.svelte'; 48 + 49 + import { NO_UNAUTHENTICATED_LABEL } from 'internal/utils/constants.js'; 50 + import type { PostData } from 'internal/types/post.js'; 51 + 52 + const { thread, contextless, allowUnauthenticated }: PostData = $props(); 53 + 54 + const isPwiForbidden = 55 + !allowUnauthenticated && 56 + thread !== null && 57 + thread.$type === 'app.bsky.feed.defs#threadViewPost' && 58 + thread.post.author.labels?.some((label) => label.val === NO_UNAUTHENTICATED_LABEL); 59 + </script> 60 + 61 + {#if thread === null} 62 + {@render Message(`The post can't be found, it may have been deleted.`)} 63 + {:else if isPwiForbidden} 64 + {@render Message(`The author has requested for their posts to not be displayed on external sites.`)} 65 + {:else} 66 + {@const posts = unwrapPostThread(thread, contextless, allowUnauthenticated)} 67 + 68 + <EmbedFrame> 69 + {#each posts as { post, parent }, idx} 70 + {@const hasPrevious = idx !== 0} 71 + 72 + {#if idx === posts.length - 1} 73 + <HighlightedPost {post} {parent} prev={hasPrevious} /> 74 + {:else} 75 + <Post {post} {parent} prev={hasPrevious} /> 76 + {/if} 77 + {/each} 78 + </EmbedFrame> 79 + {/if} 80 + 81 + {#snippet Message(msg: string)} 82 + <EmbedFrame> 83 + <div class="message">{msg}</div> 84 + </EmbedFrame> 85 + {/snippet} 86 + 87 + <style> 88 + .message { 89 + margin: 0 auto; 90 + padding: 32px 16px; 91 + max-width: 380px; 92 + color: var(--text-secondary); 93 + text-align: center; 94 + } 95 + </style>
+70
packages/bluesky-post-embed/lib/core.ts
··· 1 + import '@atcute/bluesky/lexicons'; 2 + 3 + import { simpleFetchHandler, XRPC, XRPCError } from '@atcute/client'; 4 + import type { At } from '@atcute/client/lexicons'; 5 + import { render } from 'svelte/server'; 6 + 7 + import type { PostData } from 'internal/types/post.js'; 8 + import { DEFAULT_APPVIEW_URL } from 'internal/utils/constants.js'; 9 + 10 + import BlueskyPost from './bluesky-post.svelte'; 11 + 12 + export type { PostData }; 13 + 14 + export interface PostFetchOptions { 15 + /** 16 + * AT-URI of the post in question 17 + */ 18 + uri: string; 19 + /** 20 + * Abort signal to cancel the request 21 + */ 22 + signal?: AbortSignal; 23 + /** 24 + * Whether to fetch post without context (no parent replies) 25 + * @default false 26 + */ 27 + contextless?: boolean; 28 + /** 29 + * Whether to allow unauthenticated viewing 30 + * @default false 31 + */ 32 + allowUnauthenticated?: boolean; 33 + /** 34 + * AppView service to use 35 + * @default "https://public.api.bsky.app" 36 + */ 37 + serviceUri?: string; 38 + } 39 + 40 + export const fetchPost = async (opts: PostFetchOptions): Promise<PostData> => { 41 + const rpc = new XRPC({ handler: simpleFetchHandler({ service: opts.serviceUri ?? DEFAULT_APPVIEW_URL }) }); 42 + const contextless = opts.contextless ?? false; 43 + 44 + const { data } = await rpc 45 + .get('app.bsky.feed.getPostThread', { 46 + signal: opts.signal, 47 + params: { 48 + uri: opts.uri as At.ResourceUri, 49 + parentHeight: !contextless ? 2 : 1, 50 + depth: 0, 51 + }, 52 + }) 53 + .catch((err) => { 54 + if (err instanceof XRPCError) { 55 + if (err.kind === 'NotFound') { 56 + return { data: null }; 57 + } 58 + } 59 + 60 + return Promise.reject(err); 61 + }); 62 + 63 + const thread = data?.thread.$type === 'app.bsky.feed.defs#threadViewPost' ? data.thread : null; 64 + 65 + return { thread, contextless, allowUnauthenticated: opts.allowUnauthenticated ?? false }; 66 + }; 67 + 68 + export const renderPost = (data: PostData): string => { 69 + return render(BlueskyPost, { props: data }).body; 70 + };
+59
packages/bluesky-post-embed/lib/wc.ts
··· 1 + import { fetchPost, renderPost } from './core'; 2 + 3 + export class BlueskyPost extends HTMLElement { 4 + connectedCallback() { 5 + this.load().then( 6 + () => this.dispatchEvent(new CustomEvent('loaded')), 7 + (err) => { 8 + const defaulted = this.dispatchEvent(new CustomEvent('error', { detail: err })); 9 + if (defaulted) { 10 + throw err; 11 + } 12 + }, 13 + ); 14 + } 15 + 16 + async load() { 17 + const src = this.getAttribute('src')!; 18 + const serviceUri = this.getAttribute('service-uri') || undefined; 19 + const contextless = this.getAttribute('contextless') !== null; 20 + const allowUnauthenticated = this.getAttribute('allow-unauthenticated') !== null; 21 + const silent = this.getAttribute('silent') !== null; 22 + 23 + const data = await fetchPost({ uri: src, contextless, allowUnauthenticated, serviceUri }).catch( 24 + (error) => { 25 + if (silent) { 26 + console.warn('Failed to fetch post:', error); 27 + return null; 28 + } 29 + throw error; 30 + }, 31 + ); 32 + 33 + if (data === null) { 34 + return; 35 + } 36 + 37 + const html = renderPost(data); 38 + 39 + const root = this.shadowRoot; 40 + 41 + if (!root) { 42 + this.innerHTML = html; 43 + } else { 44 + const template = document.createElement('template'); 45 + template.innerHTML = html; 46 + 47 + const fragment = template.content; 48 + const slot = root.querySelector('slot'); 49 + 50 + if (slot) { 51 + slot.replaceWith(fragment); 52 + } else { 53 + root.appendChild(fragment); 54 + } 55 + } 56 + } 57 + } 58 + 59 + customElements.define('bluesky-post', BlueskyPost);
+45
packages/bluesky-post-embed/package.json
··· 1 + { 2 + "type": "module", 3 + "name": "bluesky-post-embed", 4 + "description": "Custom element for embedding Bluesky posts", 5 + "version": "1.0.6", 6 + "author": "externdefs", 7 + "license": "MIT", 8 + "repository": { 9 + "type": "git", 10 + "url": "https://github.com/mary-ext/bluesky-embed", 11 + "directory": "packages/bluesky-post-embed" 12 + }, 13 + "files": [ 14 + "dist/", 15 + "themes/" 16 + ], 17 + "exports": { 18 + ".": "./dist/wc.js", 19 + "./core": "./dist/core.js", 20 + "./style.css": "./dist/core.css", 21 + "./themes/*": "./themes/*" 22 + }, 23 + "scripts": { 24 + "dev": "vite", 25 + "build": "pnpm run check && vite build && rsync -aHAX --delete ../../themes/ themes/", 26 + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json", 27 + "prepack": "pnpm run build" 28 + }, 29 + "dependencies": { 30 + "@atcute/bluesky": "^2.1.1", 31 + "@atcute/bluesky-richtext-segmenter": "^2.0.4", 32 + "@atcute/client": "^3.1.0" 33 + }, 34 + "devDependencies": { 35 + "@preact/preset-vite": "^2.10.2", 36 + "@tsconfig/svelte": "^5.0.6", 37 + "@types/node": "^24.10.1", 38 + "internal": "workspace:^", 39 + "preact": "^10.28.0", 40 + "svelte": "catalog:", 41 + "svelte-check": "^4.3.4", 42 + "vite": "^7.2.6", 43 + "vite-plugin-dts": "^4.5.4" 44 + } 45 + }
+53
packages/bluesky-post-embed/src/app.tsx
··· 1 + import { useEffect, useMemo, useState } from 'preact/hooks'; 2 + 3 + import type { PostData } from 'internal/types/post.js'; 4 + import { fetchPost, renderPost } from '../lib/core'; 5 + 6 + const uri = `at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.feed.post/3kj2umze7zj2n`; 7 + 8 + const App = () => { 9 + const [state, setState] = useState<{ uri: string; data: PostData }>(); 10 + 11 + useEffect(() => { 12 + if (state && state.uri === uri) { 13 + return; 14 + } 15 + 16 + const controller = new AbortController(); 17 + const promise = fetchPost({ 18 + uri: uri, 19 + signal: controller.signal, 20 + allowUnauthenticated: true, 21 + }); 22 + 23 + promise.then((data) => { 24 + setState({ uri, data }); 25 + }); 26 + 27 + return () => { 28 + controller.abort(); 29 + }; 30 + }, [uri]); 31 + 32 + return <div class="app">{state && <BlueskyPost data={state.data} />}</div>; 33 + }; 34 + 35 + export default App; 36 + 37 + const BlueskyPost = ({ data }: { data: PostData }) => { 38 + const html = useMemo(() => renderPost(data), [data]); 39 + 40 + return <bluesky-post src={data.thread?.post.uri} dangerouslySetInnerHTML={{ __html: html }}></bluesky-post>; 41 + }; 42 + 43 + declare module 'preact' { 44 + namespace JSX { 45 + interface BlueskyPostAttributes extends HTMLAttributes<HTMLElement> { 46 + src?: string; 47 + } 48 + 49 + interface IntrinsicElements { 50 + 'bluesky-post': BlueskyPostAttributes; 51 + } 52 + } 53 + }
+8
packages/bluesky-post-embed/src/main.tsx
··· 1 + import { render } from 'preact'; 2 + 3 + import App from './app'; 4 + 5 + import '../../../themes/light.css'; 6 + import './styles/main.css'; 7 + 8 + render(<App />, document.body);
+8
packages/bluesky-post-embed/src/styles/main.css
··· 1 + @import './normalize.css'; 2 + 3 + .app { 4 + margin: 0 auto; 5 + padding: 36px 16px; 6 + width: 100%; 7 + max-width: calc(550px + (16 * 2px)); 8 + }
+199
packages/bluesky-post-embed/src/styles/normalize.css
··· 1 + /*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */ 2 + 3 + /* 4 + Document 5 + ======== 6 + */ 7 + 8 + /** 9 + Use a better box model (opinionated). 10 + */ 11 + 12 + *, 13 + ::before, 14 + ::after { 15 + box-sizing: border-box; 16 + } 17 + 18 + html { 19 + line-height: 1.15; /* 1. Correct the line height in all browsers. */ 20 + /* Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) */ 21 + font-family: 'Inter', 'Roboto', ui-sans-serif, sans-serif, 'Noto Color Emoji', 'Twemoji Mozilla'; 22 + -webkit-text-size-adjust: 100%; /* 2. Prevent adjustments of font size after orientation changes in iOS. */ 23 + tab-size: 4; /* 3. Use a more readable tab size (opinionated). */ 24 + } 25 + 26 + /* 27 + Sections 28 + ======== 29 + */ 30 + 31 + body { 32 + margin: 0; /* Remove the margin in all browsers. */ 33 + } 34 + 35 + /* 36 + Text-level semantics 37 + ==================== 38 + */ 39 + 40 + /** 41 + Add the correct font weight in Chrome and Safari. 42 + */ 43 + 44 + b, 45 + strong { 46 + font-weight: bolder; 47 + } 48 + 49 + /** 50 + 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) 51 + 2. Correct the odd 'em' font sizing in all browsers. 52 + */ 53 + 54 + code, 55 + kbd, 56 + samp, 57 + pre { 58 + font-size: 1em; /* 2 */ 59 + font-family: 'JetBrains Mono NL', ui-monospace, monospace; /* 1 */ 60 + } 61 + 62 + /** 63 + Add the correct font size in all browsers. 64 + */ 65 + 66 + small { 67 + font-size: 80%; 68 + } 69 + 70 + /** 71 + Prevent 'sub' and 'sup' elements from affecting the line height in all browsers. 72 + */ 73 + 74 + sub, 75 + sup { 76 + position: relative; 77 + vertical-align: baseline; 78 + font-size: 75%; 79 + line-height: 0; 80 + } 81 + 82 + sub { 83 + bottom: -0.25em; 84 + } 85 + 86 + sup { 87 + top: -0.5em; 88 + } 89 + 90 + /* 91 + Tabular data 92 + ============ 93 + */ 94 + 95 + /** 96 + Correct table border color inheritance in Chrome and Safari. (https://issues.chromium.org/issues/40615503, https://bugs.webkit.org/show_bug.cgi?id=195016) 97 + */ 98 + 99 + table { 100 + border-color: currentcolor; 101 + } 102 + 103 + /* 104 + Forms 105 + ===== 106 + */ 107 + 108 + /** 109 + 1. Change the font styles in all browsers. 110 + 2. Remove the margin in Firefox and Safari. 111 + */ 112 + 113 + button, 114 + input, 115 + optgroup, 116 + select, 117 + textarea { 118 + margin: 0; /* 2 */ 119 + font-size: 100%; /* 1 */ 120 + line-height: 1.15; /* 1 */ 121 + font-family: inherit; /* 1 */ 122 + } 123 + 124 + /** 125 + Correct the inability to style clickable types in iOS and Safari. 126 + */ 127 + 128 + button, 129 + [type='button'], 130 + [type='reset'], 131 + [type='submit'] { 132 + -webkit-appearance: button; 133 + } 134 + 135 + /** 136 + Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers. 137 + */ 138 + 139 + legend { 140 + padding: 0; 141 + } 142 + 143 + /** 144 + Add the correct vertical alignment in Chrome and Firefox. 145 + */ 146 + 147 + progress { 148 + vertical-align: baseline; 149 + } 150 + 151 + /** 152 + Correct the cursor style of increment and decrement buttons in Safari. 153 + */ 154 + 155 + ::-webkit-inner-spin-button, 156 + ::-webkit-outer-spin-button { 157 + height: auto; 158 + } 159 + 160 + /** 161 + 1. Correct the odd appearance in Chrome and Safari. 162 + 2. Correct the outline style in Safari. 163 + */ 164 + 165 + [type='search'] { 166 + -webkit-appearance: textfield; /* 1 */ 167 + outline-offset: -2px; /* 2 */ 168 + } 169 + 170 + /** 171 + Remove the inner padding in Chrome and Safari on macOS. 172 + */ 173 + 174 + ::-webkit-search-decoration { 175 + -webkit-appearance: none; 176 + } 177 + 178 + /** 179 + 1. Correct the inability to style clickable types in iOS and Safari. 180 + 2. Change font properties to 'inherit' in Safari. 181 + */ 182 + 183 + ::-webkit-file-upload-button { 184 + -webkit-appearance: button; /* 1 */ 185 + font: inherit; /* 2 */ 186 + } 187 + 188 + /* 189 + Interactive 190 + =========== 191 + */ 192 + 193 + /* 194 + Add the correct display in Chrome and Safari. 195 + */ 196 + 197 + summary { 198 + display: list-item; 199 + }
+8
packages/bluesky-post-embed/svelte.config.js
··· 1 + export default { 2 + compilerOptions: { 3 + warningFilter: (warning) => { 4 + if (warning.code === 'state_referenced_locally') return false; 5 + return true; 6 + }, 7 + }, 8 + };
+14
packages/bluesky-post-embed/tsconfig.build.json
··· 1 + { 2 + "extends": "@tsconfig/svelte/tsconfig.json", 3 + "compilerOptions": { 4 + "types": [], 5 + "target": "ESNext", 6 + "useDefineForClassFields": true, 7 + "module": "ESNext", 8 + "resolveJsonModule": true, 9 + "isolatedModules": true, 10 + "moduleDetection": "force", 11 + "noEmit": true, 12 + }, 13 + "include": ["lib"], 14 + }
+17
packages/bluesky-post-embed/tsconfig.json
··· 1 + { 2 + "extends": "@tsconfig/svelte/tsconfig.json", 3 + "compilerOptions": { 4 + "types": [], 5 + "target": "ESNext", 6 + "useDefineForClassFields": true, 7 + "module": "ESNext", 8 + "resolveJsonModule": true, 9 + "isolatedModules": true, 10 + "moduleDetection": "force", 11 + "noEmit": true, 12 + "jsx": "react-jsx", 13 + "jsxImportSource": "preact", 14 + }, 15 + "include": ["lib", "src"], 16 + "references": [{ "path": "./tsconfig.node.json" }], 17 + }
+14
packages/bluesky-post-embed/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "types": ["node"], 5 + "skipLibCheck": true, 6 + "module": "ESNext", 7 + "target": "ESNext", 8 + "moduleResolution": "Bundler", 9 + "strict": true, 10 + "noEmit": true, 11 + "noUncheckedSideEffectImports": true, 12 + }, 13 + "include": ["vite.config.ts"], 14 + }
+103
packages/bluesky-post-embed/vite.config.ts
··· 1 + import * as path from 'node:path'; 2 + 3 + import { compile as compileSvelte } from 'svelte/compiler'; 4 + import { type Plugin, createFilter, defineConfig } from 'vite'; 5 + 6 + import preact from '@preact/preset-vite'; 7 + import dts from 'vite-plugin-dts'; 8 + 9 + export default defineConfig({ 10 + base: './', 11 + build: { 12 + outDir: 'dist/', 13 + target: 'esnext', 14 + minify: false, 15 + cssMinify: false, 16 + cssCodeSplit: true, 17 + lib: { 18 + entry: { 19 + core: 'lib/core.ts', 20 + wc: 'lib/wc.ts', 21 + }, 22 + formats: ['es'], 23 + }, 24 + rollupOptions: { 25 + external: ['@atcute/client', '@atcute/bluesky-richtext-segmenter'], 26 + }, 27 + }, 28 + esbuild: { 29 + target: 'esnext', 30 + }, 31 + plugins: [ 32 + svelte(), 33 + preact(), 34 + dts({ 35 + rollupTypes: true, 36 + tsconfigPath: 'tsconfig.build.json', 37 + beforeWriteFile(filePath, content) { 38 + if (filePath.endsWith('/core.d.ts')) { 39 + // Make sure the relevant types are present 40 + return { content: `import '@atcute/bluesky/lexicons';\n${content}` }; 41 + } 42 + }, 43 + }), 44 + ], 45 + }); 46 + 47 + function svelte(): Plugin { 48 + const filter = createFilter('**/*.svelte'); 49 + const stylesheets = new Map<string, string>(); 50 + 51 + return { 52 + name: 'svelte', 53 + resolveId(id) { 54 + return stylesheets.has(id) ? id : null; 55 + }, 56 + load(id) { 57 + const css = stylesheets.get(id); 58 + if (css !== undefined) { 59 + this.addWatchFile(id.slice(0, -4)); 60 + return { code: css }; 61 + } 62 + 63 + return null; 64 + }, 65 + transform(source, id) { 66 + if (!filter(id)) { 67 + return null; 68 + } 69 + 70 + const result = compileSvelte(source, { 71 + generate: 'server', 72 + css: 'external', 73 + cssHash({ hash, filename }) { 74 + const prefix = `github:mary-ext/bluesky-post-embed/`; 75 + return `s-` + hash(prefix + path.relative(__dirname, filename)); 76 + }, 77 + runes: true, 78 + filename: id, 79 + }); 80 + 81 + { 82 + const { js, css, warnings } = result; 83 + 84 + let jsCode = js.code; 85 + 86 + if (css) { 87 + const cssId = `${id}.css`; 88 + jsCode = jsCode + `\nimport ${JSON.stringify(cssId)};\n`; 89 + stylesheets.set(cssId, css.code); 90 + } 91 + 92 + for (const warn of warnings) { 93 + if (warn.code === 'state_referenced_locally') { 94 + continue; 95 + } 96 + this.warn(warn); 97 + } 98 + 99 + return { code: jsCode }; 100 + } 101 + }, 102 + }; 103 + }
+1
packages/bluesky-profile-card-embed/.gitignore
··· 1 + themes/
+81
packages/bluesky-profile-card-embed/README.md
··· 1 + # &lt;bluesky-profile-card-embed> 2 + 3 + A custom element for embedding Bluesky profile cards. 4 + 5 + ## Installation 6 + 7 + ### via npm 8 + 9 + ``` 10 + npm install bluesky-profile-card-embed 11 + ``` 12 + 13 + then, import the package on your app. 14 + 15 + ```js 16 + import 'bluesky-profile-card-embed'; 17 + 18 + import 'bluesky-profile-card-embed/style.css'; 19 + import 'bluesky-profile-card-embed/themes/light.css'; 20 + ``` 21 + 22 + ## Usage 23 + 24 + ```html 25 + <bluesky-profile-card actor="did:plc:2gkh62xvzokhlf6li4ol3b3d"> 26 + <a 27 + target="_blank" 28 + href="https://bsky.app/profile/did:plc:2gkh62xvzokhlf6li4ol3b3d" 29 + class="bluesky-profile-card-fallback" 30 + > 31 + @patak.dev's Bluesky profile 32 + </a> 33 + </bluesky-profile-card> 34 + ``` 35 + 36 + ### Attributes 37 + 38 + - `actor` **Required** 39 + DID or handle of the account 40 + - `allow-unauthenticated` **Optional** 41 + Whether to allow unauthenticated viewing 42 + - `service-uri` **Optional** 43 + URL to an AppView service, defaults to `https://public.api.bsky.app` 44 + 45 + ### Events 46 + 47 + - `loaded` 48 + Fired when the embed has successfully loaded the profile card 49 + - `error` 50 + Fired when the embed fails to load the profile card 51 + 52 + ## SSR usage 53 + 54 + The embeds are powered by a static HTML renderer, this renderer can be used directly in your 55 + server-rendering framework of choice for a zero-JS experience. 56 + 57 + ```tsx 58 + import { fetchProfileCard, renderProfileCard } from 'bluesky-profile-card-embed/core'; 59 + 60 + import 'bluesky-profile-card-embed/style.css'; 61 + import 'bluesky-profile-card-embed/themes/light.css'; 62 + 63 + // fetch the profile 64 + const controller = new AbortController(); 65 + const data = await fetchProfileCard({ 66 + actor: `did:plc:ragtjsm2j2vknwkz3zp4oxrd`, 67 + signal: controller.signal, 68 + }); 69 + 70 + // render the profile 71 + const html = renderProfileCard(data); 72 + return ( 73 + <bluesky-profile-card 74 + actor={data.profile?.did} 75 + dangerouslySetInnerHTML={{ __html: html }} 76 + ></bluesky-profile-card> 77 + ); 78 + ``` 79 + 80 + Check out examples for [Astro](https://github.com/mary-ext/bluesky-embed-astro) and 81 + [SvelteKit](https://github.com/mary-ext/bluesky-embed-sveltekit).
+11
packages/bluesky-profile-card-embed/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>Bluesky profile card embed</title> 7 + </head> 8 + <body> 9 + <script type="module" src="./src/main.tsx"></script> 10 + </body> 11 + </html>
+38
packages/bluesky-profile-card-embed/lib/bluesky-profile-card.svelte
··· 1 + <script lang="ts"> 2 + import EmbedFrame from 'internal/components/embed-frame.svelte'; 3 + import ProfileCard from 'internal/components/profile-card.svelte'; 4 + 5 + import type { ProfileCardData } from 'internal/types/profile-card.js'; 6 + import { NO_UNAUTHENTICATED_LABEL } from 'internal/utils/constants.js'; 7 + 8 + const { profile, allowUnauthenticated }: ProfileCardData = $props(); 9 + 10 + const isPwiForbidden = 11 + !allowUnauthenticated && profile?.labels?.some((label) => label.val === NO_UNAUTHENTICATED_LABEL); 12 + </script> 13 + 14 + {#if profile === null} 15 + {@render Message(`The profile can't be found, it may have been deleted.`)} 16 + {:else if isPwiForbidden} 17 + {@render Message(`The user has requested for their profile to not be displayed on external sites.`)} 18 + {:else} 19 + <EmbedFrame> 20 + <ProfileCard {profile} /> 21 + </EmbedFrame> 22 + {/if} 23 + 24 + {#snippet Message(msg: string)} 25 + <EmbedFrame> 26 + <div class="message">{msg}</div> 27 + </EmbedFrame> 28 + {/snippet} 29 + 30 + <style> 31 + .message { 32 + margin: 0 auto; 33 + padding: 32px 16px; 34 + max-width: 380px; 35 + color: var(--text-secondary); 36 + text-align: center; 37 + } 38 + </style>
+61
packages/bluesky-profile-card-embed/lib/core.ts
··· 1 + import '@atcute/bluesky/lexicons'; 2 + 3 + import { simpleFetchHandler, XRPC, XRPCError } from '@atcute/client'; 4 + import type { At } from '@atcute/client/lexicons'; 5 + import { render } from 'svelte/server'; 6 + 7 + import type { ProfileCardData } from 'internal/types/profile-card.js'; 8 + import { DEFAULT_APPVIEW_URL } from 'internal/utils/constants.js'; 9 + 10 + import BlueskyProfileCard from './bluesky-profile-card.svelte'; 11 + 12 + export type { ProfileCardData }; 13 + 14 + export interface ProfileCardFetchOptions { 15 + /** 16 + * Handle or DID identifier of the user 17 + */ 18 + actor: string; 19 + /** 20 + * Abort signal to cancel the request 21 + */ 22 + signal?: AbortSignal; 23 + /** 24 + * Allow unauthenticated viewing 25 + * @default false 26 + */ 27 + allowUnauthenticated?: boolean; 28 + /** 29 + * AppView service to use 30 + * @default "https://public.api.bsky.app" 31 + */ 32 + serviceUri?: string; 33 + } 34 + 35 + export const fetchProfileCard = async (opts: ProfileCardFetchOptions): Promise<ProfileCardData> => { 36 + const actor = opts.actor; 37 + const allowUnauthenticated = opts.allowUnauthenticated ?? false; 38 + 39 + const rpc = new XRPC({ handler: simpleFetchHandler({ service: opts.serviceUri ?? DEFAULT_APPVIEW_URL }) }); 40 + 41 + const { data: profile } = await rpc 42 + .get('app.bsky.actor.getProfile', { 43 + signal: opts.signal, 44 + params: { actor: actor as At.Identifier }, 45 + }) 46 + .catch((err) => { 47 + if (err instanceof XRPCError) { 48 + if (err.kind === 'InvalidRequest' && err.description === 'Profile not found') { 49 + return { data: null }; 50 + } 51 + } 52 + 53 + return Promise.reject(err); 54 + }); 55 + 56 + return { profile: profile, allowUnauthenticated }; 57 + }; 58 + 59 + export const renderProfileCard = (data: ProfileCardData): string => { 60 + return render(BlueskyProfileCard, { props: data }).body; 61 + };
+56
packages/bluesky-profile-card-embed/lib/wc.ts
··· 1 + import { fetchProfileCard, renderProfileCard } from './core'; 2 + 3 + export class BlueskyProfileCard extends HTMLElement { 4 + connectedCallback() { 5 + this.load().then( 6 + () => this.dispatchEvent(new CustomEvent('loaded')), 7 + (err) => { 8 + const defaulted = this.dispatchEvent(new CustomEvent('error', { detail: err })); 9 + if (defaulted) { 10 + throw err; 11 + } 12 + }, 13 + ); 14 + } 15 + 16 + async load() { 17 + const actor = this.getAttribute('actor')!; 18 + const serviceUri = this.getAttribute('service-uri') || undefined; 19 + const allowUnauthenticated = this.getAttribute('allow-unauthenticated') !== null; 20 + const silent = this.getAttribute('silent') !== null; 21 + 22 + const data = await fetchProfileCard({ actor, allowUnauthenticated, serviceUri }).catch((error) => { 23 + if (silent) { 24 + console.warn('Failed to fetch profile card:', error); 25 + return null; 26 + } 27 + throw error; 28 + }); 29 + 30 + if (data === null) { 31 + return; 32 + } 33 + 34 + const html = renderProfileCard(data); 35 + 36 + const root = this.shadowRoot; 37 + 38 + if (!root) { 39 + this.innerHTML = html; 40 + } else { 41 + const template = document.createElement('template'); 42 + template.innerHTML = html; 43 + 44 + const fragment = template.content; 45 + const slot = root.querySelector('slot'); 46 + 47 + if (slot) { 48 + slot.replaceWith(fragment); 49 + } else { 50 + root.appendChild(fragment); 51 + } 52 + } 53 + } 54 + } 55 + 56 + customElements.define('bluesky-profile-card', BlueskyProfileCard);
+45
packages/bluesky-profile-card-embed/package.json
··· 1 + { 2 + "type": "module", 3 + "name": "bluesky-profile-card-embed", 4 + "description": "Custom element for embedding Bluesky profile cards", 5 + "version": "1.0.1", 6 + "author": "externdefs", 7 + "license": "MIT", 8 + "repository": { 9 + "type": "git", 10 + "url": "https://github.com/mary-ext/bluesky-embed", 11 + "directory": "packages/bluesky-profile-card-embed" 12 + }, 13 + "files": [ 14 + "dist/", 15 + "themes/" 16 + ], 17 + "exports": { 18 + ".": "./dist/wc.js", 19 + "./core": "./dist/core.js", 20 + "./style.css": "./dist/core.css", 21 + "./themes/*": "./themes/*" 22 + }, 23 + "scripts": { 24 + "dev": "vite", 25 + "build": "pnpm run check && vite build && rsync -aHAX --delete ../../themes/ themes/", 26 + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json", 27 + "prepack": "pnpm run build" 28 + }, 29 + "dependencies": { 30 + "@atcute/bluesky": "^2.1.1", 31 + "@atcute/bluesky-richtext-parser": "^1.0.7", 32 + "@atcute/client": "^3.1.0" 33 + }, 34 + "devDependencies": { 35 + "@preact/preset-vite": "^2.10.2", 36 + "@tsconfig/svelte": "^5.0.6", 37 + "@types/node": "^24.10.1", 38 + "internal": "workspace:^", 39 + "preact": "^10.28.0", 40 + "svelte": "catalog:", 41 + "svelte-check": "^4.3.4", 42 + "vite": "^7.2.6", 43 + "vite-plugin-dts": "^4.5.4" 44 + } 45 + }
+58
packages/bluesky-profile-card-embed/src/app.tsx
··· 1 + import { useEffect, useMemo, useState } from 'preact/hooks'; 2 + 3 + import type { ProfileCardData } from 'internal/types/profile-card.js'; 4 + import { fetchProfileCard, renderProfileCard } from '../lib/core'; 5 + 6 + const actor = `patak.dev`; 7 + 8 + const App = () => { 9 + const [state, setState] = useState<{ actor: string; data: ProfileCardData }>(); 10 + 11 + useEffect(() => { 12 + if (state && state.actor === actor) { 13 + return; 14 + } 15 + 16 + const controller = new AbortController(); 17 + const promise = fetchProfileCard({ 18 + actor: actor, 19 + signal: controller.signal, 20 + allowUnauthenticated: true, 21 + }); 22 + 23 + promise.then((data) => { 24 + setState({ actor, data }); 25 + }); 26 + 27 + return () => { 28 + controller.abort(); 29 + }; 30 + }, [actor]); 31 + 32 + return <div class="app">{state && <BlueskyProfileCard data={state.data} />}</div>; 33 + }; 34 + 35 + export default App; 36 + 37 + const BlueskyProfileCard = ({ data }: { data: ProfileCardData }) => { 38 + const html = useMemo(() => renderProfileCard(data), [data, renderProfileCard]); 39 + 40 + return ( 41 + <bluesky-profile-card 42 + actor={data.profile?.did} 43 + dangerouslySetInnerHTML={{ __html: html }} 44 + ></bluesky-profile-card> 45 + ); 46 + }; 47 + 48 + declare module 'preact' { 49 + namespace JSX { 50 + interface BlueskyProfileCardAttributes extends HTMLAttributes<HTMLElement> { 51 + actor?: string; 52 + } 53 + 54 + interface IntrinsicElements { 55 + 'bluesky-profile-card': BlueskyProfileCardAttributes; 56 + } 57 + } 58 + }
+8
packages/bluesky-profile-card-embed/src/main.tsx
··· 1 + import { render } from 'preact'; 2 + 3 + import App from './app'; 4 + 5 + import '../../../themes/light.css'; 6 + import './styles/main.css'; 7 + 8 + render(<App />, document.body);
+8
packages/bluesky-profile-card-embed/src/styles/main.css
··· 1 + @import './normalize.css'; 2 + 3 + .app { 4 + margin: 0 auto; 5 + padding: 36px 16px; 6 + width: 100%; 7 + max-width: calc(550px + (16 * 2px)); 8 + }
+199
packages/bluesky-profile-card-embed/src/styles/normalize.css
··· 1 + /*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */ 2 + 3 + /* 4 + Document 5 + ======== 6 + */ 7 + 8 + /** 9 + Use a better box model (opinionated). 10 + */ 11 + 12 + *, 13 + ::before, 14 + ::after { 15 + box-sizing: border-box; 16 + } 17 + 18 + html { 19 + line-height: 1.15; /* 1. Correct the line height in all browsers. */ 20 + /* Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) */ 21 + font-family: 'Inter', 'Roboto', ui-sans-serif, sans-serif, 'Noto Color Emoji', 'Twemoji Mozilla'; 22 + -webkit-text-size-adjust: 100%; /* 2. Prevent adjustments of font size after orientation changes in iOS. */ 23 + tab-size: 4; /* 3. Use a more readable tab size (opinionated). */ 24 + } 25 + 26 + /* 27 + Sections 28 + ======== 29 + */ 30 + 31 + body { 32 + margin: 0; /* Remove the margin in all browsers. */ 33 + } 34 + 35 + /* 36 + Text-level semantics 37 + ==================== 38 + */ 39 + 40 + /** 41 + Add the correct font weight in Chrome and Safari. 42 + */ 43 + 44 + b, 45 + strong { 46 + font-weight: bolder; 47 + } 48 + 49 + /** 50 + 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) 51 + 2. Correct the odd 'em' font sizing in all browsers. 52 + */ 53 + 54 + code, 55 + kbd, 56 + samp, 57 + pre { 58 + font-size: 1em; /* 2 */ 59 + font-family: 'JetBrains Mono NL', ui-monospace, monospace; /* 1 */ 60 + } 61 + 62 + /** 63 + Add the correct font size in all browsers. 64 + */ 65 + 66 + small { 67 + font-size: 80%; 68 + } 69 + 70 + /** 71 + Prevent 'sub' and 'sup' elements from affecting the line height in all browsers. 72 + */ 73 + 74 + sub, 75 + sup { 76 + position: relative; 77 + vertical-align: baseline; 78 + font-size: 75%; 79 + line-height: 0; 80 + } 81 + 82 + sub { 83 + bottom: -0.25em; 84 + } 85 + 86 + sup { 87 + top: -0.5em; 88 + } 89 + 90 + /* 91 + Tabular data 92 + ============ 93 + */ 94 + 95 + /** 96 + Correct table border color inheritance in Chrome and Safari. (https://issues.chromium.org/issues/40615503, https://bugs.webkit.org/show_bug.cgi?id=195016) 97 + */ 98 + 99 + table { 100 + border-color: currentcolor; 101 + } 102 + 103 + /* 104 + Forms 105 + ===== 106 + */ 107 + 108 + /** 109 + 1. Change the font styles in all browsers. 110 + 2. Remove the margin in Firefox and Safari. 111 + */ 112 + 113 + button, 114 + input, 115 + optgroup, 116 + select, 117 + textarea { 118 + margin: 0; /* 2 */ 119 + font-size: 100%; /* 1 */ 120 + line-height: 1.15; /* 1 */ 121 + font-family: inherit; /* 1 */ 122 + } 123 + 124 + /** 125 + Correct the inability to style clickable types in iOS and Safari. 126 + */ 127 + 128 + button, 129 + [type='button'], 130 + [type='reset'], 131 + [type='submit'] { 132 + -webkit-appearance: button; 133 + } 134 + 135 + /** 136 + Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers. 137 + */ 138 + 139 + legend { 140 + padding: 0; 141 + } 142 + 143 + /** 144 + Add the correct vertical alignment in Chrome and Firefox. 145 + */ 146 + 147 + progress { 148 + vertical-align: baseline; 149 + } 150 + 151 + /** 152 + Correct the cursor style of increment and decrement buttons in Safari. 153 + */ 154 + 155 + ::-webkit-inner-spin-button, 156 + ::-webkit-outer-spin-button { 157 + height: auto; 158 + } 159 + 160 + /** 161 + 1. Correct the odd appearance in Chrome and Safari. 162 + 2. Correct the outline style in Safari. 163 + */ 164 + 165 + [type='search'] { 166 + -webkit-appearance: textfield; /* 1 */ 167 + outline-offset: -2px; /* 2 */ 168 + } 169 + 170 + /** 171 + Remove the inner padding in Chrome and Safari on macOS. 172 + */ 173 + 174 + ::-webkit-search-decoration { 175 + -webkit-appearance: none; 176 + } 177 + 178 + /** 179 + 1. Correct the inability to style clickable types in iOS and Safari. 180 + 2. Change font properties to 'inherit' in Safari. 181 + */ 182 + 183 + ::-webkit-file-upload-button { 184 + -webkit-appearance: button; /* 1 */ 185 + font: inherit; /* 2 */ 186 + } 187 + 188 + /* 189 + Interactive 190 + =========== 191 + */ 192 + 193 + /* 194 + Add the correct display in Chrome and Safari. 195 + */ 196 + 197 + summary { 198 + display: list-item; 199 + }
+8
packages/bluesky-profile-card-embed/svelte.config.js
··· 1 + export default { 2 + compilerOptions: { 3 + warningFilter: (warning) => { 4 + if (warning.code === 'state_referenced_locally') return false; 5 + return true; 6 + }, 7 + }, 8 + };
+14
packages/bluesky-profile-card-embed/tsconfig.build.json
··· 1 + { 2 + "extends": "@tsconfig/svelte/tsconfig.json", 3 + "compilerOptions": { 4 + "types": [], 5 + "target": "ESNext", 6 + "useDefineForClassFields": true, 7 + "module": "ESNext", 8 + "resolveJsonModule": true, 9 + "isolatedModules": true, 10 + "moduleDetection": "force", 11 + "noEmit": true, 12 + }, 13 + "include": ["lib"], 14 + }
+17
packages/bluesky-profile-card-embed/tsconfig.json
··· 1 + { 2 + "extends": "@tsconfig/svelte/tsconfig.json", 3 + "compilerOptions": { 4 + "types": [], 5 + "target": "ESNext", 6 + "useDefineForClassFields": true, 7 + "module": "ESNext", 8 + "resolveJsonModule": true, 9 + "isolatedModules": true, 10 + "moduleDetection": "force", 11 + "noEmit": true, 12 + "jsx": "react-jsx", 13 + "jsxImportSource": "preact", 14 + }, 15 + "include": ["lib", "src"], 16 + "references": [{ "path": "./tsconfig.node.json" }], 17 + }
+14
packages/bluesky-profile-card-embed/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "types": ["node"], 5 + "skipLibCheck": true, 6 + "module": "ESNext", 7 + "target": "ESNext", 8 + "moduleResolution": "Bundler", 9 + "strict": true, 10 + "noEmit": true, 11 + "noUncheckedSideEffectImports": true, 12 + }, 13 + "include": ["vite.config.ts"], 14 + }
+103
packages/bluesky-profile-card-embed/vite.config.ts
··· 1 + import * as path from 'node:path'; 2 + 3 + import { compile as compileSvelte } from 'svelte/compiler'; 4 + import { type Plugin, createFilter, defineConfig } from 'vite'; 5 + 6 + import preact from '@preact/preset-vite'; 7 + import dts from 'vite-plugin-dts'; 8 + 9 + export default defineConfig({ 10 + base: './', 11 + build: { 12 + outDir: 'dist/', 13 + target: 'esnext', 14 + minify: false, 15 + cssMinify: false, 16 + cssCodeSplit: true, 17 + lib: { 18 + entry: { 19 + core: 'lib/core.ts', 20 + wc: 'lib/wc.ts', 21 + }, 22 + formats: ['es'], 23 + }, 24 + rollupOptions: { 25 + external: ['@atcute/client', '@atcute/bluesky-richtext-parser'], 26 + }, 27 + }, 28 + esbuild: { 29 + target: 'esnext', 30 + }, 31 + plugins: [ 32 + svelte(), 33 + preact(), 34 + dts({ 35 + rollupTypes: true, 36 + tsconfigPath: 'tsconfig.build.json', 37 + beforeWriteFile(filePath, content) { 38 + if (filePath.endsWith('/core.d.ts')) { 39 + // Make sure the relevant types are present 40 + return { content: `import '@atcute/bluesky/lexicons';\n${content}` }; 41 + } 42 + }, 43 + }), 44 + ], 45 + }); 46 + 47 + function svelte(): Plugin { 48 + const filter = createFilter('**/*.svelte'); 49 + const stylesheets = new Map<string, string>(); 50 + 51 + return { 52 + name: 'svelte', 53 + resolveId(id) { 54 + return stylesheets.has(id) ? id : null; 55 + }, 56 + load(id) { 57 + const css = stylesheets.get(id); 58 + if (css !== undefined) { 59 + this.addWatchFile(id.slice(0, -4)); 60 + return { code: css }; 61 + } 62 + 63 + return null; 64 + }, 65 + transform(source, id) { 66 + if (!filter(id)) { 67 + return null; 68 + } 69 + 70 + const result = compileSvelte(source, { 71 + generate: 'server', 72 + css: 'external', 73 + cssHash({ hash, filename }) { 74 + const prefix = `github:mary-ext/bluesky-profile-card-embed/`; 75 + return `s-` + hash(prefix + path.relative(__dirname, filename)); 76 + }, 77 + runes: true, 78 + filename: id, 79 + }); 80 + 81 + { 82 + const { js, css, warnings } = result; 83 + 84 + let jsCode = js.code; 85 + 86 + if (css) { 87 + const cssId = `${id}.css`; 88 + jsCode = jsCode + `\nimport ${JSON.stringify(cssId)};\n`; 89 + stylesheets.set(cssId, css.code); 90 + } 91 + 92 + for (const warn of warnings) { 93 + if (warn.code === 'state_referenced_locally') { 94 + continue; 95 + } 96 + this.warn(warn); 97 + } 98 + 99 + return { code: jsCode }; 100 + } 101 + }, 102 + }; 103 + }
+1
packages/bluesky-profile-feed-embed/.gitignore
··· 1 + themes/
+84
packages/bluesky-profile-feed-embed/README.md
··· 1 + # &lt;bluesky-profile-feed-embed> 2 + 3 + A custom element for embedding Bluesky profile feeds. 4 + 5 + ## Installation 6 + 7 + ### via npm 8 + 9 + ``` 10 + npm install bluesky-profile-feed-embed 11 + ``` 12 + 13 + then, import the package on your app. 14 + 15 + ```js 16 + import 'bluesky-profile-feed-embed'; 17 + 18 + import 'bluesky-profile-feed-embed/style.css'; 19 + import 'bluesky-profile-feed-embed/themes/light.css'; 20 + ``` 21 + 22 + ## Usage 23 + 24 + ```html 25 + <bluesky-profile-feed actor="did:plc:ragtjsm2j2vknwkz3zp4oxrd" include-pins> 26 + <a 27 + target="_blank" 28 + href="https://bsky.app/profile/did:plc:ragtjsm2j2vknwkz3zp4oxrd" 29 + class="bluesky-profile-feed-fallback" 30 + > 31 + Posts by Paul Frazee (@pfrazee.com) 32 + </a> 33 + </bluesky-profile-feed> 34 + ``` 35 + 36 + ### Attributes 37 + 38 + - `actor` **Required** 39 + DID or handle of the account 40 + - `include-pins` **Optional** 41 + Whether to show pinned posts 42 + - `allow-unauthenticated` **Optional** 43 + Whether to allow unauthenticated viewing 44 + - `service-uri` **Optional** 45 + URL to an AppView service, defaults to `https://public.api.bsky.app` 46 + 47 + ### Events 48 + 49 + - `loaded` 50 + Fired when the embed has successfully loaded the post 51 + - `error` 52 + Fired when the embed fails to load the post 53 + 54 + ## SSR usage 55 + 56 + The embeds are powered by a static HTML renderer, this renderer can be used directly in your 57 + server-rendering framework of choice for a zero-JS experience. 58 + 59 + ```tsx 60 + import { fetchProfileFeed, renderProfileFeed } from 'bluesky-profile-feed-embed/core'; 61 + 62 + import 'bluesky-post-embed/style.css'; 63 + import 'bluesky-post-embed/themes/light.css'; 64 + 65 + // fetch the profile 66 + const controller = new AbortController(); 67 + const data = await fetchProfileFeed({ 68 + actor: `did:plc:ragtjsm2j2vknwkz3zp4oxrd`, 69 + includePins: true, 70 + signal: controller.signal, 71 + }); 72 + 73 + // render the profile 74 + const html = renderProfileFeed(data); 75 + return ( 76 + <bluesky-profile-feed 77 + src={data.thread?.post.uri} 78 + dangerouslySetInnerHTML={{ __html: html }} 79 + ></bluesky-profile-feed> 80 + ); 81 + ``` 82 + 83 + Check out examples for [Astro](https://github.com/mary-ext/bluesky-embed-astro) and 84 + [SvelteKit](https://github.com/mary-ext/bluesky-embed-sveltekit).
+100
packages/bluesky-profile-feed-embed/lib/bluesky-profile-feed.svelte
··· 1 + <script lang="ts"> 2 + import EmbedFrame from 'internal/components/embed-frame.svelte'; 3 + import FeedPost from 'internal/components/feed-post.svelte'; 4 + import ProfileFeedHeader from 'internal/components/profile-feed-header.svelte'; 5 + 6 + import type { ProfileFeedData } from 'internal/types/profile-feed.js'; 7 + import { NO_UNAUTHENTICATED_LABEL } from 'internal/utils/constants.js'; 8 + 9 + const { profile, feed, allowUnauthenticated }: ProfileFeedData = $props(); 10 + 11 + const isPwiForbidden = 12 + !allowUnauthenticated && profile?.labels?.some((label) => label.val === NO_UNAUTHENTICATED_LABEL); 13 + 14 + const items = feed.filter((item) => { 15 + if (!profile) { 16 + return false; 17 + } 18 + 19 + const reason = item.reason; 20 + if (reason) { 21 + if (reason.$type === 'app.bsky.feed.defs#reasonPin') { 22 + return true; 23 + } 24 + 25 + if (reason.$type === 'app.bsky.feed.defs#reasonRepost') { 26 + const author = item.post.author; 27 + 28 + if (author.did !== profile.did) { 29 + return ( 30 + allowUnauthenticated || !author.labels?.some((label) => label.val === NO_UNAUTHENTICATED_LABEL) 31 + ); 32 + } 33 + 34 + return true; 35 + } 36 + 37 + // Don't show anything we don't recognize 38 + return false; 39 + } 40 + 41 + return !item.reply; 42 + }); 43 + </script> 44 + 45 + {#if profile === null} 46 + {@render Message(`The profile can't be found, it may have been deleted.`)} 47 + {:else if isPwiForbidden} 48 + {@render Message(`The user has requested for their posts to not be displayed on external sites.`)} 49 + {:else} 50 + <EmbedFrame> 51 + <ProfileFeedHeader {profile} /> 52 + 53 + {#if items.length > 0} 54 + <div class="feed"> 55 + {#each items as item} 56 + <FeedPost {item} /> 57 + {/each} 58 + 59 + <div class="end-marker"> 60 + <div class="dot"></div> 61 + </div> 62 + </div> 63 + {:else} 64 + <div class="message">This user has not made any posts.</div> 65 + {/if} 66 + </EmbedFrame> 67 + {/if} 68 + 69 + {#snippet Message(msg: string)} 70 + <EmbedFrame> 71 + <div class="message">{msg}</div> 72 + </EmbedFrame> 73 + {/snippet} 74 + 75 + <style> 76 + .message { 77 + margin: 0 auto; 78 + padding: 32px 16px; 79 + max-width: 380px; 80 + color: var(--text-secondary); 81 + text-align: center; 82 + } 83 + 84 + .feed { 85 + max-height: var(--max-feed-height); 86 + overflow-y: auto; 87 + } 88 + .end-marker { 89 + display: grid; 90 + place-items: center; 91 + height: 48px; 92 + 93 + .dot { 94 + border-radius: 50%; 95 + background: var(--text-secondary); 96 + width: 4px; 97 + height: 4px; 98 + } 99 + } 100 + </style>
+87
packages/bluesky-profile-feed-embed/lib/core.ts
··· 1 + import '@atcute/bluesky/lexicons'; 2 + 3 + import { simpleFetchHandler, XRPC, XRPCError } from '@atcute/client'; 4 + import type { At } from '@atcute/client/lexicons'; 5 + import { render } from 'svelte/server'; 6 + 7 + import type { ProfileFeedData } from 'internal/types/profile-feed.js'; 8 + import { DEFAULT_APPVIEW_URL } from 'internal/utils/constants.js'; 9 + 10 + import BlueskyProfileFeed from './bluesky-profile-feed.svelte'; 11 + 12 + export type { ProfileFeedData }; 13 + 14 + export interface ProfileFeedFetchOptions { 15 + /** 16 + * Handle or DID identifier of the user 17 + */ 18 + actor: string; 19 + /** 20 + * Abort signal to cancel the request 21 + */ 22 + signal?: AbortSignal; 23 + /** 24 + * Include pinned posts 25 + * @default false 26 + */ 27 + includePins?: boolean; 28 + /** 29 + * Allow unauthenticated viewing 30 + * @default false 31 + */ 32 + allowUnauthenticated?: boolean; 33 + /** 34 + * AppView service to use 35 + * @default "https://public.api.bsky.app" 36 + */ 37 + serviceUri?: string; 38 + } 39 + 40 + export const fetchProfileFeed = async (opts: ProfileFeedFetchOptions): Promise<ProfileFeedData> => { 41 + const actor = opts.actor as At.Identifier; 42 + const allowUnauthenticated = opts.allowUnauthenticated ?? false; 43 + 44 + const rpc = new XRPC({ handler: simpleFetchHandler({ service: opts.serviceUri ?? DEFAULT_APPVIEW_URL }) }); 45 + 46 + const [{ data: profile }, { data: timeline }] = await Promise.all([ 47 + rpc 48 + .get('app.bsky.actor.getProfile', { 49 + signal: opts.signal, 50 + params: { actor: actor as At.Identifier }, 51 + }) 52 + .catch((err) => { 53 + if (err instanceof XRPCError) { 54 + if (err.kind === 'InvalidRequest' && err.description === 'Profile not found') { 55 + return { data: null }; 56 + } 57 + } 58 + 59 + return Promise.reject(err); 60 + }), 61 + rpc 62 + .get('app.bsky.feed.getAuthorFeed', { 63 + signal: opts.signal, 64 + params: { 65 + actor, 66 + filter: 'posts_no_replies', 67 + includePins: opts.includePins, 68 + limit: 30, 69 + }, 70 + }) 71 + .catch((err) => { 72 + if (err instanceof XRPCError) { 73 + if (err.kind === 'InvalidRequest' && err.description === 'Profile not found') { 74 + return { data: { feed: [] } }; 75 + } 76 + } 77 + 78 + return Promise.reject(err); 79 + }), 80 + ]); 81 + 82 + return { profile: profile, feed: timeline.feed, allowUnauthenticated }; 83 + }; 84 + 85 + export const renderProfileFeed = (data: ProfileFeedData): string => { 86 + return render(BlueskyProfileFeed, { props: data }).body; 87 + };
+59
packages/bluesky-profile-feed-embed/lib/wc.ts
··· 1 + import { fetchProfileFeed, renderProfileFeed } from './core'; 2 + 3 + export class BlueskyProfileFeed extends HTMLElement { 4 + connectedCallback() { 5 + this.load().then( 6 + () => this.dispatchEvent(new CustomEvent('loaded')), 7 + (err) => { 8 + const defaulted = this.dispatchEvent(new CustomEvent('error', { detail: err })); 9 + if (defaulted) { 10 + throw err; 11 + } 12 + }, 13 + ); 14 + } 15 + 16 + async load() { 17 + const actor = this.getAttribute('actor')!; 18 + const serviceUri = this.getAttribute('service-uri') || undefined; 19 + const allowUnauthenticated = this.getAttribute('allow-unauthenticated') !== null; 20 + const includePins = this.getAttribute('include-pins') !== null; 21 + const silent = this.getAttribute('silent') !== null; 22 + 23 + const data = await fetchProfileFeed({ actor, allowUnauthenticated, includePins, serviceUri }).catch( 24 + (error) => { 25 + if (silent) { 26 + console.warn('Failed to fetch profile feed:', error); 27 + return null; 28 + } 29 + throw error; 30 + }, 31 + ); 32 + 33 + if (data === null) { 34 + return; 35 + } 36 + 37 + const html = renderProfileFeed(data); 38 + 39 + const root = this.shadowRoot; 40 + 41 + if (!root) { 42 + this.innerHTML = html; 43 + } else { 44 + const template = document.createElement('template'); 45 + template.innerHTML = html; 46 + 47 + const fragment = template.content; 48 + const slot = root.querySelector('slot'); 49 + 50 + if (slot) { 51 + slot.replaceWith(fragment); 52 + } else { 53 + root.appendChild(fragment); 54 + } 55 + } 56 + } 57 + } 58 + 59 + customElements.define('bluesky-profile-feed', BlueskyProfileFeed);
+43
packages/bluesky-profile-feed-embed/package.json
··· 1 + { 2 + "type": "module", 3 + "name": "bluesky-profile-feed-embed", 4 + "description": "Custom element for embedding Bluesky profile feeds", 5 + "version": "1.0.4", 6 + "author": "externdefs", 7 + "license": "MIT", 8 + "repository": { 9 + "type": "git", 10 + "url": "https://github.com/mary-ext/bluesky-embed", 11 + "directory": "packages/bluesky-profile-feed-embed" 12 + }, 13 + "files": [ 14 + "dist/", 15 + "themes/" 16 + ], 17 + "exports": { 18 + ".": "./dist/wc.js", 19 + "./core": "./dist/core.js", 20 + "./style.css": "./dist/core.css", 21 + "./themes/*": "./themes/*" 22 + }, 23 + "scripts": { 24 + "dev": "vite", 25 + "build": "pnpm run check && vite build && rsync -aHAX --delete ../../themes/ themes/", 26 + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json", 27 + "prepack": "pnpm run build" 28 + }, 29 + "dependencies": { 30 + "@atcute/bluesky": "^2.1.1", 31 + "@atcute/bluesky-richtext-segmenter": "^2.0.4", 32 + "@atcute/client": "^3.1.0" 33 + }, 34 + "devDependencies": { 35 + "@tsconfig/svelte": "^5.0.6", 36 + "@types/node": "^24.10.1", 37 + "internal": "workspace:^", 38 + "svelte": "catalog:", 39 + "svelte-check": "^4.3.4", 40 + "vite": "^7.2.6", 41 + "vite-plugin-dts": "^4.5.4" 42 + } 43 + }
+8
packages/bluesky-profile-feed-embed/svelte.config.js
··· 1 + export default { 2 + compilerOptions: { 3 + warningFilter: (warning) => { 4 + if (warning.code === 'state_referenced_locally') return false; 5 + return true; 6 + }, 7 + }, 8 + };
+15
packages/bluesky-profile-feed-embed/tsconfig.json
··· 1 + { 2 + "extends": "@tsconfig/svelte/tsconfig.json", 3 + "compilerOptions": { 4 + "types": [], 5 + "target": "ESNext", 6 + "useDefineForClassFields": true, 7 + "module": "ESNext", 8 + "resolveJsonModule": true, 9 + "isolatedModules": true, 10 + "moduleDetection": "force", 11 + "noEmit": true, 12 + }, 13 + "include": ["lib"], 14 + "references": [{ "path": "./tsconfig.node.json" }], 15 + }
+14
packages/bluesky-profile-feed-embed/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "types": ["node"], 5 + "skipLibCheck": true, 6 + "module": "ESNext", 7 + "target": "ESNext", 8 + "moduleResolution": "Bundler", 9 + "strict": true, 10 + "noEmit": true, 11 + "noUncheckedSideEffectImports": true, 12 + }, 13 + "include": ["vite.config.ts"], 14 + }
+100
packages/bluesky-profile-feed-embed/vite.config.ts
··· 1 + import * as path from 'node:path'; 2 + 3 + import { compile as compileSvelte } from 'svelte/compiler'; 4 + import { type Plugin, createFilter, defineConfig } from 'vite'; 5 + 6 + import dts from 'vite-plugin-dts'; 7 + 8 + export default defineConfig({ 9 + base: './', 10 + build: { 11 + outDir: 'dist/', 12 + target: 'esnext', 13 + minify: false, 14 + cssMinify: false, 15 + cssCodeSplit: true, 16 + lib: { 17 + entry: { 18 + core: 'lib/core.ts', 19 + wc: 'lib/wc.ts', 20 + }, 21 + formats: ['es'], 22 + }, 23 + rollupOptions: { 24 + external: ['@atcute/client', '@atcute/bluesky-richtext-segmenter'], 25 + }, 26 + }, 27 + esbuild: { 28 + target: 'esnext', 29 + }, 30 + plugins: [ 31 + svelte(), 32 + dts({ 33 + rollupTypes: true, 34 + beforeWriteFile(filePath, content) { 35 + if (filePath.endsWith('/core.d.ts')) { 36 + // Make sure the relevant types are present 37 + return { content: `import '@atcute/bluesky/lexicons';\n${content}` }; 38 + } 39 + }, 40 + }), 41 + ], 42 + }); 43 + 44 + function svelte(): Plugin { 45 + const filter = createFilter('**/*.svelte'); 46 + const stylesheets = new Map<string, string>(); 47 + 48 + return { 49 + name: 'svelte', 50 + resolveId(id) { 51 + return stylesheets.has(id) ? id : null; 52 + }, 53 + load(id) { 54 + const css = stylesheets.get(id); 55 + if (css !== undefined) { 56 + this.addWatchFile(id.slice(0, -4)); 57 + return { code: css }; 58 + } 59 + 60 + return null; 61 + }, 62 + transform(source, id) { 63 + if (!filter(id)) { 64 + return null; 65 + } 66 + 67 + const result = compileSvelte(source, { 68 + generate: 'server', 69 + css: 'external', 70 + cssHash({ hash, filename }) { 71 + const prefix = `github:mary-ext/bluesky-profile-feed-embed/`; 72 + return `s-` + hash(prefix + path.relative(__dirname, filename)); 73 + }, 74 + runes: true, 75 + filename: id, 76 + }); 77 + 78 + { 79 + const { js, css, warnings } = result; 80 + 81 + let jsCode = js.code; 82 + 83 + if (css) { 84 + const cssId = `${id}.css`; 85 + jsCode = jsCode + `\nimport ${JSON.stringify(cssId)};\n`; 86 + stylesheets.set(cssId, css.code); 87 + } 88 + 89 + for (const warn of warnings) { 90 + if (warn.code === 'state_referenced_locally') { 91 + continue; 92 + } 93 + this.warn(warn); 94 + } 95 + 96 + return { code: jsCode }; 97 + } 98 + }, 99 + }; 100 + }
+88
packages/internal/components/content-hider.svelte
··· 1 + <script lang="ts"> 2 + import type { Snippet } from 'svelte'; 3 + import type { LabelDefinition } from '../utils/labels'; 4 + 5 + interface Props { 6 + warning: LabelDefinition | undefined; 7 + children: Snippet; 8 + } 9 + 10 + const { warning, children }: Props = $props(); 11 + </script> 12 + 13 + {#if !warning} 14 + {@render children()} 15 + {:else} 16 + <details class="content-hider"> 17 + <summary class="gate"> 18 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 19 + <path 20 + stroke="currentColor" 21 + stroke-linecap="square" 22 + stroke-width="2" 23 + d="M11 11h1v5m9-4a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" 24 + /> 25 + <path 26 + fill="currentColor" 27 + stroke="currentColor" 28 + stroke-width=".5" 29 + d="M11.5 7.25h-.25v1.5h1.5v-1.5H11.5Z" 30 + /> 31 + </svg> 32 + 33 + <span class="label">{warning.name}</span> 34 + 35 + <span class="action"></span> 36 + </summary> 37 + 38 + {@render children()} 39 + </details> 40 + {/if} 41 + 42 + <style> 43 + .gate { 44 + display: flex; 45 + align-items: center; 46 + gap: 12px; 47 + cursor: pointer; 48 + border: 1px solid var(--divider); 49 + border-radius: 6px; 50 + padding: 0 12px; 51 + height: 44px; 52 + 53 + .content-hider[open] & { 54 + margin-bottom: 12px; 55 + } 56 + 57 + &:hover { 58 + border-color: var(--divider-hover); 59 + } 60 + } 61 + 62 + .icon { 63 + width: 18px; 64 + height: 18px; 65 + color: var(--text-secondary); 66 + } 67 + .label { 68 + flex-grow: 1; 69 + overflow: hidden; 70 + font-weight: 500; 71 + user-select: none; 72 + text-overflow: ellipsis; 73 + } 74 + 75 + .action { 76 + color: var(--text-link); 77 + font-weight: 500; 78 + font-size: calc(var(--font-size) * 0.8125); 79 + line-height: calc(var(--font-size) * 1.25); 80 + 81 + &::before { 82 + content: 'Show'; 83 + } 84 + .content-hider[open] &::before { 85 + content: 'Hide'; 86 + } 87 + } 88 + </style>
+50
packages/internal/components/embed-frame.svelte
··· 1 + <script lang="ts"> 2 + import type { Snippet } from 'svelte'; 3 + 4 + interface Props { 5 + children: Snippet; 6 + } 7 + 8 + const { children }: Props = $props(); 9 + </script> 10 + 11 + <div class="bluesky-embed"> 12 + {@render children()} 13 + </div> 14 + 15 + <style> 16 + .bluesky-embed { 17 + position: relative; 18 + box-sizing: border-box; 19 + margin: 0 auto; 20 + border: 1px solid var(--divider); 21 + border-radius: 8px; 22 + background: var(--background-primary); 23 + min-width: 250px; 24 + max-width: 550px; 25 + overflow: hidden; 26 + color: var(--text-primary); 27 + font-weight: 400; 28 + font-size: calc(var(--font-size) * 0.875); 29 + line-height: calc(var(--font-size) * 1.25); 30 + font-family: var(--font-family); 31 + 32 + :global(:where(*)), 33 + :global(:where(*::before)), 34 + :global(:where(*::after)) { 35 + box-sizing: border-box; 36 + margin: 0; 37 + padding: 0; 38 + } 39 + :global(:where(a)) { 40 + color: inherit; 41 + text-decoration: none; 42 + } 43 + 44 + :global(:where(.icon)) { 45 + flex-shrink: 0; 46 + width: 1em; 47 + height: 1em; 48 + } 49 + } 50 + </style>
+126
packages/internal/components/embeds/embeds.svelte
··· 1 + <script lang="ts" module> 2 + const collectionToLabel = (collection: string): string | null => { 3 + switch (collection) { 4 + case 'app.bsky.feed.post': 5 + return 'post'; 6 + case 'app.bsky.feed.generator': 7 + return 'feed'; 8 + case 'app.bsky.graph.list': 9 + return 'list'; 10 + case 'app.bsky.graph.starterpack': 11 + return 'starter pack'; 12 + case 'app.bsky.labeler.service': 13 + return 'labeler'; 14 + } 15 + 16 + return null; 17 + }; 18 + </script> 19 + 20 + <script lang="ts"> 21 + import type { 22 + AppBskyEmbedExternal, 23 + AppBskyEmbedImages, 24 + AppBskyEmbedRecord, 25 + AppBskyEmbedVideo, 26 + AppBskyFeedDefs, 27 + Brand, 28 + } from '@atcute/client/lexicons'; 29 + 30 + import { findLabel } from '../../utils/labels'; 31 + import { parseAtUri } from '../../utils/syntax/at-url'; 32 + 33 + import ContentHider from '../content-hider.svelte'; 34 + 35 + import ExternalEmbed from './external-embed.svelte'; 36 + import FeedEmbed from './feed-embed.svelte'; 37 + import ImageEmbed from './image-embed.svelte'; 38 + import ListEmbed from './list-embed.svelte'; 39 + import QuoteEmbed from './quote-embed.svelte'; 40 + import StarterpackEmbed from './starterpack-embed.svelte'; 41 + import VideoEmbed from './video-embed.svelte'; 42 + 43 + type Embed = NonNullable<AppBskyFeedDefs.PostView['embed']>; 44 + type MediaEmbed = Brand.Union<AppBskyEmbedExternal.View | AppBskyEmbedImages.View | AppBskyEmbedVideo.View>; 45 + type RecordEmbed = AppBskyEmbedRecord.View; 46 + 47 + interface Props { 48 + post?: AppBskyFeedDefs.PostView; 49 + embed: Embed; 50 + large?: boolean; 51 + } 52 + 53 + const { post, embed, large = false }: Props = $props(); 54 + </script> 55 + 56 + <div class="embeds"> 57 + {#if embed.$type === 'app.bsky.embed.recordWithMedia#view'} 58 + {@render Media(embed.media)} 59 + {@render Record(embed.record)} 60 + {:else if embed.$type === 'app.bsky.embed.record#view'} 61 + {@render Record(embed)} 62 + {:else} 63 + {@render Media(embed)} 64 + {/if} 65 + </div> 66 + 67 + {#snippet Media(embed: MediaEmbed)} 68 + {@const warning = post && findLabel(post.labels, post.author.did)} 69 + 70 + <ContentHider {warning}> 71 + {#if embed.$type === 'app.bsky.embed.external#view'} 72 + <ExternalEmbed {embed} /> 73 + {:else if embed.$type === 'app.bsky.embed.images#view'} 74 + <ImageEmbed {embed} standalone /> 75 + {:else if embed.$type === 'app.bsky.embed.video#view'} 76 + <VideoEmbed {post} {embed} standalone /> 77 + {:else} 78 + {@render Message(`Unsupported media embed`)} 79 + {/if} 80 + </ContentHider> 81 + {/snippet} 82 + 83 + {#snippet Record(embed: RecordEmbed)} 84 + {@const record = embed.record} 85 + 86 + {#if record.$type === 'app.bsky.embed.record#viewRecord'} 87 + <QuoteEmbed embed={record} {large} /> 88 + {:else if record.$type === 'app.bsky.feed.defs#generatorView'} 89 + <FeedEmbed embed={record} /> 90 + {:else if record.$type === 'app.bsky.graph.defs#listView'} 91 + <ListEmbed embed={record} /> 92 + {:else if record.$type === 'app.bsky.graph.defs#starterPackViewBasic'} 93 + <StarterpackEmbed embed={record} {large} /> 94 + {:else} 95 + {@const uri = parseAtUri(record.uri)} 96 + {@const resource = collectionToLabel(uri.collection)} 97 + 98 + {@const isUnavailable = 99 + resource && 100 + (record.$type === 'app.bsky.embed.record#viewNotFound' || 101 + record.$type === 'app.bsky.embed.record#viewBlocked' || 102 + record.$type === 'app.bsky.embed.record#viewDetached')} 103 + 104 + {@render Message(isUnavailable ? `This ${resource} is unavailable` : `Unsupported record embed`)} 105 + {/if} 106 + {/snippet} 107 + 108 + {#snippet Message(message: string)} 109 + <div class="message">{message}</div> 110 + {/snippet} 111 + 112 + <style> 113 + .embeds { 114 + display: flex; 115 + flex-direction: column; 116 + gap: 12px; 117 + margin: 12px 0 0 0; 118 + } 119 + 120 + .message { 121 + border: 1px solid var(--divider); 122 + border-radius: 6px; 123 + padding: 12px; 124 + color: var(--text-secondary); 125 + } 126 + </style>
+129
packages/internal/components/embeds/external-embed.svelte
··· 1 + <script lang="ts" module> 2 + const safeParseUrl = (str: string): URL | null => { 3 + let url: URL | null | undefined; 4 + if ('parse' in URL) { 5 + url = URL.parse(str); 6 + } else { 7 + try { 8 + // @ts-expect-error: `'parse' in URL` is giving truthy 9 + url = new URL(str); 10 + } catch {} 11 + } 12 + 13 + if (url && (url.protocol === 'https:' || url.protocol === 'http:')) { 14 + return url; 15 + } 16 + 17 + return null; 18 + }; 19 + </script> 20 + 21 + <script lang="ts"> 22 + import type { AppBskyEmbedExternal } from '@atcute/client/lexicons'; 23 + 24 + interface Props { 25 + embed: AppBskyEmbedExternal.View; 26 + } 27 + 28 + const { embed }: Props = $props(); 29 + 30 + const external = embed.external; 31 + 32 + const domain = safeParseUrl(external.uri)?.host; 33 + </script> 34 + 35 + <a target="_blank" href={domain && external.uri} rel="noopener noreferrer nofollow" class="external-embed"> 36 + {#if external.thumb} 37 + <img loading="lazy" src={external.thumb} alt="" class="thumbnail" /> 38 + {/if} 39 + 40 + <div class="meta"> 41 + <p class="title">{external.title}</p> 42 + <p class="description">{external.description}</p> 43 + 44 + {#if domain} 45 + <div class="domain"> 46 + <!-- earth --> 47 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 48 + <path 49 + stroke="currentColor" 50 + stroke-linecap="round" 51 + stroke-width="2" 52 + d="m4.172 8.07 3.94 2.957.977-1.941 3.887-.978 1.15-4.6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-6.078 4.865.973-1.946-2.869-1.928-1.89-.12-1.08 1.075 1.947 2.919h2.919Z" 53 + /> 54 + </svg> 55 + 56 + <span class="domain-name">{domain}</span> 57 + </div> 58 + {/if} 59 + </div> 60 + </a> 61 + 62 + <style> 63 + .external-embed { 64 + display: block; 65 + border: 1px solid var(--divider); 66 + border-radius: 6px; 67 + overflow: hidden; 68 + 69 + &:hover { 70 + border-color: var(--divider-hover); 71 + } 72 + } 73 + 74 + .thumbnail { 75 + display: block; 76 + border-bottom: 1px solid var(--divider); 77 + background: #000000; 78 + aspect-ratio: 1.91; 79 + width: 100%; 80 + 81 + .external-embed:hover & { 82 + border-color: var(--divider-hover); 83 + } 84 + } 85 + 86 + .meta { 87 + padding: 12px; 88 + } 89 + 90 + .title { 91 + display: -webkit-box; 92 + overflow: hidden; 93 + font-weight: 700; 94 + white-space: pre-wrap; 95 + -webkit-box-orient: vertical; 96 + -webkit-line-clamp: 2; 97 + line-clamp: 2; 98 + overflow-wrap: break-word; 99 + 100 + &:empty { 101 + display: none; 102 + } 103 + } 104 + .description { 105 + display: -webkit-box; 106 + overflow: hidden; 107 + color: var(--text-secondary); 108 + font-size: calc(var(--font-size) * 0.8125); 109 + white-space: pre-wrap; 110 + -webkit-box-orient: vertical; 111 + -webkit-line-clamp: 2; 112 + line-clamp: 2; 113 + overflow-wrap: break-word; 114 + 115 + &:empty { 116 + display: none; 117 + } 118 + } 119 + 120 + .domain { 121 + display: flex; 122 + align-items: center; 123 + gap: 6px; 124 + margin: 6px 0 0 0; 125 + color: var(--text-secondary); 126 + font-weight: 500; 127 + font-size: calc(var(--font-size) * 0.75); 128 + } 129 + </style>
+100
packages/internal/components/embeds/feed-embed.svelte
··· 1 + <script lang="ts"> 2 + import type { AppBskyFeedDefs } from '@atcute/client/lexicons'; 3 + 4 + import { getFeedUrl } from '../../utils/bsky-url'; 5 + import { parseAtUri } from '../../utils/syntax/at-url'; 6 + 7 + interface Props { 8 + embed: AppBskyFeedDefs.GeneratorView; 9 + } 10 + 11 + const { embed: feed }: Props = $props(); 12 + 13 + const creator = feed.creator; 14 + 15 + const feedUrl = getFeedUrl(creator.did, parseAtUri(feed.uri).rkey); 16 + </script> 17 + 18 + <a target="_blank" href={feedUrl} class="feed-embed"> 19 + <div class="main"> 20 + <div class="avatar-wrapper"> 21 + {#if feed.avatar} 22 + <img loading="lazy" src={feed.avatar} alt="" class="avatar" /> 23 + {:else} 24 + <svg viewBox="0 0 32 32" class="avatar"> 25 + <path fill="#0070FF" d="M0 0h32v32H0z" /> 26 + <path 27 + fill="#fff" 28 + d="M22.153 22.354a9.328 9.328 0 0 0 3.837-.491 3.076 3.076 0 0 0-4.802-2.79m.965 3.281a6.128 6.128 0 0 0-.965-3.28Zm-11.342-3.28a3.077 3.077 0 0 0-4.801 2.79 9.21 9.21 0 0 0 3.835.49m.966-3.28a6.127 6.127 0 0 0-.966 3.28Zm8.265-8.997a3.076 3.076 0 1 1-6.153 0 3.076 3.076 0 0 1 6.153 0Zm6.154 3.077a2.307 2.307 0 1 1-4.615 0 2.307 2.307 0 0 1 4.615 0Zm-13.847 0a2.307 2.307 0 1 1-4.614 0 2.307 2.307 0 0 1 4.614 0Z" 29 + /> 30 + <path fill="#fff" d="M22 22c0 3.314-2.686 3.5-6 3.5s-6-.186-6-3.5a6 6 0 0 1 12 0Z" /> 31 + </svg> 32 + {/if} 33 + </div> 34 + 35 + <div class="info"> 36 + <p class="name">{feed.displayName}</p> 37 + <p class="creator">Feed by @{creator.handle}</p> 38 + </div> 39 + </div> 40 + 41 + <p class="description">{feed.description}</p> 42 + </a> 43 + 44 + <style> 45 + .feed-embed { 46 + display: flex; 47 + flex-direction: column; 48 + gap: 12px; 49 + border: 1px solid var(--divider); 50 + border-radius: 6px; 51 + padding: 12px; 52 + 53 + &:hover { 54 + border-color: var(--divider-hover); 55 + } 56 + } 57 + 58 + .main { 59 + display: flex; 60 + gap: 12px; 61 + } 62 + 63 + .avatar-wrapper { 64 + margin: 2px 0 0 0; 65 + border-radius: 6px; 66 + background: var(--background-secondary); 67 + width: 36px; 68 + height: 36px; 69 + overflow: hidden; 70 + } 71 + .avatar { 72 + width: 100%; 73 + height: 100%; 74 + object-fit: cover; 75 + } 76 + 77 + .name { 78 + font-weight: 700; 79 + } 80 + 81 + .creator { 82 + color: var(--text-secondary); 83 + font-size: calc(var(--font-size) * 0.8125); 84 + } 85 + 86 + .description { 87 + display: -webkit-box; 88 + overflow: hidden; 89 + font-size: calc(var(--font-size) * 0.8125); 90 + white-space: pre-wrap; 91 + -webkit-box-orient: vertical; 92 + -webkit-line-clamp: 2; 93 + line-clamp: 2; 94 + overflow-wrap: break-word; 95 + 96 + &:empty { 97 + display: none; 98 + } 99 + } 100 + </style>
+183
packages/internal/components/embeds/image-embed.svelte
··· 1 + <script lang="ts" module> 2 + const DEFAULT_RATIO = { width: 16, height: 9 }; 3 + </script> 4 + 5 + <script lang="ts"> 6 + import type { AppBskyEmbedImages } from '@atcute/client/lexicons'; 7 + 8 + interface Props { 9 + embed: AppBskyEmbedImages.View; 10 + borderless?: boolean; 11 + standalone?: boolean; 12 + blur?: boolean; 13 + } 14 + 15 + const { embed, borderless, standalone, blur }: Props = $props(); 16 + 17 + const images = embed.images; 18 + const length = images.length; 19 + </script> 20 + 21 + <div 22 + class={'image-embed' + 23 + (!borderless ? ` is-bordered` : ``) + 24 + (standalone && length === 1 ? ` is-aligned` : ``)} 25 + > 26 + {#if length === 4} 27 + <div class="grid"> 28 + <div class="col"> 29 + <div class="item wide tl"> 30 + {@render Image(0)} 31 + </div> 32 + <div class="item wide bl"> 33 + {@render Image(2)} 34 + </div> 35 + </div> 36 + <div class="col"> 37 + <div class="item wide tr"> 38 + {@render Image(1)} 39 + </div> 40 + <div class="item wide br"> 41 + {@render Image(3)} 42 + </div> 43 + </div> 44 + </div> 45 + {:else if length === 3} 46 + <div class="grid"> 47 + <div class="col square"> 48 + <div class="item tl bl"> 49 + {@render Image(0)} 50 + </div> 51 + </div> 52 + <div class="col square"> 53 + <div class="item tr"> 54 + {@render Image(1)} 55 + </div> 56 + <div class="item br"> 57 + {@render Image(2)} 58 + </div> 59 + </div> 60 + </div> 61 + {:else if length === 2} 62 + <div class="grid"> 63 + <div class="col"> 64 + <div class="item square tl bl"> 65 + {@render Image(0)} 66 + </div> 67 + </div> 68 + <div class="col"> 69 + <div class="item square tr br"> 70 + {@render Image(1)} 71 + </div> 72 + </div> 73 + </div> 74 + {:else if length === 1} 75 + {@const ratio = standalone && (images[0].aspectRatio || DEFAULT_RATIO)} 76 + 77 + <div 78 + class={`single-item tl tr bl br` + (ratio ? ` is-standalone` : ``)} 79 + style={ratio ? `aspect-ratio: ${ratio.width}/${ratio.height}` : ``} 80 + > 81 + {@render Image(0)} 82 + 83 + {#if ratio} 84 + <div class="placeholder"></div> 85 + {/if} 86 + </div> 87 + {/if} 88 + </div> 89 + 90 + {#snippet Image(index: number)} 91 + {@const image = images[index]} 92 + 93 + <img loading="lazy" src={image.thumb} alt={image.alt} class={`image` + (blur ? ` is-blurred` : ``)} /> 94 + {/snippet} 95 + 96 + <style> 97 + .is-aligned { 98 + align-self: baseline; 99 + max-width: 100%; 100 + } 101 + 102 + .grid { 103 + display: flex; 104 + gap: 2px; 105 + } 106 + .col { 107 + display: flex; 108 + flex: 1; 109 + flex-direction: column; 110 + gap: 2px; 111 + } 112 + 113 + .square { 114 + aspect-ratio: 1; 115 + } 116 + .wide { 117 + aspect-ratio: 1.5; 118 + } 119 + 120 + .item { 121 + position: relative; 122 + flex-grow: 1; 123 + flex-shrink: 0; 124 + overflow: hidden; 125 + } 126 + 127 + .is-bordered { 128 + .tl, 129 + .tr, 130 + .bl, 131 + .br { 132 + border: 1px solid var(--divider); 133 + } 134 + 135 + .tl { 136 + border-top-left-radius: 6px; 137 + } 138 + .tr { 139 + border-top-right-radius: 6px; 140 + } 141 + .bl { 142 + border-bottom-left-radius: 6px; 143 + } 144 + .br { 145 + border-bottom-right-radius: 6px; 146 + } 147 + } 148 + 149 + .single-item { 150 + position: relative; 151 + aspect-ratio: 16 / 9; 152 + overflow: hidden; 153 + 154 + .image { 155 + object-fit: contain; 156 + } 157 + } 158 + .is-standalone { 159 + min-width: 64px; 160 + max-width: 100%; 161 + min-height: 64px; 162 + max-height: 320px; 163 + } 164 + 165 + .image { 166 + position: absolute; 167 + inset: 0; 168 + background: #000000; 169 + width: 100%; 170 + height: 100%; 171 + object-fit: cover; 172 + font-size: 0px; 173 + } 174 + .is-blurred { 175 + scale: 125%; 176 + filter: blur(24px); 177 + } 178 + 179 + .placeholder { 180 + width: 100vw; 181 + height: 100vh; 182 + } 183 + </style>
+113
packages/internal/components/embeds/list-embed.svelte
··· 1 + <script lang="ts" module> 2 + const getPurpose = (purpose: AppBskyGraphDefs.ListPurpose) => { 3 + switch (purpose) { 4 + case 'app.bsky.graph.defs#curatelist': 5 + return `User list`; 6 + case 'app.bsky.graph.defs#modlist': 7 + return `Moderation list`; 8 + } 9 + 10 + return `Unknown list`; 11 + }; 12 + </script> 13 + 14 + <script lang="ts"> 15 + import type { AppBskyGraphDefs } from '@atcute/client/lexicons'; 16 + 17 + import { getFeedUrl } from '../../utils/bsky-url'; 18 + import { parseAtUri } from '../../utils/syntax/at-url'; 19 + 20 + interface Props { 21 + embed: AppBskyGraphDefs.ListView; 22 + } 23 + 24 + const { embed: list }: Props = $props(); 25 + 26 + const creator = list.creator; 27 + 28 + const listUrl = getFeedUrl(creator.did, parseAtUri(list.uri).rkey); 29 + </script> 30 + 31 + <a target="_blank" href={listUrl} class="list-embed"> 32 + <div class="main"> 33 + <div class="avatar-wrapper"> 34 + {#if list.avatar} 35 + <img loading="lazy" src={list.avatar} alt="" class="avatar" /> 36 + {:else} 37 + <svg viewBox="0 0 32 32" class="avatar"> 38 + <path fill="#0070FF" d="M0 0h32v32H0z" /> 39 + <path 40 + fill="#fff" 41 + d="M22.153 22.354a9.328 9.328 0 0 0 3.837-.491 3.076 3.076 0 0 0-4.802-2.79m.965 3.281a6.128 6.128 0 0 0-.965-3.28Zm-11.342-3.28a3.077 3.077 0 0 0-4.801 2.79 9.21 9.21 0 0 0 3.835.49m.966-3.28a6.127 6.127 0 0 0-.966 3.28Zm8.265-8.997a3.076 3.076 0 1 1-6.153 0 3.076 3.076 0 0 1 6.153 0Zm6.154 3.077a2.307 2.307 0 1 1-4.615 0 2.307 2.307 0 0 1 4.615 0Zm-13.847 0a2.307 2.307 0 1 1-4.614 0 2.307 2.307 0 0 1 4.614 0Z" 42 + /> 43 + <path fill="#fff" d="M22 22c0 3.314-2.686 3.5-6 3.5s-6-.186-6-3.5a6 6 0 0 1 12 0Z" /> 44 + </svg> 45 + {/if} 46 + </div> 47 + 48 + <div class="info"> 49 + <p class="name">{list.name}</p> 50 + <p class="creator">{getPurpose(list.purpose)} by @{creator.handle}</p> 51 + </div> 52 + </div> 53 + 54 + <p class="description">{list.description}</p> 55 + </a> 56 + 57 + <style> 58 + .list-embed { 59 + display: flex; 60 + flex-direction: column; 61 + gap: 12px; 62 + border: 1px solid var(--divider); 63 + border-radius: 6px; 64 + padding: 12px; 65 + 66 + &:hover { 67 + border-color: var(--divider-hover); 68 + } 69 + } 70 + 71 + .main { 72 + display: flex; 73 + gap: 12px; 74 + } 75 + 76 + .avatar-wrapper { 77 + margin: 2px 0 0 0; 78 + border-radius: 6px; 79 + background: var(--background-secondary); 80 + width: 36px; 81 + height: 36px; 82 + overflow: hidden; 83 + } 84 + .avatar { 85 + width: 100%; 86 + height: 100%; 87 + object-fit: cover; 88 + } 89 + 90 + .name { 91 + font-weight: 700; 92 + } 93 + 94 + .creator { 95 + color: var(--text-secondary); 96 + font-size: calc(var(--font-size) * 0.8125); 97 + } 98 + 99 + .description { 100 + display: -webkit-box; 101 + overflow: hidden; 102 + font-size: calc(var(--font-size) * 0.8125); 103 + white-space: pre-wrap; 104 + -webkit-box-orient: vertical; 105 + -webkit-line-clamp: 2; 106 + line-clamp: 2; 107 + overflow-wrap: break-word; 108 + 109 + &:empty { 110 + display: none; 111 + } 112 + } 113 + </style>
+213
packages/internal/components/embeds/quote-embed.svelte
··· 1 + <script lang="ts" module> 2 + const getPostImage = (embed: AppBskyFeedDefs.PostView['embed']): AppBskyEmbedImages.View | undefined => { 3 + if (embed) { 4 + if (embed.$type === 'app.bsky.embed.images#view') { 5 + return embed; 6 + } 7 + 8 + if (embed.$type === 'app.bsky.embed.recordWithMedia#view') { 9 + return getPostImage(embed.media); 10 + } 11 + } 12 + }; 13 + 14 + const getPostVideo = (embed: AppBskyFeedDefs.PostView['embed']): AppBskyEmbedVideo.View | undefined => { 15 + if (embed) { 16 + if (embed.$type === 'app.bsky.embed.video#view') { 17 + return embed; 18 + } 19 + 20 + if (embed.$type === 'app.bsky.embed.recordWithMedia#view') { 21 + return getPostVideo(embed.media); 22 + } 23 + } 24 + }; 25 + </script> 26 + 27 + <script lang="ts"> 28 + import type { 29 + AppBskyEmbedImages, 30 + AppBskyEmbedRecord, 31 + AppBskyEmbedVideo, 32 + AppBskyFeedDefs, 33 + AppBskyFeedPost, 34 + } from '@atcute/client/lexicons'; 35 + 36 + import { getPostUrl } from '../../utils/bsky-url'; 37 + import { formatShortDate } from '../../utils/date'; 38 + import { findLabel } from '../../utils/labels'; 39 + import { parseAtUri } from '../../utils/syntax/at-url'; 40 + 41 + import ImageEmbed from './image-embed.svelte'; 42 + import VideoEmbed from './video-embed.svelte'; 43 + 44 + interface Props { 45 + embed: AppBskyEmbedRecord.ViewRecord; 46 + large?: boolean; 47 + } 48 + 49 + const { embed: quote, large = false }: Props = $props(); 50 + 51 + const record = quote.value as AppBskyFeedPost.Record; 52 + const text = record.text.trim(); 53 + 54 + const author = quote.author; 55 + const authorName = author.displayName?.trim(); 56 + 57 + const embed = quote.embeds?.[0]; 58 + const image = getPostImage(embed); 59 + const video = getPostVideo(embed); 60 + 61 + const postUrl = getPostUrl(author.did, parseAtUri(quote.uri).rkey); 62 + 63 + const isMediaBlurred = !!findLabel(quote.labels, author.did); 64 + </script> 65 + 66 + <a target="_blank" href={postUrl} class="quote-embed"> 67 + <div class="meta"> 68 + <div class="avatar-wrapper"> 69 + {#if author.avatar} 70 + <img loading="lazy" src={author.avatar} alt="" class="avatar" /> 71 + {/if} 72 + </div> 73 + 74 + <span class="name-wrapper"> 75 + {#if authorName} 76 + <bdi class="display-name-wrapper"> 77 + <span class="display-name">{authorName}</span> 78 + </bdi> 79 + {/if} 80 + 81 + <span class="handle">@{author.handle}</span> 82 + </span> 83 + 84 + <span aria-hidden="true" class="dot">ยท</span> 85 + 86 + <time datetime={record.createdAt} class="date"> 87 + {formatShortDate(record.createdAt)} 88 + </time> 89 + </div> 90 + 91 + {#if text} 92 + <div class="body"> 93 + {#if !large} 94 + {#if image} 95 + <div class="aside"> 96 + <ImageEmbed embed={image} blur={isMediaBlurred} /> 97 + </div> 98 + {:else if video} 99 + <div class="aside"> 100 + <VideoEmbed embed={video} blur={isMediaBlurred} /> 101 + </div> 102 + {/if} 103 + {/if} 104 + 105 + <p class="text">{text}</p> 106 + </div> 107 + {:else} 108 + <div class="divide"></div> 109 + {/if} 110 + 111 + {#if large || !text} 112 + {#if image} 113 + <ImageEmbed embed={image} borderless blur={isMediaBlurred} /> 114 + {:else if video} 115 + <VideoEmbed embed={video} borderless blur={isMediaBlurred} /> 116 + {/if} 117 + {/if} 118 + </a> 119 + 120 + <style> 121 + .quote-embed { 122 + display: block; 123 + border: 1px solid var(--divider); 124 + border-radius: 6px; 125 + overflow: hidden; 126 + 127 + &:hover { 128 + border-color: var(--divider-hover); 129 + } 130 + } 131 + 132 + .meta { 133 + display: flex; 134 + padding: 12px 12px 0 12px; 135 + color: var(--text-secondary); 136 + 137 + .avatar-wrapper { 138 + flex-shrink: 0; 139 + margin: 0 8px 0 0; 140 + border-radius: 9999px; 141 + background: var(--background-secondary); 142 + width: 20px; 143 + height: 20px; 144 + overflow: hidden; 145 + } 146 + .avatar { 147 + width: 100%; 148 + height: 100%; 149 + } 150 + 151 + .name-wrapper { 152 + display: flex; 153 + gap: 4px; 154 + max-width: 100%; 155 + overflow: hidden; 156 + text-overflow: ellipsis; 157 + white-space: nowrap; 158 + } 159 + .display-name-wrapper { 160 + overflow: hidden; 161 + text-overflow: ellipsis; 162 + } 163 + .display-name { 164 + color: var(--text-primary); 165 + font-weight: 700; 166 + } 167 + .handle { 168 + display: block; 169 + overflow: hidden; 170 + text-overflow: ellipsis; 171 + white-space: nowrap; 172 + } 173 + 174 + .dot { 175 + flex-shrink: 0; 176 + margin: 0 6px; 177 + } 178 + 179 + .date { 180 + white-space: nowrap; 181 + } 182 + } 183 + 184 + .body { 185 + display: flex; 186 + align-items: flex-start; 187 + } 188 + 189 + .aside { 190 + flex-grow: 1; 191 + flex-basis: 0; 192 + margin: 8px 0 12px 12px; 193 + max-width: 20%; 194 + } 195 + 196 + .text { 197 + display: -webkit-box; 198 + margin: 8px 12px 12px 12px; 199 + overflow: hidden; 200 + -webkit-box-orient: vertical; 201 + flex-grow: 4; 202 + flex-basis: 0px; 203 + min-width: 0px; 204 + -webkit-line-clamp: 6; 205 + line-clamp: 6; 206 + white-space: pre-wrap; 207 + overflow-wrap: break-word; 208 + } 209 + 210 + .divide { 211 + padding: 6px 0; 212 + } 213 + </style>
+122
packages/internal/components/embeds/starterpack-embed.svelte
··· 1 + <script lang="ts"> 2 + import type { AppBskyGraphDefs, AppBskyGraphStarterpack } from '@atcute/client/lexicons'; 3 + 4 + import { getStarterpackImgUrl, getStarterpackUrl } from '../../utils/bsky-url'; 5 + import { parseAtUri } from '../../utils/syntax/at-url'; 6 + 7 + interface Props { 8 + embed: AppBskyGraphDefs.StarterPackViewBasic; 9 + large?: boolean; 10 + } 11 + 12 + const { embed: pack, large = false }: Props = $props(); 13 + 14 + const record = pack.record as AppBskyGraphStarterpack.Record; 15 + 16 + const creator = pack.creator; 17 + const creatorDid = creator.did; 18 + 19 + const rkey = parseAtUri(pack.uri).rkey; 20 + const packUrl = getStarterpackUrl(creatorDid, rkey); 21 + </script> 22 + 23 + <a target="_blank" href={packUrl} class="starterpack-embed"> 24 + {#if large} 25 + {@const imageUrl = getStarterpackImgUrl(creatorDid, rkey)} 26 + 27 + <img loading="lazy" src={imageUrl} alt="" class="banner" /> 28 + {/if} 29 + 30 + <div class="meta"> 31 + <div class="main"> 32 + <svg fill="none" viewBox="0 0 24 24" class="avatar"> 33 + <defs> 34 + <linearGradient id="a" x1="0" x2="100%" y1="0" y2="0" gradientTransform="rotate(45)"> 35 + <stop offset="0" stop-color="#0A7AFF" /> 36 + <stop offset="1" stop-color="#59B9FF" /> 37 + </linearGradient> 38 + </defs> 39 + <path 40 + fill="url(#a)" 41 + fill-rule="evenodd" 42 + d="M11.26 5.227 5.02 6.899c-.734.197-1.17.95-.973 1.685l1.672 6.24c.197.734.951 1.17 1.685.973l6.24-1.672a1.376 1.376 0 0 0 .973-1.685L12.945 6.2a1.375 1.375 0 0 0-1.685-.973Zm-6.566.459a2.632 2.632 0 0 0-1.86 3.223l1.672 6.24a2.632 2.632 0 0 0 3.223 1.861l6.24-1.672a2.631 2.631 0 0 0 1.861-3.223l-1.672-6.24a2.632 2.632 0 0 0-3.223-1.861l-6.24 1.672Z" 43 + clip-rule="evenodd" 44 + /> 45 + <path 46 + fill="url(#a)" 47 + fill-rule="evenodd" 48 + d="M15.138 18.411a4.606 4.606 0 1 0 0-9.211 4.606 4.606 0 0 0 0 9.211Zm0 1.257a5.862 5.862 0 1 0 0-11.724 5.862 5.862 0 0 0 0 11.724Z" 49 + clip-rule="evenodd" 50 + /> 51 + </svg> 52 + 53 + <div class="info"> 54 + <p class="name">{record.name}</p> 55 + <p class="creator">Starter pack by @{creator.handle}</p> 56 + </div> 57 + </div> 58 + 59 + <p class="description">{record.description}</p> 60 + </div> 61 + </a> 62 + 63 + <style> 64 + .starterpack-embed { 65 + display: block; 66 + border: 1px solid var(--divider); 67 + border-radius: 6px; 68 + overflow: hidden; 69 + 70 + &:hover { 71 + border-color: var(--divider-hover); 72 + } 73 + } 74 + 75 + .banner { 76 + display: block; 77 + aspect-ratio: 1.91; 78 + width: 100%; 79 + } 80 + 81 + .meta { 82 + display: flex; 83 + flex-direction: column; 84 + gap: 12px; 85 + padding: 12px; 86 + } 87 + 88 + .main { 89 + display: flex; 90 + gap: 12px; 91 + } 92 + 93 + .avatar { 94 + margin: 2px; 95 + width: 36px; 96 + height: 36px; 97 + } 98 + 99 + .name { 100 + font-weight: 700; 101 + } 102 + 103 + .creator { 104 + color: var(--text-secondary); 105 + font-size: calc(var(--font-size) * 0.8125); 106 + } 107 + 108 + .description { 109 + display: -webkit-box; 110 + overflow: hidden; 111 + font-size: calc(var(--font-size) * 0.8125); 112 + white-space: pre-wrap; 113 + -webkit-box-orient: vertical; 114 + -webkit-line-clamp: 2; 115 + line-clamp: 2; 116 + overflow-wrap: break-word; 117 + 118 + &:empty { 119 + display: none; 120 + } 121 + } 122 + </style>
+120
packages/internal/components/embeds/video-embed.svelte
··· 1 + <script lang="ts"> 2 + import type { AppBskyEmbedVideo, AppBskyFeedDefs } from '@atcute/client/lexicons'; 3 + 4 + import { getPostUrl } from '../../utils/bsky-url'; 5 + import { parseAtUri } from '../../utils/syntax/at-url'; 6 + 7 + interface Props { 8 + post?: AppBskyFeedDefs.PostView; 9 + embed: AppBskyEmbedVideo.View; 10 + borderless?: boolean; 11 + standalone?: boolean; 12 + blur?: boolean; 13 + } 14 + 15 + const { post, embed: video, borderless, standalone, blur }: Props = $props(); 16 + 17 + const ratio = standalone && video.aspectRatio; 18 + 19 + const postUrl = post && getPostUrl(post.author.did, parseAtUri(post.uri).rkey); 20 + </script> 21 + 22 + {#if standalone} 23 + <a 24 + target="_blank" 25 + href={postUrl} 26 + class={`video-embed` + (!borderless ? ` is-bordered` : ``) + (standalone ? ` is-standalone` : ``)} 27 + > 28 + <div class="constrainer" style={ratio ? `aspect-ratio: ${ratio.width}/${ratio.height}` : ``}> 29 + {@render Content()} 30 + </div> 31 + </a> 32 + {:else} 33 + <div 34 + class={`video-embed` + (!borderless ? ` is-bordered` : ``)} 35 + style={ratio ? `aspect-ratio: ${ratio.width}/${ratio.height}` : ``} 36 + > 37 + {@render Content()} 38 + </div> 39 + {/if} 40 + 41 + {#snippet Content()} 42 + <img loading="lazy" src={video.thumbnail} alt="" class={`thumbnail` + (blur ? ` is-blurred` : ``)} /> 43 + 44 + {#if ratio} 45 + <div class="placeholder"></div> 46 + {/if} 47 + 48 + <div class="play"> 49 + <!-- play --> 50 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 51 + <path fill="currentColor" d="M22 12 5 2v20l17-10Z" /> 52 + </svg> 53 + </div> 54 + {/snippet} 55 + 56 + <style> 57 + .video-embed { 58 + display: block; 59 + position: relative; 60 + background: #000000; 61 + aspect-ratio: 16 / 9; 62 + overflow: hidden; 63 + } 64 + .is-bordered { 65 + border: 1px solid var(--divider); 66 + border-radius: 6px; 67 + } 68 + .is-standalone { 69 + align-self: baseline; 70 + aspect-ratio: auto; 71 + max-width: 100%; 72 + } 73 + 74 + .constrainer { 75 + min-width: 64px; 76 + max-width: 100%; 77 + min-height: 64px; 78 + max-height: 320px; 79 + } 80 + 81 + .thumbnail { 82 + width: 100%; 83 + height: 100%; 84 + object-fit: contain; 85 + } 86 + .is-blurred { 87 + scale: 125%; 88 + filter: blur(24px); 89 + } 90 + 91 + .placeholder { 92 + width: 100vw; 93 + height: 100vh; 94 + } 95 + 96 + .play { 97 + display: grid; 98 + position: absolute; 99 + top: 50%; 100 + left: 50%; 101 + place-items: center; 102 + translate: -50% -50%; 103 + border-radius: 50%; 104 + background: rgba(64, 64, 64, 0.6); 105 + aspect-ratio: 1 / 1; 106 + height: 40%; 107 + max-height: 48px; 108 + color: #ffffff; 109 + font-size: 20px; 110 + 111 + .icon { 112 + width: 40%; 113 + height: 40%; 114 + } 115 + 116 + .is-standalone &:hover { 117 + background: rgba(64, 64, 64, 0.8); 118 + } 119 + } 120 + </style>
+400
packages/internal/components/feed-post.svelte
··· 1 + <script lang="ts"> 2 + import type { AppBskyFeedDefs, AppBskyFeedPost } from '@atcute/client/lexicons'; 3 + 4 + import { getPostUrl, getProfileUrl } from '../utils/bsky-url'; 5 + import { formatLongDate, formatShortDate } from '../utils/date'; 6 + import { parseAtUri } from '../utils/syntax/at-url'; 7 + 8 + import { formatCompactNumber, formatLongNumber } from '../utils/number'; 9 + import Embeds from './embeds/embeds.svelte'; 10 + import RichtextRenderer from './richtext-renderer.svelte'; 11 + 12 + interface Props { 13 + item: AppBskyFeedDefs.FeedViewPost; 14 + prev?: boolean; 15 + next?: boolean; 16 + } 17 + 18 + const { item, prev = false, next = false }: Props = $props(); 19 + 20 + const reason = item.reason; 21 + const post = item.post; 22 + const parent = item.reply?.parent; 23 + 24 + const author = post.author; 25 + const authorUrl = getProfileUrl(author.did); 26 + const authorName = author.displayName?.trim(); 27 + 28 + const record = post.record as AppBskyFeedPost.Record; 29 + const postUrl = getPostUrl(author.did, parseAtUri(post.uri).rkey); 30 + 31 + const replyCount = post.replyCount || 0; 32 + const likeCount = post.likeCount || 0; 33 + const repostCount = (post.repostCount || 0) + (post.quoteCount || 0); 34 + </script> 35 + 36 + <div class={`feed-post` + (!next ? ` is-leaf` : ``)}> 37 + <div class="contexts"> 38 + {#if prev} 39 + <div class="ascendant-line-wrapper"> 40 + <div class="line"></div> 41 + </div> 42 + {/if} 43 + 44 + {#if reason} 45 + {#if reason.$type === 'app.bsky.feed.defs#reasonRepost'} 46 + {@const by = reason.by} 47 + 48 + <div class="context"> 49 + <div class="aside"> 50 + <svg class="icon" viewBox="0 0 24 24" fill="none"> 51 + <path 52 + d="M17 3L20 6L17 9M7 21L4 18L7 15M5 18H20V13M4 11V6H19" 53 + stroke="currentColor" 54 + stroke-width="2" 55 + stroke-linecap="square" 56 + /> 57 + </svg> 58 + </div> 59 + <a href={getProfileUrl(by.did)} class="main"> 60 + <span dir="auto" class="name">{by.displayName}</span> 61 + <span class="affix">{' '}reposted</span> 62 + </a> 63 + </div> 64 + {:else if reason.$type === 'app.bsky.feed.defs#reasonPin'} 65 + <div class="context"> 66 + <div class="aside"> 67 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 68 + <path 69 + stroke="currentColor" 70 + stroke-linecap="square" 71 + stroke-width="2" 72 + d="M12 15H5v-2.5l.377-.377A7.25 7.25 0 0 0 7.5 6.997V3h9v3.997a7.25 7.25 0 0 0 2.123 5.127L19 12.5V15h-7Zm0 0v6" 73 + /> 74 + </svg> 75 + </div> 76 + <span class="main">Pinned</span> 77 + </div> 78 + {/if} 79 + {/if} 80 + </div> 81 + 82 + <div class="content"> 83 + <div class="aside"> 84 + <a target="_blank" href={authorUrl} class="avatar-wrapper"> 85 + {#if author.avatar} 86 + <img loading="lazy" src={author.avatar} alt="" class="avatar" /> 87 + {/if} 88 + </a> 89 + 90 + {#if next} 91 + <div class="descendant-line"></div> 92 + {/if} 93 + </div> 94 + 95 + <div class="main"> 96 + <div class="meta"> 97 + <a href={authorUrl} target="_blank" class="name-wrapper"> 98 + {#if authorName} 99 + <bdi class="display-name-wrapper"> 100 + <span class="display-name">{authorName}</span> 101 + </bdi> 102 + {/if} 103 + 104 + <span class="handle">@{author.handle}</span> 105 + </a> 106 + 107 + <span aria-hidden="true" class="dot"> ยท </span> 108 + 109 + <a target="_blank" href={postUrl} title={formatLongDate(record.createdAt)} class="date"> 110 + <time datetime={record.createdAt}>{formatShortDate(record.createdAt)}</time> 111 + </a> 112 + </div> 113 + 114 + {#if !prev && record.reply} 115 + <p class="reply-context"> 116 + {#if parent && parent.$type === 'app.bsky.feed.defs#postView'} 117 + {@const author = parent.author} 118 + 119 + Replying to 120 + <a target="_blank" href={getProfileUrl(author.did)} dir="auto"> 121 + {author.displayName?.trim() || `@${author.handle}`} 122 + </a> 123 + {:else} 124 + Replying to an unknown post 125 + {/if} 126 + </p> 127 + {/if} 128 + 129 + <RichtextRenderer text={record.text} facets={record.facets} /> 130 + 131 + {#if post.embed} 132 + <Embeds {post} embed={post.embed} /> 133 + {/if} 134 + 135 + <div class="metrics"> 136 + <div 137 + title={replyCount === 1 138 + ? `${formatLongNumber(replyCount)} reply` 139 + : `${formatLongNumber(replyCount)} replies`} 140 + class="stat" 141 + > 142 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 143 + <path 144 + stroke="currentColor" 145 + stroke-linecap="square" 146 + stroke-width="2" 147 + d="M3.002 4h18v14h-9l-5 3v-3h-4V4Z" 148 + /> 149 + </svg> 150 + 151 + <span class="count"> 152 + {formatCompactNumber(replyCount)} 153 + </span> 154 + </div> 155 + 156 + <div 157 + title={repostCount === 1 158 + ? `${formatLongNumber(repostCount)} repost` 159 + : `${formatLongNumber(repostCount)} reposts`} 160 + class="stat" 161 + > 162 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 163 + <path 164 + stroke="currentColor" 165 + stroke-linecap="square" 166 + stroke-width="2" 167 + d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15" 168 + /> 169 + </svg> 170 + 171 + <span class="count"> 172 + {formatCompactNumber(repostCount)} 173 + </span> 174 + </div> 175 + 176 + <div 177 + title={likeCount === 1 178 + ? `${formatLongNumber(likeCount)} like` 179 + : `${formatLongNumber(likeCount)} likes`} 180 + class="stat" 181 + > 182 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 183 + <path 184 + stroke="currentColor" 185 + stroke-width="2" 186 + d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z" 187 + /> 188 + </svg> 189 + 190 + <span class="count"> 191 + {formatCompactNumber(likeCount)} 192 + </span> 193 + </div> 194 + </div> 195 + </div> 196 + </div> 197 + </div> 198 + 199 + <style> 200 + .feed-post { 201 + padding: 0 16px; 202 + } 203 + .is-leaf { 204 + border-bottom: 1px solid var(--divider); 205 + } 206 + 207 + .ascendant-line-wrapper { 208 + display: flex; 209 + flex-direction: column; 210 + align-items: center; 211 + width: 36px; 212 + 213 + .line { 214 + position: absolute; 215 + top: 0; 216 + bottom: 4px; 217 + flex-grow: 1; 218 + border-left: 2px solid var(--divider); 219 + } 220 + } 221 + .descendant-line { 222 + flex-grow: 1; 223 + margin-top: 4px; 224 + border-left: 2px solid var(--divider); 225 + } 226 + 227 + .contexts { 228 + display: flex; 229 + position: relative; 230 + flex-direction: column; 231 + padding: 8px 0 4px 0; 232 + } 233 + .context { 234 + display: flex; 235 + align-items: center; 236 + gap: 12px; 237 + color: var(--text-secondary); 238 + font-size: 0.8125rem; 239 + line-height: 1.25rem; 240 + 241 + .aside { 242 + display: flex; 243 + flex-shrink: 0; 244 + justify-content: flex-end; 245 + width: 36px; 246 + } 247 + 248 + .main { 249 + display: flex; 250 + min-width: 0px; 251 + 252 + &:hover { 253 + text-decoration-line: underline; 254 + } 255 + } 256 + 257 + .name { 258 + overflow: hidden; 259 + font-weight: 500; 260 + text-overflow: ellipsis; 261 + white-space: nowrap; 262 + } 263 + 264 + .affix { 265 + flex-shrink: 0; 266 + white-space: pre; 267 + } 268 + } 269 + 270 + .content { 271 + display: flex; 272 + gap: 12px; 273 + 274 + .aside { 275 + display: flex; 276 + flex-shrink: 0; 277 + flex-direction: column; 278 + align-items: center; 279 + } 280 + 281 + .main { 282 + flex-grow: 1; 283 + padding-bottom: 12px; 284 + min-width: 0; 285 + } 286 + } 287 + 288 + .avatar-wrapper { 289 + display: block; 290 + border-radius: 9999px; 291 + background: var(--background-secondary); 292 + width: 36px; 293 + height: 36px; 294 + overflow: hidden; 295 + 296 + &:hover { 297 + filter: brightness(0.85); 298 + } 299 + } 300 + .avatar { 301 + width: 100%; 302 + height: 100%; 303 + object-fit: cover; 304 + } 305 + 306 + .meta { 307 + display: flex; 308 + align-items: center; 309 + margin: 0 0 2px 0; 310 + color: var(--text-secondary); 311 + 312 + .name-wrapper { 313 + display: flex; 314 + gap: 4px; 315 + max-width: 100%; 316 + overflow: hidden; 317 + color: inherit; 318 + text-decoration: none; 319 + text-overflow: ellipsis; 320 + white-space: nowrap; 321 + } 322 + 323 + .display-name-wrapper { 324 + overflow: hidden; 325 + text-overflow: ellipsis; 326 + 327 + .name-wrapper:hover & { 328 + text-decoration: underline; 329 + } 330 + } 331 + 332 + .display-name { 333 + color: var(--text-primary); 334 + font-weight: 700; 335 + } 336 + 337 + .handle { 338 + display: block; 339 + overflow: hidden; 340 + text-overflow: ellipsis; 341 + white-space: nowrap; 342 + } 343 + 344 + .dot { 345 + flex-shrink: 0; 346 + margin: 0 6px; 347 + } 348 + 349 + .date { 350 + color: inherit; 351 + text-decoration: none; 352 + white-space: nowrap; 353 + 354 + &:hover { 355 + text-decoration: underline; 356 + } 357 + } 358 + } 359 + 360 + .reply-context { 361 + overflow: hidden; 362 + color: var(--text-secondary); 363 + font-size: calc(var(--font-size) * 0.8125); 364 + text-overflow: ellipsis; 365 + white-space: nowrap; 366 + 367 + a { 368 + color: inherit; 369 + font-weight: 500; 370 + 371 + &:hover { 372 + text-decoration: underline; 373 + } 374 + } 375 + } 376 + 377 + .metrics { 378 + display: flex; 379 + align-items: center; 380 + gap: 16px; 381 + margin-top: 12px; 382 + color: var(--text-secondary); 383 + } 384 + .stat { 385 + display: flex; 386 + align-items: center; 387 + gap: 8px; 388 + min-width: 0px; 389 + max-width: 100%; 390 + 391 + .count { 392 + padding-right: 8px; 393 + overflow: hidden; 394 + font-size: calc(var(--font-size) * 0.8125); 395 + line-height: calc(var(--font-size) * 1.25); 396 + text-overflow: ellipsis; 397 + white-space: nowrap; 398 + } 399 + } 400 + </style>
+271
packages/internal/components/highlighted-post.svelte
··· 1 + <script lang="ts"> 2 + import type { AppBskyFeedDefs, AppBskyFeedPost } from '@atcute/client/lexicons'; 3 + 4 + import { getPostUrl, getProfileUrl } from '../utils/bsky-url'; 5 + import { formatLongDate } from '../utils/date'; 6 + import { findLabel } from '../utils/labels'; 7 + import { formatCompactNumber, formatLongNumber } from '../utils/number'; 8 + import { parseAtUri } from '../utils/syntax/at-url'; 9 + 10 + import Embeds from './embeds/embeds.svelte'; 11 + import RichTextRenderer from './richtext-renderer.svelte'; 12 + 13 + interface Props { 14 + post: AppBskyFeedDefs.PostView; 15 + parent: AppBskyFeedDefs.PostView | null; 16 + prev?: boolean; 17 + } 18 + 19 + const { post, parent, prev = false }: Props = $props(); 20 + 21 + const author = post.author; 22 + const authorUrl = getProfileUrl(author.did); 23 + const authorName = author.displayName?.trim(); 24 + 25 + const record = post.record as AppBskyFeedPost.Record; 26 + const postUrl = getPostUrl(author.did, parseAtUri(post.uri).rkey); 27 + 28 + const replyCount = post.replyCount || 0; 29 + const likeCount = post.likeCount || 0; 30 + const repostCount = (post.repostCount || 0) + (post.quoteCount || 0); 31 + 32 + const isAuthorBlurred = !!findLabel(author.labels, author.did); 33 + </script> 34 + 35 + <div class="highlighted-post"> 36 + <div class="meta"> 37 + <a href={authorUrl} target="_blank" class="avatar-wrapper"> 38 + {#if author.avatar} 39 + <img 40 + loading="lazy" 41 + src={author.avatar} 42 + alt="" 43 + class={`avatar` + (isAuthorBlurred ? ` is-blurred` : ``)} 44 + /> 45 + {/if} 46 + </a> 47 + 48 + <a href={authorUrl} target="_blank" class="name-wrapper"> 49 + {#if authorName} 50 + <bdi class="display-name-wrapper"> 51 + <span class="display-name">{authorName}</span> 52 + </bdi> 53 + {/if} 54 + <span class="handle">@{author.handle}</span> 55 + </a> 56 + 57 + {#if !prev} 58 + <svg class="logo" fill="none" viewBox="0 0 320 286"> 59 + <path 60 + fill="#0A7AFF" 61 + d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z" 62 + /> 63 + </svg> 64 + {/if} 65 + </div> 66 + 67 + {#if !prev && record.reply} 68 + <p class="context"> 69 + {#if parent} 70 + {@const author = parent.author} 71 + 72 + Replying to 73 + <a target="_blank" href={getProfileUrl(author.did)} dir="auto"> 74 + {author.displayName?.trim() || `@${author.handle}`} 75 + </a> 76 + {:else} 77 + Replying to an unknown post 78 + {/if} 79 + </p> 80 + {/if} 81 + 82 + <RichTextRenderer text={record.text} facets={record.facets} large /> 83 + 84 + {#if post.embed} 85 + <Embeds {post} embed={post.embed} large /> 86 + {/if} 87 + 88 + <time datetime={record.createdAt} class="date"> 89 + {formatLongDate(record.createdAt)} 90 + </time> 91 + 92 + <div class="stats"> 93 + <span 94 + class="stat" 95 + title={likeCount === 1 ? `${formatLongNumber(likeCount)} like` : `${formatLongNumber(likeCount)} likes`} 96 + > 97 + <!-- heart-2 --> 98 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 99 + <path 100 + stroke="currentColor" 101 + stroke-width="2" 102 + d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z" 103 + /> 104 + </svg> 105 + 106 + <span>{formatCompactNumber(likeCount)}</span> 107 + </span> 108 + 109 + <span 110 + class="stat" 111 + title={repostCount === 1 112 + ? `${formatLongNumber(repostCount)} repost` 113 + : `${formatLongNumber(repostCount)} reposts`} 114 + > 115 + <!-- arrows-repeat-right-left --> 116 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 117 + <path 118 + stroke="currentColor" 119 + stroke-linecap="square" 120 + stroke-width="2" 121 + d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15" 122 + /> 123 + </svg> 124 + 125 + <span>{formatCompactNumber(repostCount)}</span> 126 + </span> 127 + 128 + <div class="gap"></div> 129 + 130 + <a href={postUrl} target="_blank" class="permalink"> 131 + <span> 132 + {!replyCount 133 + ? `View on Bluesky` 134 + : replyCount === 1 135 + ? `Read ${formatCompactNumber(replyCount)} reply on Bluesky` 136 + : `Read ${formatCompactNumber(replyCount)} replies on Bluesky`} 137 + </span> 138 + </a> 139 + </div> 140 + </div> 141 + 142 + <style> 143 + .highlighted-post { 144 + padding: 16px; 145 + } 146 + 147 + .meta { 148 + display: flex; 149 + align-items: center; 150 + gap: 12px; 151 + margin: 0 0 12px 0; 152 + color: var(--text-secondary); 153 + } 154 + 155 + .avatar-wrapper { 156 + display: block; 157 + flex-shrink: 0; 158 + border-radius: 9999px; 159 + background: var(--background-secondary); 160 + width: 40px; 161 + height: 40px; 162 + overflow: hidden; 163 + 164 + &:hover { 165 + filter: brightness(0.85); 166 + } 167 + } 168 + 169 + .avatar { 170 + width: 100%; 171 + height: 100%; 172 + object-fit: cover; 173 + } 174 + .is-blurred { 175 + scale: 125%; 176 + filter: blur(4px); 177 + } 178 + 179 + .name-wrapper { 180 + display: block; 181 + flex-grow: 1; 182 + max-width: 100%; 183 + overflow: hidden; 184 + color: inherit; 185 + text-overflow: ellipsis; 186 + white-space: nowrap; 187 + } 188 + .display-name-wrapper { 189 + overflow: hidden; 190 + text-overflow: ellipsis; 191 + 192 + .name-wrapper:hover & { 193 + text-decoration: underline; 194 + } 195 + } 196 + .display-name { 197 + color: var(--text-primary); 198 + font-weight: 700; 199 + } 200 + .handle { 201 + display: block; 202 + overflow: hidden; 203 + text-overflow: ellipsis; 204 + white-space: nowrap; 205 + } 206 + 207 + .logo { 208 + width: 32px; 209 + height: 32px; 210 + } 211 + 212 + .context { 213 + overflow: hidden; 214 + color: var(--text-secondary); 215 + font-size: calc(var(--font-size) * 0.8125); 216 + text-overflow: ellipsis; 217 + white-space: nowrap; 218 + 219 + a { 220 + color: inherit; 221 + font-weight: 500; 222 + 223 + &:hover { 224 + text-decoration: underline; 225 + } 226 + } 227 + } 228 + 229 + .date { 230 + display: flex; 231 + flex-wrap: wrap; 232 + align-items: center; 233 + gap: 8px; 234 + margin: 12px 0 0; 235 + border-bottom: 1px solid var(--divider); 236 + padding: 0 0 12px 0; 237 + color: var(--text-secondary); 238 + } 239 + 240 + .stats { 241 + display: flex; 242 + flex-wrap: wrap; 243 + align-items: center; 244 + gap: 8px 16px; 245 + margin: 0 0 -16px 0; 246 + padding: 12px 0; 247 + color: var(--text-secondary); 248 + 249 + .gap { 250 + flex: 1 1 auto; 251 + } 252 + 253 + .permalink { 254 + display: flex; 255 + align-items: center; 256 + gap: 4px; 257 + color: var(--text-link); 258 + font-weight: 700; 259 + 260 + &:hover { 261 + text-decoration: underline; 262 + } 263 + } 264 + } 265 + .stat { 266 + display: flex; 267 + align-items: center; 268 + gap: 8px; 269 + font-weight: 500; 270 + } 271 + </style>
+226
packages/internal/components/post.svelte
··· 1 + <script lang="ts"> 2 + import type { AppBskyFeedDefs, AppBskyFeedPost } from '@atcute/client/lexicons'; 3 + 4 + import { getPostUrl, getProfileUrl } from '../utils/bsky-url'; 5 + import { formatLongDate, formatShortDate } from '../utils/date'; 6 + import { findLabel } from '../utils/labels'; 7 + import { parseAtUri } from '../utils/syntax/at-url'; 8 + 9 + import Embeds from './embeds/embeds.svelte'; 10 + import RichtextRenderer from './richtext-renderer.svelte'; 11 + 12 + interface Props { 13 + post: AppBskyFeedDefs.PostView; 14 + parent?: AppBskyFeedDefs.PostView | null; 15 + prev?: boolean; 16 + } 17 + 18 + const { post, parent, prev }: Props = $props(); 19 + 20 + const author = post.author; 21 + const authorUrl = getProfileUrl(author.did); 22 + const authorName = author.displayName?.trim(); 23 + 24 + const record = post.record as AppBskyFeedPost.Record; 25 + const postUrl = getPostUrl(author.did, parseAtUri(post.uri).rkey); 26 + 27 + const isAuthorBlurred = !!findLabel(author.labels, author.did); 28 + </script> 29 + 30 + <div class="post"> 31 + {#if !prev} 32 + <svg class="logo" fill="none" viewBox="0 0 320 286"> 33 + <path 34 + fill="#0A7AFF" 35 + d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z" 36 + /> 37 + </svg> 38 + {/if} 39 + 40 + <div class="aside"> 41 + <a target="_blank" href={authorUrl} class="avatar-wrapper"> 42 + {#if author.avatar} 43 + <img 44 + loading="lazy" 45 + src={author.avatar} 46 + alt="" 47 + class={`avatar` + (isAuthorBlurred ? ` is-blurred` : ``)} 48 + /> 49 + {/if} 50 + </a> 51 + 52 + <div class="line"></div> 53 + </div> 54 + 55 + <div class="main"> 56 + <div class="meta"> 57 + <a href={authorUrl} target="_blank" class="name-wrapper"> 58 + {#if authorName} 59 + <bdi class="display-name-wrapper"> 60 + <span class="display-name">{authorName}</span> 61 + </bdi> 62 + {/if} 63 + 64 + <span class="handle">@{author.handle}</span> 65 + </a> 66 + 67 + <span aria-hidden="true" class="dot"> ยท </span> 68 + 69 + <a target="_blank" href={postUrl} title={formatLongDate(record.createdAt)} class="date"> 70 + <time datetime={record.createdAt}>{formatShortDate(record.createdAt)}</time> 71 + </a> 72 + </div> 73 + 74 + {#if !prev && record.reply} 75 + <p class="context"> 76 + {#if parent} 77 + {@const author = parent.author} 78 + 79 + Replying to 80 + <a target="_blank" href={getProfileUrl(author.did)} dir="auto"> 81 + {author.displayName?.trim() || `@${author.handle}`} 82 + </a> 83 + {:else} 84 + Replying to an unknown post 85 + {/if} 86 + </p> 87 + {/if} 88 + 89 + <RichtextRenderer text={record.text} facets={record.facets} /> 90 + 91 + {#if post.embed} 92 + <Embeds {post} embed={post.embed} /> 93 + {/if} 94 + </div> 95 + </div> 96 + 97 + <style> 98 + .post { 99 + display: flex; 100 + position: relative; 101 + gap: 12px; 102 + padding: 12px 16px 0 16px; 103 + } 104 + 105 + .logo { 106 + position: absolute; 107 + top: 12px; 108 + right: 12px; 109 + width: 24px; 110 + height: 24px; 111 + } 112 + 113 + .aside { 114 + flex-shrink: 0; 115 + } 116 + 117 + .avatar-wrapper { 118 + display: block; 119 + border-radius: 9999px; 120 + background: var(--background-secondary); 121 + width: 40px; 122 + height: 40px; 123 + overflow: hidden; 124 + 125 + &:hover { 126 + filter: brightness(0.85); 127 + } 128 + } 129 + 130 + .avatar { 131 + width: 100%; 132 + height: 100%; 133 + object-fit: cover; 134 + } 135 + .is-blurred { 136 + scale: 125%; 137 + filter: blur(4px); 138 + } 139 + 140 + .line { 141 + position: absolute; 142 + top: 56px; 143 + bottom: -12px; 144 + left: 35px; 145 + border-left: 2px solid var(--divider); 146 + } 147 + 148 + .main { 149 + display: flex; 150 + flex-grow: 1; 151 + flex-direction: column; 152 + min-width: 0px; 153 + } 154 + 155 + .meta { 156 + display: flex; 157 + align-items: center; 158 + margin: 0 0 2px 0; 159 + padding: 0 calc(24px + 8px) 0 0; 160 + color: var(--text-secondary); 161 + 162 + .name-wrapper { 163 + display: flex; 164 + gap: 4px; 165 + max-width: 100%; 166 + overflow: hidden; 167 + color: inherit; 168 + text-decoration: none; 169 + text-overflow: ellipsis; 170 + white-space: nowrap; 171 + } 172 + 173 + .display-name-wrapper { 174 + overflow: hidden; 175 + text-overflow: ellipsis; 176 + 177 + .name-wrapper:hover & { 178 + text-decoration: underline; 179 + } 180 + } 181 + 182 + .display-name { 183 + color: var(--text-primary); 184 + font-weight: 700; 185 + } 186 + 187 + .handle { 188 + display: block; 189 + overflow: hidden; 190 + text-overflow: ellipsis; 191 + white-space: nowrap; 192 + } 193 + 194 + .dot { 195 + flex-shrink: 0; 196 + margin: 0 6px; 197 + } 198 + 199 + .date { 200 + color: inherit; 201 + text-decoration: none; 202 + white-space: nowrap; 203 + 204 + &:hover { 205 + text-decoration: underline; 206 + } 207 + } 208 + } 209 + 210 + .context { 211 + overflow: hidden; 212 + color: var(--text-secondary); 213 + font-size: calc(var(--font-size) * 0.8125); 214 + text-overflow: ellipsis; 215 + white-space: nowrap; 216 + 217 + a { 218 + color: inherit; 219 + font-weight: 500; 220 + 221 + &:hover { 222 + text-decoration: underline; 223 + } 224 + } 225 + } 226 + </style>
+204
packages/internal/components/profile-card.svelte
··· 1 + <script lang="ts"> 2 + import type { AppBskyActorDefs } from '@atcute/client/lexicons'; 3 + 4 + import { getProfileUrl } from '../utils/bsky-url'; 5 + import { findLabel } from '../utils/labels'; 6 + import { formatCompactNumber } from '../utils/number'; 7 + import RichtextRawRenderer from './richtext-raw-renderer.svelte'; 8 + 9 + interface Props { 10 + profile: AppBskyActorDefs.ProfileViewDetailed; 11 + } 12 + 13 + const { profile }: Props = $props(); 14 + 15 + const url = getProfileUrl(profile.did); 16 + const isBlurred = findLabel(profile.labels, profile.did); 17 + </script> 18 + 19 + <div class="profile-card has-banner"> 20 + <div class="banner-wrapper"> 21 + {#if profile.banner} 22 + <img loading="lazy" src={profile.banner} alt="" class={`banner` + (isBlurred ? ` is-blurred` : ``)} /> 23 + {/if} 24 + </div> 25 + 26 + <div class="contents"> 27 + <div class="header"> 28 + <a href={url} target="_blank" class="avatar-wrapper"> 29 + {#if profile.avatar} 30 + <img 31 + loading="lazy" 32 + src={profile.avatar} 33 + alt="" 34 + class={`avatar` + (isBlurred ? ` is-blurred` : ``)} 35 + /> 36 + {/if} 37 + </a> 38 + 39 + <div class="actions"> 40 + <a href={url} target="_blank" class="follow-button"> 41 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 42 + <path 43 + stroke="currentColor" 44 + stroke-linecap="square" 45 + stroke-width="2" 46 + d="M12 4v8m0 0v8m0-8H4m8 0h8" 47 + /> 48 + </svg> 49 + <span>Follow</span> 50 + </a> 51 + 52 + <svg class="logo" fill="none" viewBox="0 0 320 286"> 53 + <path 54 + fill="#0A7AFF" 55 + d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z" 56 + /> 57 + </svg> 58 + </div> 59 + </div> 60 + 61 + <div class="name-wrapper"> 62 + <p dir="auto" class="display-name">{profile.displayName?.trim() || profile.handle.slice(0, 64)}</p> 63 + <p class="handle">@{profile.handle}</p> 64 + </div> 65 + 66 + <div class="stats"> 67 + <span class="stat-entry"> 68 + <span class="stat-count">{formatCompactNumber(profile.followersCount || 0)}</span> 69 + <span> {profile.followersCount === 1 ? `Follower` : `Followers`}</span> 70 + </span> 71 + 72 + <span class="stat-entry"> 73 + <span class="stat-count">{formatCompactNumber(profile.followsCount || 0)}</span> 74 + <span> Following</span> 75 + </span> 76 + </div> 77 + 78 + {#if profile.description?.trim()} 79 + <RichtextRawRenderer text={profile.description} /> 80 + {/if} 81 + </div> 82 + </div> 83 + 84 + <style> 85 + .profile-card { 86 + display: flex; 87 + flex-direction: column; 88 + } 89 + 90 + .is-blurred { 91 + scale: 125%; 92 + filter: blur(4px); 93 + } 94 + 95 + .banner-wrapper { 96 + background: var(--background-secondary); 97 + aspect-ratio: 3 / 1; 98 + overflow: hidden; 99 + } 100 + .banner { 101 + width: 100%; 102 + height: 100%; 103 + object-fit: cover; 104 + } 105 + 106 + .contents { 107 + display: flex; 108 + position: relative; 109 + flex-direction: column; 110 + gap: 8px; 111 + padding: 12px 16px 16px; 112 + } 113 + .logo { 114 + width: 24px; 115 + height: 24px; 116 + } 117 + 118 + .header { 119 + display: flex; 120 + justify-content: space-between; 121 + align-items: end; 122 + } 123 + .actions { 124 + display: flex; 125 + align-items: center; 126 + gap: 16px; 127 + } 128 + 129 + .avatar-wrapper { 130 + display: block; 131 + flex-shrink: 0; 132 + outline: 2px solid var(--background-primary); 133 + border-radius: 9999px; 134 + background: var(--background-secondary); 135 + width: 90px; 136 + height: 90px; 137 + overflow: hidden; 138 + 139 + .has-banner & { 140 + margin-top: calc(-90px + 34px); 141 + } 142 + } 143 + .avatar { 144 + width: 100%; 145 + height: 100%; 146 + object-fit: cover; 147 + 148 + .avatar-wrapper:hover & { 149 + filter: brightness(0.85); 150 + 151 + &.is-blurred { 152 + filter: brightness(0.85) blur(4px); 153 + } 154 + } 155 + } 156 + 157 + .follow-button { 158 + display: flex; 159 + align-items: center; 160 + gap: 6px; 161 + border-radius: 9999px; 162 + background: var(--button); 163 + padding: 9px 12px; 164 + color: var(--button-text); 165 + font-weight: 600; 166 + font-size: calc(var(--font-size) * 0.8125); 167 + line-height: calc(var(--font-size) * 1); 168 + user-select: none; 169 + 170 + .icon { 171 + font-size: 16px; 172 + } 173 + 174 + &:hover { 175 + background: var(--button-hover); 176 + } 177 + } 178 + 179 + .display-name { 180 + font-weight: 700; 181 + font-size: calc(var(--font-size) * 1.25); 182 + line-height: calc(var(--font-size) * 1.75); 183 + overflow-wrap: break-word; 184 + } 185 + .handle { 186 + color: var(--text-secondary); 187 + overflow-wrap: break-word; 188 + } 189 + 190 + .stats { 191 + display: flex; 192 + flex-wrap: wrap; 193 + gap: 20px; 194 + 195 + min-width: 0; 196 + } 197 + .stat-entry { 198 + color: var(--text-secondary); 199 + } 200 + .stat-count { 201 + color: var(--text-primary); 202 + font-weight: 700; 203 + } 204 + </style>
+54
packages/internal/components/profile-feed-header.svelte
··· 1 + <script lang="ts"> 2 + import type { AppBskyActorDefs } from '@atcute/client/lexicons'; 3 + 4 + import { getProfileUrl } from '../utils/bsky-url'; 5 + 6 + interface Props { 7 + profile: AppBskyActorDefs.ProfileViewDetailed; 8 + } 9 + 10 + const { profile }: Props = $props(); 11 + </script> 12 + 13 + <div class="profile-feed-header"> 14 + <a target="_blank" href={getProfileUrl(profile.did)} class="title">Posts from @{profile.handle}</a> 15 + 16 + <svg class="logo" fill="none" viewBox="0 0 320 286"> 17 + <path 18 + fill="#0A7AFF" 19 + d="M69.364 19.146c36.687 27.806 76.147 84.186 90.636 114.439 14.489-30.253 53.948-86.633 90.636-114.439C277.107-.917 320-16.44 320 32.957c0 9.865-5.603 82.875-8.889 94.729-11.423 41.208-53.045 51.719-90.071 45.357 64.719 11.12 81.182 47.953 45.627 84.785-80 82.874-106.667-44.333-106.667-44.333s-26.667 127.207-106.667 44.333c-35.555-36.832-19.092-73.665 45.627-84.785-37.026 6.362-78.648-4.149-90.071-45.357C5.603 115.832 0 42.822 0 32.957 0-16.44 42.893-.917 69.364 19.147Z" 20 + /> 21 + </svg> 22 + </div> 23 + 24 + <style> 25 + .profile-feed-header { 26 + display: flex; 27 + justify-content: space-between; 28 + align-items: center; 29 + gap: 16px; 30 + container-type: inline-size; 31 + border-bottom: 1px solid var(--divider); 32 + padding: 12px 16px; 33 + } 34 + 35 + .title { 36 + padding: 4px 0; 37 + min-width: 0; 38 + overflow: hidden; 39 + font-weight: 600; 40 + font-size: calc(var(--font-size) * 1); 41 + line-height: calc(var(--font-size) * 1.5); 42 + text-overflow: ellipsis; 43 + white-space: nowrap; 44 + 45 + &:hover { 46 + text-decoration: underline; 47 + } 48 + } 49 + 50 + .logo { 51 + width: 24px; 52 + height: 24px; 53 + } 54 + </style>
+53
packages/internal/components/richtext-raw-renderer.svelte
··· 1 + <script lang="ts" module> 2 + const HTTP_RE = /^https?:\/\//; 3 + </script> 4 + 5 + <script lang="ts"> 6 + import { tokenize } from '@atcute/bluesky-richtext-parser'; 7 + 8 + import { getHashtagUrl, getProfileUrl } from '../utils/bsky-url'; 9 + 10 + interface Props { 11 + text: string; 12 + } 13 + 14 + const { text }: Props = $props(); 15 + </script> 16 + 17 + <p class="rich-text is-small"> 18 + {#each tokenize(text) as token} 19 + {#if token.type === 'autolink'} 20 + <a target="_blank" href={token.url} rel="noopener nofollow" class="link"> 21 + {token.raw.replace(HTTP_RE, '')} 22 + </a> 23 + {:else if token.type === 'mention'} 24 + <a target="_blank" href={getProfileUrl(token.handle)} class="mention">{token.raw}</a> 25 + {:else if token.type === 'topic'} 26 + <a target="_blank" href={getHashtagUrl(token.name)} class="hashtag">{token.raw}</a> 27 + {:else} 28 + {token.raw} 29 + {/if} 30 + {/each} 31 + </p> 32 + 33 + <style> 34 + .rich-text { 35 + overflow: hidden; 36 + white-space: pre-wrap; 37 + overflow-wrap: break-word; 38 + 39 + &:empty { 40 + display: none; 41 + } 42 + } 43 + 44 + .link, 45 + .mention, 46 + .hashtag { 47 + color: var(--text-link); 48 + 49 + &:hover { 50 + text-decoration: underline; 51 + } 52 + } 53 + </style>
+66
packages/internal/components/richtext-renderer.svelte
··· 1 + <script lang="ts" module> 2 + import { segmentize, type Facet, type FacetFeature } from '@atcute/bluesky-richtext-segmenter'; 3 + 4 + import { getHashtagUrl, getProfileUrl } from '../utils/bsky-url'; 5 + 6 + const grabFirstSupported = (features: FacetFeature[] | undefined): FacetFeature | undefined => { 7 + return features?.find( 8 + (feature) => 9 + feature.$type === 'app.bsky.richtext.facet#link' || 10 + feature.$type === 'app.bsky.richtext.facet#mention' || 11 + feature.$type === 'app.bsky.richtext.facet#tag', 12 + ); 13 + }; 14 + </script> 15 + 16 + <script lang="ts"> 17 + interface Props { 18 + text: string; 19 + facets?: Facet[]; 20 + large?: boolean; 21 + } 22 + 23 + const { text, facets, large }: Props = $props(); 24 + </script> 25 + 26 + <p class={`rich-text` + (large ? ` is-large` : ` is-small`)}> 27 + {#each segmentize(text, facets) as segment} 28 + {@const feature = grabFirstSupported(segment.features)} 29 + 30 + {#if !feature} 31 + {segment.text} 32 + {:else if feature.$type === 'app.bsky.richtext.facet#link'} 33 + <a target="_blank" href={feature.uri} rel="noopener nofollow" class="link">{segment.text}</a> 34 + {:else if feature.$type === 'app.bsky.richtext.facet#mention'} 35 + <a target="_blank" href={getProfileUrl(feature.did)} class="mention">{segment.text}</a> 36 + {:else if feature.$type === 'app.bsky.richtext.facet#tag'} 37 + <a target="_blank" href={getHashtagUrl(feature.tag)} class="hashtag">{segment.text}</a> 38 + {/if} 39 + {/each} 40 + </p> 41 + 42 + <style> 43 + .rich-text { 44 + overflow: hidden; 45 + white-space: pre-wrap; 46 + overflow-wrap: break-word; 47 + 48 + &:empty { 49 + display: none; 50 + } 51 + } 52 + .is-large { 53 + font-size: calc(var(--font-size) * 1); 54 + line-height: calc(var(--font-size) * 1.5); 55 + } 56 + 57 + .link, 58 + .mention, 59 + .hashtag { 60 + color: var(--text-link); 61 + 62 + &:hover { 63 + text-decoration: underline; 64 + } 65 + } 66 + </style>
+34
packages/internal/package.json
··· 1 + { 2 + "private": true, 3 + "type": "module", 4 + "name": "internal", 5 + "version": "0.1.0", 6 + "exports": { 7 + "./components/*": "./components/*", 8 + "./types/*": "./types/*", 9 + "./utils/*": "./utils/*" 10 + }, 11 + "peerDependencies": { 12 + "@atcute/bluesky": "^1.0.9", 13 + "@atcute/bluesky-richtext-parser": "^1.0.7", 14 + "@atcute/bluesky-richtext-segmenter": "^1.0.5", 15 + "@atcute/client": "^2.0.6", 16 + "svelte": "catalog:" 17 + }, 18 + "peerDependenciesMeta": { 19 + "@atcute/bluesky-richtext-parser": { 20 + "optional": true 21 + }, 22 + "@atcute/bluesky-richtext-segmenter": { 23 + "optional": true 24 + } 25 + }, 26 + "devDependencies": { 27 + "@atcute/bluesky": "^2.1.1", 28 + "@atcute/bluesky-richtext-parser": "^1.0.7", 29 + "@atcute/bluesky-richtext-segmenter": "^2.0.4", 30 + "@atcute/client": "^3.1.0", 31 + "@tsconfig/svelte": "^5.0.6", 32 + "svelte": "catalog:" 33 + } 34 + }
+8
packages/internal/svelte.config.js
··· 1 + export default { 2 + compilerOptions: { 3 + warningFilter: (warning) => { 4 + if (warning.code === 'state_referenced_locally') return false; 5 + return true; 6 + }, 7 + }, 8 + };
+13
packages/internal/tsconfig.json
··· 1 + { 2 + "extends": "@tsconfig/svelte/tsconfig.json", 3 + "compilerOptions": { 4 + "types": ["@atcute/bluesky/lexicons"], 5 + "target": "ESNext", 6 + "useDefineForClassFields": true, 7 + "module": "ESNext", 8 + "resolveJsonModule": true, 9 + "isolatedModules": true, 10 + "moduleDetection": "force", 11 + "noEmit": true, 12 + }, 13 + }
+7
packages/internal/types/post.ts
··· 1 + import type { AppBskyFeedDefs, Brand } from '@atcute/client/lexicons'; 2 + 3 + export interface PostData { 4 + thread: Brand.Union<AppBskyFeedDefs.ThreadViewPost> | null; 5 + contextless: boolean; 6 + allowUnauthenticated: boolean; 7 + }
+6
packages/internal/types/profile-card.ts
··· 1 + import type { AppBskyActorDefs } from '@atcute/client/lexicons'; 2 + 3 + export interface ProfileCardData { 4 + profile: AppBskyActorDefs.ProfileViewDetailed | null; 5 + allowUnauthenticated: boolean; 6 + }
+7
packages/internal/types/profile-feed.ts
··· 1 + import type { AppBskyActorDefs, AppBskyFeedDefs } from '@atcute/client/lexicons'; 2 + 3 + export interface ProfileFeedData { 4 + profile: AppBskyActorDefs.ProfileViewDetailed | null; 5 + feed: AppBskyFeedDefs.FeedViewPost[]; 6 + allowUnauthenticated: boolean; 7 + }
+27
packages/internal/utils/bsky-url.ts
··· 1 + export const getProfileUrl = (author: string): string => { 2 + return `https://bsky.app/profile/${author}`; 3 + }; 4 + 5 + export const getPostUrl = (author: string, rkey: string): string => { 6 + return `https://bsky.app/profile/${author}/post/${rkey}`; 7 + }; 8 + 9 + export const getHashtagUrl = (tag: string): string => { 10 + return `https://bsky.app/hashtag/${tag}`; 11 + }; 12 + 13 + export const getFeedUrl = (author: string, rkey: string): string => { 14 + return `https://bsky.app/profile/${author}/feed/${rkey}`; 15 + }; 16 + 17 + export const getListUrl = (author: string, rkey: string): string => { 18 + return `https://bsky.app/profile/${author}/list/${rkey}`; 19 + }; 20 + 21 + export const getStarterpackUrl = (author: string, rkey: string): string => { 22 + return `https://bsky.app/starter-pack/${author}/${rkey}`; 23 + }; 24 + 25 + export const getStarterpackImgUrl = (author: string, rkey: string): string => { 26 + return `https://ogcard.cdn.bsky.app/start/${author}/${rkey}`; 27 + };
+3
packages/internal/utils/constants.ts
··· 1 + export const DEFAULT_APPVIEW_URL = 'https://public.api.bsky.app'; 2 + 3 + export const NO_UNAUTHENTICATED_LABEL = '!no-unauthenticated';
+44
packages/internal/utils/date.ts
··· 1 + let startOfYear = 0; 2 + let endOfYear = 0; 3 + 4 + const fmtAbsoluteLong = new Intl.DateTimeFormat('en-US', { dateStyle: 'long', timeStyle: 'short' }); 5 + const fmtAbsShortWithYear = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' }); 6 + const fmtAbsShort = new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' }); 7 + 8 + export const formatShortDate = (date: string | number): string => { 9 + const inst = new Date(date); 10 + const time = inst.getTime(); 11 + 12 + if (isNaN(time)) { 13 + return 'N/A'; 14 + } 15 + 16 + const now = Date.now(); 17 + if (now > endOfYear) { 18 + const date = new Date(now); 19 + 20 + date.setMonth(0, 1); 21 + date.setHours(0, 0, 0); 22 + startOfYear = date.getTime(); 23 + 24 + date.setFullYear(date.getFullYear() + 1, 0, 0); 25 + date.setHours(23, 59, 59, 999); 26 + endOfYear = date.getTime(); 27 + } 28 + 29 + if (time >= startOfYear && time <= endOfYear) { 30 + return fmtAbsShort.format(inst); 31 + } 32 + 33 + return fmtAbsShortWithYear.format(inst); 34 + }; 35 + 36 + export const formatLongDate = (date: string | number): string => { 37 + const inst = new Date(date); 38 + 39 + if (isNaN(inst.getTime())) { 40 + return 'N/A'; 41 + } 42 + 43 + return fmtAbsoluteLong.format(inst); 44 + };
+60
packages/internal/utils/labels.ts
··· 1 + import type { At, ComAtprotoLabelDefs } from '@atcute/client/lexicons'; 2 + 3 + export const FlagsNone = 0; 4 + export const FlagsNoSelf = 1 << 0; 5 + 6 + type Label = ComAtprotoLabelDefs.Label; 7 + 8 + export interface LabelDefinition { 9 + name: string; 10 + flags: number; 11 + } 12 + 13 + export const LABEL_MAPPING: Record<string, LabelDefinition> = { 14 + '!hide': { 15 + name: `Hidden by moderators`, 16 + flags: FlagsNoSelf, 17 + }, 18 + '!warn': { 19 + name: `Content warning`, 20 + flags: FlagsNoSelf, 21 + }, 22 + 23 + porn: { 24 + name: `Adult content`, 25 + flags: FlagsNone, 26 + }, 27 + sexual: { 28 + name: `Sexually suggestive`, 29 + flags: FlagsNone, 30 + }, 31 + 'graphic-media': { 32 + name: `Graphic media`, 33 + flags: FlagsNone, 34 + }, 35 + nudity: { 36 + name: `Nudity`, 37 + flags: FlagsNone, 38 + }, 39 + }; 40 + 41 + export const findLabel = (labels: Label[] | undefined, authorDid: At.Did): LabelDefinition | undefined => { 42 + if (labels?.length) { 43 + for (let idx = 0, len = labels.length; idx < len; idx++) { 44 + const label = labels[idx]; 45 + const val = label.val; 46 + 47 + if (!(val in LABEL_MAPPING)) { 48 + continue; 49 + } 50 + 51 + const def = LABEL_MAPPING[val]; 52 + 53 + if (def.flags & FlagsNoSelf && label.src === authorDid) { 54 + continue; 55 + } 56 + 57 + return def; 58 + } 59 + } 60 + };
+18
packages/internal/utils/number.ts
··· 1 + const long = new Intl.NumberFormat('en-US'); 2 + const compact = new Intl.NumberFormat('en-US', { notation: 'compact' }); 3 + 4 + export const formatCompactNumber = (value: number) => { 5 + if (value < 1_000) { 6 + return '' + value; 7 + } 8 + 9 + if (value < 100_000) { 10 + return long.format(value); 11 + } 12 + 13 + return compact.format(value); 14 + }; 15 + 16 + export const formatLongNumber = (value: number) => { 17 + return long.format(value); 18 + };
+27
packages/internal/utils/syntax/at-url.ts
··· 1 + export const AT_URI_RE = 2 + /^at:\/\/((?:did:[a-zA-Z0-9._:%-]+)|(?:[a-zA-Z0-9][a-zA-Z0-9-.]*))(?:\/([a-zA-Z0-9.-]+)(?:\/([a-zA-Z0-9_~.:-]{1,512}))?)?\/?(?:\?([^#\s]*))?(?:#([^\s]*))?$/; 3 + 4 + export interface ParsedAtUri { 5 + repo: string; 6 + collection: string; 7 + rkey: string; 8 + query: string; 9 + fragment: string; 10 + } 11 + 12 + export const parseAtUri = (str: string): ParsedAtUri => { 13 + const match = AT_URI_RE.exec(str); 14 + if (!match) { 15 + throw new InvalidAtUriError(`invalid at-uri: ${str}`); 16 + } 17 + 18 + return { 19 + repo: match[1], 20 + collection: match[2] ?? '', 21 + rkey: match[3] ?? '', 22 + query: match[4] ?? '', 23 + fragment: match[5] ?? '', 24 + }; 25 + }; 26 + 27 + export class InvalidAtUriError extends Error {}
+1
packages/svelte-site/.gitignore
··· 1 + pages/
+66
packages/svelte-site/README.md
··· 1 + # Svelte + TS + Vite 2 + 3 + This template should help get you started developing with Svelte and TypeScript in Vite. 4 + 5 + ## Recommended IDE Setup 6 + 7 + [VS Code](https://code.visualstudio.com/) + 8 + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). 9 + 10 + ## Need an official Svelte framework? 11 + 12 + Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy 13 + anywhere with its serverless-first approach and adapt to various platforms, with out of the box 14 + support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, 15 + Tailwind CSS, and more. 16 + 17 + ## Technical considerations 18 + 19 + **Why use this over SvelteKit?** 20 + 21 + - It brings its own routing solution which might not be preferable for some users. 22 + - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. 23 + 24 + This template contains as little as possible to get started with Vite + TypeScript + Svelte, while 25 + taking into account the developer experience with regards to HMR and intellisense. It demonstrates 26 + capabilities on par with the other `create-vite` templates and is a good starting point for 27 + beginners dipping their toes into a Vite + Svelte project. 28 + 29 + Should you later need the extended capabilities and extensibility provided by SvelteKit, the 30 + template has been structured similarly to SvelteKit so that it is easy to migrate. 31 + 32 + **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** 33 + 34 + Setting `compilerOptions.types` shuts out all other types not explicitly listed in the 35 + configuration. Using triple-slash references keeps the default TypeScript setting of accepting type 36 + information from the entire workspace, while also adding `svelte` and `vite/client` type 37 + information. 38 + 39 + **Why include `.vscode/extensions.json`?** 40 + 41 + Other templates indirectly recommend extensions via the README, but this file allows VS Code to 42 + prompt the user to install the recommended extension upon opening the project. 43 + 44 + **Why enable `allowJs` in the TS template?** 45 + 46 + While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not 47 + prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force 48 + `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase 49 + is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there 50 + are valid use cases in which a mixed codebase may be relevant. 51 + 52 + **Why is HMR not preserving my local component state?** 53 + 54 + HMR state preservation comes with a number of gotchas! It has been disabled by default in both 55 + `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read 56 + the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). 57 + 58 + If you have state that's important to retain within a component, consider creating an external store 59 + which would not be replaced by HMR. 60 + 61 + ```ts 62 + // store.ts 63 + // An extremely simple external store 64 + import { writable } from 'svelte/store'; 65 + export default writable(0); 66 + ```
+12
packages/svelte-site/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>Bluesky embed</title> 7 + </head> 8 + <body> 9 + <div id="app"></div> 10 + <script type="module" src="./src/main.ts"></script> 11 + </body> 12 + </html>
+30
packages/svelte-site/package.json
··· 1 + { 2 + "name": "svelte-site", 3 + "private": true, 4 + "version": "0.0.0", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite", 8 + "build": "vite build", 9 + "preview": "vite preview", 10 + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json", 11 + "publish": "pnpm run build && ./scripts/publish.sh" 12 + }, 13 + "dependencies": { 14 + "@atcute/bluesky": "^2.1.1", 15 + "@atcute/client": "^3.1.0", 16 + "bluesky-post-embed": "workspace:^", 17 + "bluesky-profile-card-embed": "workspace:^", 18 + "bluesky-profile-feed-embed": "workspace:^", 19 + "internal": "workspace:^" 20 + }, 21 + "devDependencies": { 22 + "@sveltejs/vite-plugin-svelte": "^6.2.1", 23 + "@tsconfig/svelte": "^5.0.6", 24 + "svelte": "^5.45.5", 25 + "svelte-check": "^4.3.4", 26 + "terser": "^5.44.1", 27 + "tslib": "^2.8.1", 28 + "vite": "^7.2.6" 29 + } 30 + }
+18
packages/svelte-site/scripts/publish.sh
··· 1 + #!/usr/bin/env bash 2 + 3 + set -euo pipefail 4 + 5 + if [[ -n $(git status --porcelain) ]]; then 6 + echo 'Working directory is not clean' 7 + git status --short 8 + exit 1 9 + fi 10 + 11 + GIT_COMMIT=$(git rev-parse HEAD) 12 + 13 + rsync -aHAX --delete --exclude=.git --exclude=.nojekyll dist/ pages/ 14 + touch pages/.nojekyll 15 + 16 + git -C pages/ add . 17 + git -C pages/ commit -m "deploy: ${GIT_COMMIT}" 18 + git -C pages/ push
+159
packages/svelte-site/src/App.svelte
··· 1 + <script lang="ts" module> 2 + const PostDisplay = () => import('./components/display/PostDisplay.svelte'); 3 + const ProfileCardDisplay = () => import('./components/display/ProfileCardDisplay.svelte'); 4 + const ProfileFeedDisplay = () => import('./components/display/ProfileFeedDisplay.svelte'); 5 + </script> 6 + 7 + <script lang="ts"> 8 + import { extract_url } from './lib/matcher'; 9 + 10 + import Banner from './components/Banner.svelte'; 11 + import CircularSpinner from './components/CircularSpinner.svelte'; 12 + import Field from './components/Field.svelte'; 13 + import Lazy from './components/Lazy.svelte'; 14 + import TextInput from './components/TextInput.svelte'; 15 + 16 + const DEFAULT_URL = 'https://bsky.app/profile/did:plc:ragtjsm2j2vknwkz3zp4oxrd/post/3kj2umze7zj2n'; 17 + // const DEFAULT_URL = 'https://bsky.app/profile/did:plc:ragtjsm2j2vknwkz3zp4oxrd'; 18 + 19 + let url = $state(''); 20 + let profile_type = $state<'card' | 'feed'>('feed'); 21 + 22 + const matched = $derived(extract_url(url || DEFAULT_URL)); 23 + </script> 24 + 25 + <div class="app"> 26 + <h1 class="header"> 27 + <code>&lt;bluesky-embed&gt;</code> 28 + </h1> 29 + 30 + <Field label="Bluesky post or profile URL"> 31 + <TextInput type="url" bind:value={url} placeholder={DEFAULT_URL} /> 32 + </Field> 33 + 34 + {#if matched && matched.type === 'profile'} 35 + <fieldset class="choices"> 36 + <label class="choice"> 37 + <input type="radio" name="profile-type" value="feed" bind:group={profile_type} /> 38 + <span>Profile feed</span> 39 + </label> 40 + 41 + <label class="choice"> 42 + <input type="radio" name="profile-type" value="card" bind:group={profile_type} /> 43 + <span>Profile card</span> 44 + </label> 45 + </fieldset> 46 + {/if} 47 + 48 + <main class="main"> 49 + {#if !matched} 50 + <Banner type="alert">Invalid URL, did you type it correctly?</Banner> 51 + {:else if matched.type === 'post'} 52 + <Lazy loader={PostDisplay} fallback={LazyFallback} boundary={LazyBoundary}> 53 + {#snippet children(Component)} 54 + <Component {matched} /> 55 + {/snippet} 56 + </Lazy> 57 + {:else if matched.type === 'profile'} 58 + <Lazy 59 + loader={profile_type === 'card' ? ProfileCardDisplay : ProfileFeedDisplay} 60 + fallback={LazyFallback} 61 + boundary={LazyBoundary} 62 + > 63 + {#snippet children(Component)} 64 + <Component {matched} /> 65 + {/snippet} 66 + </Lazy> 67 + {/if} 68 + </main> 69 + 70 + <footer class="footer"> 71 + <span> 72 + made with โค๏ธ by <a href="https://bsky.app/profile/did:plc:ia76kvnndjutgedggx2ibrem">@mary.my.id</a> 73 + </span> 74 + <span aria-hidden="true"> ยท </span> 75 + <span> 76 + <a href="https://github.com/mary-ext/bluesky-embed">source code</a> 77 + </span> 78 + <span aria-hidden="true"> ยท </span> 79 + <span>MIT License</span> 80 + </footer> 81 + </div> 82 + 83 + {#snippet LazyFallback()} 84 + <CircularSpinner /> 85 + {/snippet} 86 + 87 + {#snippet LazyBoundary(err: unknown)} 88 + <Banner type="alert"> 89 + {'' + err} 90 + </Banner> 91 + {/snippet} 92 + 93 + <style> 94 + .app { 95 + margin: 0 auto; 96 + padding: 36px 16px; 97 + width: 100%; 98 + max-width: calc(550px + (16 * 2px)); 99 + } 100 + 101 + .header { 102 + margin: 24px 0; 103 + } 104 + 105 + .main { 106 + margin: 36px 0; 107 + } 108 + 109 + .choices { 110 + display: flex; 111 + flex-direction: column; 112 + gap: 8px; 113 + margin: 16px 0 36px 0; 114 + border: 0; 115 + padding: 0; 116 + } 117 + .choice { 118 + display: flex; 119 + align-items: center; 120 + gap: 8px; 121 + font-size: 0.875rem; 122 + line-height: 1.25rem; 123 + 124 + input { 125 + appearance: none; 126 + outline: 2px none #2563eb; 127 + outline-offset: 2px; 128 + border: 1px solid #9ca3af; 129 + border-radius: 8px; 130 + width: 16px; 131 + height: 16px; 132 + 133 + &:checked { 134 + border: 0; 135 + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); 136 + background-color: #2563eb; 137 + } 138 + &:focus { 139 + outline-style: solid; 140 + } 141 + } 142 + } 143 + 144 + .footer { 145 + display: flex; 146 + flex-wrap: wrap; 147 + gap: 0.5rem; 148 + margin: 36px 0 0 0; 149 + border-top: 1px solid #d1d5db; 150 + padding: 36px 0 0 0; 151 + color: #4b5563; 152 + font-size: 0.875rem; 153 + line-height: 1.25rem; 154 + 155 + a { 156 + color: #2563eb; 157 + } 158 + } 159 + </style>
+42
packages/svelte-site/src/components/Banner.svelte
··· 1 + <script lang="ts"> 2 + import type { Snippet } from 'svelte'; 3 + 4 + interface Props { 5 + type: 'alert' | 'inform'; 6 + children: Snippet<[]>; 7 + } 8 + 9 + let { type, children }: Props = $props(); 10 + </script> 11 + 12 + <div class="banner" class:type-alert={type === 'alert'} class:type-inform={type === 'inform'}> 13 + {@render children()} 14 + </div> 15 + 16 + <style> 17 + .banner { 18 + border: 1px solid; 19 + border-radius: 4px; 20 + padding: 10px 12px; 21 + font-weight: 500; 22 + font-size: 0.875rem; 23 + line-height: 1.25rem; 24 + 25 + :global(a) { 26 + color: inherit; 27 + font-weight: 600; 28 + } 29 + } 30 + 31 + .type-alert { 32 + border-color: #fca5a5; 33 + background: #fee2e2; 34 + color: #991b1b; 35 + } 36 + 37 + .type-inform { 38 + border-color: #bfdbfe; 39 + background: #dbeafe; 40 + color: #1e40af; 41 + } 42 + </style>
+42
packages/svelte-site/src/components/CircularSpinner.svelte
··· 1 + <script lang="ts"> 2 + interface Props {} 3 + 4 + let {}: Props = $props(); 5 + </script> 6 + 7 + <svg viewBox="0 0 32 32" class="circular-spinner"> 8 + <circle cx="16" cy="16" fill="none" r="14" stroke-width="4" class="background" /> 9 + <circle 10 + cx="16" 11 + cy="16" 12 + fill="none" 13 + r="14" 14 + stroke-width="4" 15 + stroke-dasharray="80px" 16 + stroke-dashoffset="60px" 17 + class="accented" 18 + /> 19 + </svg> 20 + 21 + <style> 22 + .circular-spinner { 23 + display: block; 24 + animation: spin 1s linear infinite; 25 + margin: 0 auto; 26 + width: 24px; 27 + height: 24px; 28 + } 29 + @keyframes spin { 30 + to { 31 + transform: rotate(360deg); 32 + } 33 + } 34 + 35 + .accented { 36 + stroke: #2563eb; 37 + } 38 + .background { 39 + opacity: 20%; 40 + stroke: #2563eb; 41 + } 42 + </style>
+100
packages/svelte-site/src/components/CodeBlock.svelte
··· 1 + <script lang="ts"> 2 + interface Props { 3 + code: string; 4 + } 5 + 6 + let { code }: Props = $props(); 7 + </script> 8 + 9 + <div class="code-block"> 10 + <pre><code>{code}</code></pre> 11 + 12 + <div class="actions"> 13 + <button 14 + title="Copy" 15 + aria-label="Copy" 16 + class="action-button" 17 + onclick={() => { 18 + navigator.clipboard.writeText(code).catch(() => alert(`Failed to copy to clipboard`)); 19 + }} 20 + > 21 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 22 + <path 23 + stroke="currentColor" 24 + stroke-linecap="square" 25 + stroke-width="2" 26 + d="M15 5h4v16H5V5h4m0-2h6v4H9V3Z" 27 + /> 28 + </svg> 29 + </button> 30 + </div> 31 + </div> 32 + 33 + <style> 34 + .code-block { 35 + display: flex; 36 + gap: 12px; 37 + border: 1px solid #d1d5db; 38 + border-radius: 4px; 39 + background: #f9fafb; 40 + padding: 12px; 41 + overflow: hidden; 42 + overflow-x: auto; 43 + 44 + pre { 45 + flex-grow: 1; 46 + margin: 0; 47 + font-size: 0.75rem; 48 + line-height: 1.25rem; 49 + } 50 + } 51 + 52 + .actions { 53 + position: sticky; 54 + top: 0; 55 + right: 0; 56 + } 57 + .action-button { 58 + display: flex; 59 + justify-content: center; 60 + align-items: center; 61 + cursor: pointer; 62 + box-shadow: 63 + 0 1px 3px 0 rgb(0 0 0 / 0.1), 64 + 0 1px 2px -1px rgb(0 0 0 / 0.1); 65 + border: 1px solid #d1d5db; 66 + border-radius: 4px; 67 + background: #ffffff; 68 + padding: 0; 69 + width: 32px; 70 + height: 32px; 71 + color: #4b5563; 72 + 73 + @media (pointer: fine) { 74 + opacity: 0; 75 + transition: 75ms ease-in; 76 + 77 + .code-block:hover &, 78 + .code-block:focus-within & { 79 + opacity: 1; 80 + } 81 + 82 + &:hover { 83 + border-color: #9ca3af; 84 + background: #e5e7eb; 85 + color: #1f2937; 86 + } 87 + } 88 + 89 + &:active { 90 + border-color: #9ca3af; 91 + background: #e5e7eb; 92 + color: #1f2937; 93 + } 94 + } 95 + 96 + .icon { 97 + width: 16px; 98 + height: 16px; 99 + } 100 + </style>
+36
packages/svelte-site/src/components/Field.svelte
··· 1 + <script lang="ts"> 2 + import type { Snippet } from 'svelte'; 3 + 4 + interface Props { 5 + label: string; 6 + children: Snippet; 7 + } 8 + 9 + let { label, children }: Props = $props(); 10 + </script> 11 + 12 + <div class="field"> 13 + <label class="input-wrapper"> 14 + <span class="label">{label}</span> 15 + {@render children()} 16 + </label> 17 + </div> 18 + 19 + <style> 20 + .field { 21 + display: flex; 22 + flex-direction: column; 23 + gap: 8px; 24 + } 25 + 26 + .input-wrapper { 27 + display: contents; 28 + } 29 + 30 + .label { 31 + color: #4b5563; 32 + font-weight: 600; 33 + font-size: 0.875rem; 34 + line-height: 1.25rem; 35 + } 36 + </style>
+37
packages/svelte-site/src/components/Lazy.svelte
··· 1 + <script lang="ts" module> 2 + import type { Snippet, Component } from 'svelte'; 3 + 4 + type SvelteComponentModule<C extends Component = Component> = { default: C }; 5 + type LoaderFunction<C extends Component = Component> = () => Promise<SvelteComponentModule<C>>; 6 + 7 + const map = new WeakMap<LoaderFunction, Promise<Component>>(); 8 + const get_promise = <C extends Component>(fn: LoaderFunction<C>): Promise<C> => { 9 + let promise = map.get(fn) satisfies Promise<Component> | undefined; 10 + if (promise === undefined) { 11 + map.set(fn, (promise = fn().then((mod) => mod.default))); 12 + } 13 + 14 + return promise as Promise<C>; 15 + }; 16 + </script> 17 + 18 + <script lang="ts" generics="C extends Component<any>"> 19 + interface Props { 20 + loader: LoaderFunction<C>; 21 + children: Snippet<[component: C]>; 22 + fallback: Snippet<[]>; 23 + boundary: Snippet<[error: unknown]>; 24 + } 25 + 26 + let { loader, children, fallback, boundary }: Props = $props(); 27 + 28 + const promise = $derived(get_promise(loader)); 29 + </script> 30 + 31 + {#await promise} 32 + {@render fallback()} 33 + {:then component} 34 + {@render children(component)} 35 + {:catch err} 36 + {@render boundary(err)} 37 + {/await}
+30
packages/svelte-site/src/components/TextInput.svelte
··· 1 + <script lang="ts"> 2 + interface Props { 3 + type?: 'text' | 'url'; 4 + value?: string; 5 + placeholder?: string; 6 + } 7 + 8 + let { type, value = $bindable(), placeholder }: Props = $props(); 9 + </script> 10 + 11 + <input {type} {placeholder} bind:value class="text-input" /> 12 + 13 + <style> 14 + .text-input { 15 + outline: 2px none #2563eb; 16 + outline-offset: -1px; 17 + border: 1px solid #9ca3af; 18 + border-radius: 4px; 19 + padding: 8px 12px; 20 + font-size: 0.875rem; 21 + line-height: 1.25rem; 22 + 23 + &::placeholder { 24 + color: #9ca3af; 25 + } 26 + &:focus { 27 + outline-style: solid; 28 + } 29 + } 30 + </style>
+115
packages/svelte-site/src/components/display/PostDisplay.svelte
··· 1 + <script lang="ts"> 2 + import { onDestroy } from 'svelte'; 3 + 4 + import type { AppBskyFeedDefs, AppBskyFeedPost } from '@atcute/client/lexicons'; 5 + import { fetchPost as fetch_post } from 'bluesky-post-embed/core'; 6 + 7 + import { getPostUrl } from 'internal/utils/bsky-url.ts'; 8 + import { formatLongDate } from 'internal/utils/date.ts'; 9 + import { parseAtUri } from 'internal/utils/syntax/at-url.ts'; 10 + 11 + import { escape_html } from '../../lib/html'; 12 + import type { ExtractedPostInfo } from '../../lib/matcher'; 13 + 14 + import Banner from '../Banner.svelte'; 15 + import CircularSpinner from '../CircularSpinner.svelte'; 16 + import CodeBlock from '../CodeBlock.svelte'; 17 + import BlueskyPost from '../embeds/BlueskyPost.svelte'; 18 + import Guide from '../guides/Guide.svelte'; 19 + import GuideInstructions from '../guides/GuideInstructions.svelte'; 20 + 21 + interface Props { 22 + matched: ExtractedPostInfo; 23 + } 24 + 25 + let { matched }: Props = $props(); 26 + 27 + let controller: AbortController | undefined; 28 + const promise = $derived.by(() => { 29 + controller?.abort(); 30 + controller = new AbortController(); 31 + 32 + const signal = controller.signal; 33 + const uri = `at://${matched.author}/app.bsky.feed.post/${matched.rkey}`; 34 + 35 + return fetch_post({ uri, signal }); 36 + }); 37 + 38 + onDestroy(() => { 39 + controller?.abort(); 40 + }); 41 + 42 + const JSDELIVR_URL = `https://cdn.jsdelivr.net/npm/bluesky-post-embed@^1.0.0`; 43 + const get_prerequisite_markup = () => { 44 + return `<!-- Core web component and styling --> 45 + <script type="module" src="${JSDELIVR_URL}/+esm"></${'script'}> 46 + <link rel="stylesheet" href="${JSDELIVR_URL}/dist/core.min.css"> 47 + 48 + <!-- Built-in themes --> 49 + <link rel="stylesheet" href="${JSDELIVR_URL}/themes/light.min.css" media="(prefers-color-scheme: light)"> 50 + <link rel="stylesheet" href="${JSDELIVR_URL}/themes/dim.min.css" media="(prefers-color-scheme: dark)"> 51 + 52 + <!-- Fallback/placeholder elements if JS script is taking a while to load or is failing --> 53 + <style> 54 + .bluesky-post-fallback { 55 + margin: 16px 0; 56 + border-left: 3px solid var(--divider); 57 + padding: 4px 8px; 58 + white-space: pre-wrap; 59 + overflow-wrap: break-word; 60 + } 61 + .bluesky-post-fallback p { 62 + margin: 0 0 8px 0; 63 + } 64 + </${'style'}> 65 + `; 66 + }; 67 + 68 + const get_markup = (post: AppBskyFeedDefs.PostView) => { 69 + const author = post.author; 70 + const record = post.record as AppBskyFeedPost.Record; 71 + 72 + return `<bluesky-post src="${escape_html(post.uri)}"> 73 + <blockquote class="bluesky-post-fallback"> 74 + <p dir="auto">${escape_html(record.text)}</p> 75 + โ€” ${author.displayName?.trim() ? `${escape_html(author.displayName)} (@${escape_html(author.handle)})` : `@${escape_html(author.handle)}`} 76 + <a href="${escape_html(getPostUrl(author.did, parseAtUri(post.uri).rkey))}">${formatLongDate(post.indexedAt)}</a> 77 + </blockquote> 78 + </bluesky-post> 79 + `; 80 + }; 81 + </script> 82 + 83 + {#await promise} 84 + <CircularSpinner /> 85 + {:then data} 86 + <BlueskyPost {data} /> 87 + 88 + {#if data.thread} 89 + <Guide title="How do I embed this to my website?"> 90 + <Banner type="inform"> 91 + Doing server-side rendering? Check out examples for 92 + <a href="https://github.com/mary-ext/bluesky-embed-astro">Astro</a> and 93 + <a href="https://github.com/mary-ext/bluesky-embed-sveltekit">SvelteKit</a>. 94 + </Banner> 95 + 96 + <GuideInstructions> 97 + <li> 98 + <p> 99 + Insert the following scripts and stylesheets to the <code>&lt;head&gt;</code> of your website. 100 + </p> 101 + <CodeBlock code={get_prerequisite_markup()} /> 102 + </li> 103 + 104 + <li> 105 + <p>Insert the following markup in wherever you want the post to be.</p> 106 + <CodeBlock code={get_markup(data.thread.post)} /> 107 + </li> 108 + </GuideInstructions> 109 + </Guide> 110 + {/if} 111 + {:catch err} 112 + <Banner type="alert"> 113 + {'' + err} 114 + </Banner> 115 + {/await}
+93
packages/svelte-site/src/components/display/ProfileCardDisplay.svelte
··· 1 + <script lang="ts"> 2 + import { onDestroy } from 'svelte'; 3 + 4 + import type { AppBskyActorDefs } from '@atcute/client/lexicons'; 5 + import { fetchProfileCard as fetch_profile_card } from 'bluesky-profile-card-embed/core'; 6 + 7 + import { escape_html } from '../../lib/html'; 8 + import type { ExtractedProfileInfo } from '../../lib/matcher'; 9 + 10 + import Banner from '../Banner.svelte'; 11 + import CircularSpinner from '../CircularSpinner.svelte'; 12 + import CodeBlock from '../CodeBlock.svelte'; 13 + import BlueskyProfileCard from '../embeds/BlueskyProfileCard.svelte'; 14 + import Guide from '../guides/Guide.svelte'; 15 + import GuideInstructions from '../guides/GuideInstructions.svelte'; 16 + 17 + interface Props { 18 + matched: ExtractedProfileInfo; 19 + } 20 + 21 + let { matched }: Props = $props(); 22 + 23 + let controller: AbortController | undefined; 24 + const promise = $derived.by(() => { 25 + controller?.abort(); 26 + controller = new AbortController(); 27 + 28 + const signal = controller.signal; 29 + const actor = matched.actor; 30 + 31 + return fetch_profile_card({ actor, signal }); 32 + }); 33 + 34 + onDestroy(() => { 35 + controller?.abort(); 36 + }); 37 + 38 + const get_prerequisite_markup = () => { 39 + const JSDELIVR_URL = `https://cdn.jsdelivr.net/npm/bluesky-profile-card-embed@^1.0.0`; 40 + 41 + return `<!-- Core web component and styling --> 42 + <script type="module" src="${JSDELIVR_URL}/+esm"></${'script'}> 43 + <link rel="stylesheet" href="${JSDELIVR_URL}/dist/core.min.css"> 44 + 45 + <!-- Built-in themes --> 46 + <link rel="stylesheet" href="${JSDELIVR_URL}/themes/light.min.css" media="(prefers-color-scheme: light)"> 47 + <link rel="stylesheet" href="${JSDELIVR_URL}/themes/dim.min.css" media="(prefers-color-scheme: dark)"> 48 + `; 49 + }; 50 + 51 + const get_markup = (profile: AppBskyActorDefs.ProfileViewDetailed) => { 52 + const url = `https://bsky.app/profile/${profile.did}`; 53 + 54 + return `<bluesky-profile-card actor="${escape_html(profile.did)}"> 55 + <a target="_blank" href="${escape_html(url)}" class="bluesky-profile-card-fallback"> 56 + ${ 57 + profile.displayName?.trim() 58 + ? `Follow ${escape_html(profile.displayName)} (@${escape_html(profile.handle)}) on Bluesky` 59 + : `Follow @${escape_html(profile.handle)} on Bluesky` 60 + } 61 + </a> 62 + </bluesky-profile-card> 63 + `; 64 + }; 65 + </script> 66 + 67 + {#await promise} 68 + <CircularSpinner /> 69 + {:then data} 70 + <BlueskyProfileCard {data} /> 71 + 72 + {#if data.profile} 73 + <Guide title="How do I embed this to my website?"> 74 + <GuideInstructions> 75 + <li> 76 + <p> 77 + Insert the following scripts and stylesheets to the <code>&lt;head&gt;</code> of your website. 78 + </p> 79 + <CodeBlock code={get_prerequisite_markup()} /> 80 + </li> 81 + 82 + <li> 83 + <p>Insert the following markup in wherever you want the profile feed to be.</p> 84 + <CodeBlock code={get_markup(data.profile)} /> 85 + </li> 86 + </GuideInstructions> 87 + </Guide> 88 + {/if} 89 + {:catch err} 90 + <Banner type="alert"> 91 + {'' + err} 92 + </Banner> 93 + {/await}
+93
packages/svelte-site/src/components/display/ProfileFeedDisplay.svelte
··· 1 + <script lang="ts"> 2 + import { onDestroy } from 'svelte'; 3 + 4 + import type { AppBskyActorDefs } from '@atcute/client/lexicons'; 5 + import { fetchProfileFeed as fetch_profile_feed } from 'bluesky-profile-feed-embed/core'; 6 + 7 + import { escape_html } from '../../lib/html'; 8 + import type { ExtractedProfileInfo } from '../../lib/matcher'; 9 + 10 + import Banner from '../Banner.svelte'; 11 + import CircularSpinner from '../CircularSpinner.svelte'; 12 + import CodeBlock from '../CodeBlock.svelte'; 13 + import BlueskyProfileFeed from '../embeds/BlueskyProfileFeed.svelte'; 14 + import Guide from '../guides/Guide.svelte'; 15 + import GuideInstructions from '../guides/GuideInstructions.svelte'; 16 + 17 + interface Props { 18 + matched: ExtractedProfileInfo; 19 + } 20 + 21 + let { matched }: Props = $props(); 22 + 23 + let controller: AbortController | undefined; 24 + const promise = $derived.by(() => { 25 + controller?.abort(); 26 + controller = new AbortController(); 27 + 28 + const signal = controller.signal; 29 + const actor = matched.actor; 30 + 31 + return fetch_profile_feed({ actor, signal }); 32 + }); 33 + 34 + onDestroy(() => { 35 + controller?.abort(); 36 + }); 37 + 38 + const get_prerequisite_markup = () => { 39 + const JSDELIVR_URL = `https://cdn.jsdelivr.net/npm/bluesky-profile-feed-embed@^1.0.0`; 40 + 41 + return `<!-- Core web component and styling --> 42 + <script type="module" src="${JSDELIVR_URL}/+esm"></${'script'}> 43 + <link rel="stylesheet" href="${JSDELIVR_URL}/dist/core.min.css"> 44 + 45 + <!-- Built-in themes --> 46 + <link rel="stylesheet" href="${JSDELIVR_URL}/themes/light.min.css" media="(prefers-color-scheme: light)"> 47 + <link rel="stylesheet" href="${JSDELIVR_URL}/themes/dim.min.css" media="(prefers-color-scheme: dark)"> 48 + `; 49 + }; 50 + 51 + const get_markup = (profile: AppBskyActorDefs.ProfileViewDetailed) => { 52 + const url = `https://bsky.app/profile/${profile.did}`; 53 + 54 + return `<bluesky-profile-feed actor="${escape_html(profile.did)}" include-pins> 55 + <a target="_blank" href="${escape_html(url)}" class="bluesky-profile-feed-fallback"> 56 + ${ 57 + profile.displayName?.trim() 58 + ? `Posts by ${escape_html(profile.displayName)} (@${escape_html(profile.handle)})` 59 + : `Posts by @${escape_html(profile.handle)}` 60 + } 61 + </a> 62 + </bluesky-profile-feed> 63 + `; 64 + }; 65 + </script> 66 + 67 + {#await promise} 68 + <CircularSpinner /> 69 + {:then data} 70 + <BlueskyProfileFeed {data} /> 71 + 72 + {#if data.profile} 73 + <Guide title="How do I embed this to my website?"> 74 + <GuideInstructions> 75 + <li> 76 + <p> 77 + Insert the following scripts and stylesheets to the <code>&lt;head&gt;</code> of your website. 78 + </p> 79 + <CodeBlock code={get_prerequisite_markup()} /> 80 + </li> 81 + 82 + <li> 83 + <p>Insert the following markup in wherever you want the profile feed to be.</p> 84 + <CodeBlock code={get_markup(data.profile)} /> 85 + </li> 86 + </GuideInstructions> 87 + </Guide> 88 + {/if} 89 + {:catch err} 90 + <Banner type="alert"> 91 + {'' + err} 92 + </Banner> 93 + {/await}
+12
packages/svelte-site/src/components/embeds/BlueskyPost.svelte
··· 1 + <script lang="ts"> 2 + import { type PostData, renderPost } from 'bluesky-post-embed/core'; 3 + import 'bluesky-post-embed/style.css'; 4 + 5 + interface Props { 6 + data: PostData; 7 + } 8 + 9 + let { data }: Props = $props(); 10 + </script> 11 + 12 + <bluesky-post src={data.thread?.post.uri}>{@html renderPost(data)}</bluesky-post>
+12
packages/svelte-site/src/components/embeds/BlueskyProfileCard.svelte
··· 1 + <script lang="ts"> 2 + import { type ProfileCardData, renderProfileCard } from 'bluesky-profile-card-embed/core'; 3 + import 'bluesky-profile-card-embed/style.css'; 4 + 5 + interface Props { 6 + data: ProfileCardData; 7 + } 8 + 9 + let { data }: Props = $props(); 10 + </script> 11 + 12 + <bluesky-profile-card actor={data.profile?.did}>{@html renderProfileCard(data)}</bluesky-profile-card>
+12
packages/svelte-site/src/components/embeds/BlueskyProfileFeed.svelte
··· 1 + <script lang="ts"> 2 + import { type ProfileFeedData, renderProfileFeed } from 'bluesky-profile-feed-embed/core'; 3 + import 'bluesky-profile-feed-embed/style.css'; 4 + 5 + interface Props { 6 + data: ProfileFeedData; 7 + } 8 + 9 + let { data }: Props = $props(); 10 + </script> 11 + 12 + <bluesky-profile-feed actor={data.profile?.did}>{@html renderProfileFeed(data)}</bluesky-profile-feed>
+26
packages/svelte-site/src/components/guides/Guide.svelte
··· 1 + <script lang="ts"> 2 + import type { Snippet } from 'svelte'; 3 + 4 + interface Props { 5 + title: string; 6 + children: Snippet<[]>; 7 + } 8 + 9 + let { title, children }: Props = $props(); 10 + </script> 11 + 12 + <div class="guide"> 13 + <h4 class="guide-header">{title}</h4> 14 + 15 + {@render children()} 16 + </div> 17 + 18 + <style> 19 + .guide { 20 + margin: 36px 0 0 0; 21 + border-top: 1px solid #d1d5db; 22 + } 23 + .guide-header { 24 + margin: 36px 0 16px 0; 25 + } 26 + </style>
+26
packages/svelte-site/src/components/guides/GuideInstructions.svelte
··· 1 + <script lang="ts"> 2 + import type { Snippet } from 'svelte'; 3 + 4 + interface Props { 5 + children: Snippet<[]>; 6 + } 7 + 8 + let { children }: Props = $props(); 9 + </script> 10 + 11 + <ol class="guide-instructions"> 12 + {@render children()} 13 + </ol> 14 + 15 + <style> 16 + .guide-instructions { 17 + margin: 24px 0 0 0; 18 + padding: 0 0 0 22px; 19 + font-size: 0.875rem; 20 + line-height: 1.25rem; 21 + 22 + :global(li + li) { 23 + margin: 24px 0 0 0; 24 + } 25 + } 26 + </style>
+4
packages/svelte-site/src/lib/component.ts
··· 1 + let uid = 0; 2 + export const use_id = () => { 3 + return `s:${uid++}`; 4 + };
+3
packages/svelte-site/src/lib/html.ts
··· 1 + export const escape_html = (text: string): string => { 2 + return text.replace(/[<"&]/g, (c) => '&#' + c.charCodeAt(0) + ';'); 3 + };
+60
packages/svelte-site/src/lib/matcher.ts
··· 1 + import { is_at_identifier, is_tid } from './strings'; 2 + 3 + export interface ExtractedPostInfo { 4 + type: 'post'; 5 + author: string; 6 + rkey: string; 7 + } 8 + 9 + export interface ExtractedProfileInfo { 10 + type: 'profile'; 11 + actor: string; 12 + } 13 + 14 + export type ExtractedInfo = ExtractedPostInfo | ExtractedProfileInfo; 15 + 16 + export const extract_url = (str: string): ExtractedInfo | null => { 17 + const url = safe_parse_url(str); 18 + if (!url) { 19 + return null; 20 + } 21 + 22 + let match: RegExpExecArray | null | undefined; 23 + if (url.host === 'bsky.app' || url.host === 'staging.bsky.app' || url.host === 'main.bsky.dev') { 24 + if ((match = /^\/profile\/([^/]+)\/post\/([^/]+)\/?$/.exec(url.pathname))) { 25 + if (!is_at_identifier(match[1]) || !is_tid(match[2])) { 26 + return null; 27 + } 28 + 29 + return { type: 'post', author: match[1], rkey: match[2] }; 30 + } 31 + 32 + if ((match = /^\/profile\/([^/]+)\/?$/.exec(url.pathname))) { 33 + if (!is_at_identifier(match[1])) { 34 + return null; 35 + } 36 + 37 + return { type: 'profile', actor: match[1] }; 38 + } 39 + } 40 + 41 + return null; 42 + }; 43 + 44 + const safe_parse_url = (str: string): URL | null => { 45 + let url: URL | null | undefined; 46 + if ('parse' in URL) { 47 + url = URL.parse(str); 48 + } else { 49 + try { 50 + // @ts-expect-error: `'parse' in URL` is giving truthy 51 + url = new URL(str); 52 + } catch {} 53 + } 54 + 55 + if (url && (url.protocol === 'https:' || url.protocol === 'http:')) { 56 + return url; 57 + } 58 + 59 + return null; 60 + };
+28
packages/svelte-site/src/lib/strings.ts
··· 1 + export const RECORD_KEY_RE = /^(?!\.{1,2}$)[a-zA-Z0-9_~.:-]{1,512}$/; 2 + 3 + export const is_record_key = (str: string): boolean => { 4 + return str.length >= 1 && str.length <= 512 && RECORD_KEY_RE.test(str); 5 + }; 6 + 7 + export const TID_RE = /^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/; 8 + 9 + export const is_tid = (str: string): boolean => { 10 + return str.length === 13 && TID_RE.test(str); 11 + }; 12 + 13 + export const DID_RE = /^did:([a-z]+):([a-zA-Z0-9._:%-]*[a-zA-Z0-9._-])$/; 14 + 15 + export const is_did = (str: string): boolean => { 16 + return str.length >= 7 && str.length <= 2048 && DID_RE.test(str); 17 + }; 18 + 19 + export const HANDLE_RE = 20 + /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/; 21 + 22 + export const is_handle = (str: string): boolean => { 23 + return str.length >= 3 && str.length <= 253 && HANDLE_RE.test(str); 24 + }; 25 + 26 + export const is_at_identifier = (str: string): boolean => { 27 + return is_did(str) || is_handle(str); 28 + };
+10
packages/svelte-site/src/main.ts
··· 1 + import { mount } from 'svelte'; 2 + 3 + import './styles/normalize.css'; 4 + import 'bluesky-post-embed/themes/light.css'; 5 + 6 + import App from './App.svelte'; 7 + 8 + mount(App, { 9 + target: document.getElementById('app')!, 10 + });
+199
packages/svelte-site/src/styles/normalize.css
··· 1 + /*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */ 2 + 3 + /* 4 + Document 5 + ======== 6 + */ 7 + 8 + /** 9 + Use a better box model (opinionated). 10 + */ 11 + 12 + *, 13 + ::before, 14 + ::after { 15 + box-sizing: border-box; 16 + } 17 + 18 + html { 19 + line-height: 1.15; /* 1. Correct the line height in all browsers. */ 20 + /* Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) */ 21 + font-family: 'Inter', 'Roboto', ui-sans-serif, sans-serif, 'Noto Color Emoji', 'Twemoji Mozilla'; 22 + -webkit-text-size-adjust: 100%; /* 2. Prevent adjustments of font size after orientation changes in iOS. */ 23 + tab-size: 4; /* 3. Use a more readable tab size (opinionated). */ 24 + } 25 + 26 + /* 27 + Sections 28 + ======== 29 + */ 30 + 31 + body { 32 + margin: 0; /* Remove the margin in all browsers. */ 33 + } 34 + 35 + /* 36 + Text-level semantics 37 + ==================== 38 + */ 39 + 40 + /** 41 + Add the correct font weight in Chrome and Safari. 42 + */ 43 + 44 + b, 45 + strong { 46 + font-weight: bolder; 47 + } 48 + 49 + /** 50 + 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) 51 + 2. Correct the odd 'em' font sizing in all browsers. 52 + */ 53 + 54 + code, 55 + kbd, 56 + samp, 57 + pre { 58 + font-size: 1em; /* 2 */ 59 + font-family: 'JetBrains Mono NL', ui-monospace, monospace; /* 1 */ 60 + } 61 + 62 + /** 63 + Add the correct font size in all browsers. 64 + */ 65 + 66 + small { 67 + font-size: 80%; 68 + } 69 + 70 + /** 71 + Prevent 'sub' and 'sup' elements from affecting the line height in all browsers. 72 + */ 73 + 74 + sub, 75 + sup { 76 + position: relative; 77 + vertical-align: baseline; 78 + font-size: 75%; 79 + line-height: 0; 80 + } 81 + 82 + sub { 83 + bottom: -0.25em; 84 + } 85 + 86 + sup { 87 + top: -0.5em; 88 + } 89 + 90 + /* 91 + Tabular data 92 + ============ 93 + */ 94 + 95 + /** 96 + Correct table border color inheritance in Chrome and Safari. (https://issues.chromium.org/issues/40615503, https://bugs.webkit.org/show_bug.cgi?id=195016) 97 + */ 98 + 99 + table { 100 + border-color: currentcolor; 101 + } 102 + 103 + /* 104 + Forms 105 + ===== 106 + */ 107 + 108 + /** 109 + 1. Change the font styles in all browsers. 110 + 2. Remove the margin in Firefox and Safari. 111 + */ 112 + 113 + button, 114 + input, 115 + optgroup, 116 + select, 117 + textarea { 118 + margin: 0; /* 2 */ 119 + font-size: 100%; /* 1 */ 120 + line-height: 1.15; /* 1 */ 121 + font-family: inherit; /* 1 */ 122 + } 123 + 124 + /** 125 + Correct the inability to style clickable types in iOS and Safari. 126 + */ 127 + 128 + button, 129 + [type='button'], 130 + [type='reset'], 131 + [type='submit'] { 132 + -webkit-appearance: button; 133 + } 134 + 135 + /** 136 + Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers. 137 + */ 138 + 139 + legend { 140 + padding: 0; 141 + } 142 + 143 + /** 144 + Add the correct vertical alignment in Chrome and Firefox. 145 + */ 146 + 147 + progress { 148 + vertical-align: baseline; 149 + } 150 + 151 + /** 152 + Correct the cursor style of increment and decrement buttons in Safari. 153 + */ 154 + 155 + ::-webkit-inner-spin-button, 156 + ::-webkit-outer-spin-button { 157 + height: auto; 158 + } 159 + 160 + /** 161 + 1. Correct the odd appearance in Chrome and Safari. 162 + 2. Correct the outline style in Safari. 163 + */ 164 + 165 + [type='search'] { 166 + -webkit-appearance: textfield; /* 1 */ 167 + outline-offset: -2px; /* 2 */ 168 + } 169 + 170 + /** 171 + Remove the inner padding in Chrome and Safari on macOS. 172 + */ 173 + 174 + ::-webkit-search-decoration { 175 + -webkit-appearance: none; 176 + } 177 + 178 + /** 179 + 1. Correct the inability to style clickable types in iOS and Safari. 180 + 2. Change font properties to 'inherit' in Safari. 181 + */ 182 + 183 + ::-webkit-file-upload-button { 184 + -webkit-appearance: button; /* 1 */ 185 + font: inherit; /* 2 */ 186 + } 187 + 188 + /* 189 + Interactive 190 + =========== 191 + */ 192 + 193 + /* 194 + Add the correct display in Chrome and Safari. 195 + */ 196 + 197 + summary { 198 + display: list-item; 199 + }
+2
packages/svelte-site/src/vite-env.d.ts
··· 1 + /// <reference types="svelte" /> 2 + /// <reference types="vite/client" />
+7
packages/svelte-site/svelte.config.js
··· 1 + import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 2 + 3 + export default { 4 + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 + // for more information about preprocessors 6 + preprocess: vitePreprocess(), 7 + };
+21
packages/svelte-site/tsconfig.json
··· 1 + { 2 + "extends": "@tsconfig/svelte/tsconfig.json", 3 + "compilerOptions": { 4 + "target": "ESNext", 5 + "useDefineForClassFields": true, 6 + "module": "ESNext", 7 + "resolveJsonModule": true, 8 + /** 9 + * Typecheck JS in `.svelte` and `.js` files by default. 10 + * Disable checkJs if you'd like to use dynamic types in JS. 11 + * Note that setting allowJs false does not prevent the use 12 + * of JS in `.svelte` files. 13 + */ 14 + "allowJs": true, 15 + "checkJs": true, 16 + "isolatedModules": true, 17 + "moduleDetection": "force", 18 + }, 19 + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], 20 + "references": [{ "path": "./tsconfig.node.json" }], 21 + }
+13
packages/svelte-site/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 + "skipLibCheck": true, 6 + "module": "ESNext", 7 + "moduleResolution": "bundler", 8 + "strict": true, 9 + "noEmit": true, 10 + "noUncheckedSideEffectImports": true, 11 + }, 12 + "include": ["vite.config.ts"], 13 + }
+15
packages/svelte-site/vite.config.ts
··· 1 + import { defineConfig } from 'vite'; 2 + import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 + 4 + // https://vite.dev/config/ 5 + export default defineConfig({ 6 + base: './', 7 + build: { 8 + target: 'esnext', 9 + minify: 'terser', 10 + }, 11 + esbuild: { 12 + target: 'esnext', 13 + }, 14 + plugins: [svelte()], 15 + });
+1041
patches/svelte.patch
··· 1 + diff --git a/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js b/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js 2 + index b8d2e421440a0f8e5b12c9cb55ed65ab0f5e7a8a..76bb16136e17cb010069eb7fd4d84cb600ad138c 100644 3 + --- a/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js 4 + +++ b/src/compiler/phases/3-transform/server/visitors/AwaitBlock.js 5 + @@ -33,5 +33,5 @@ export function AwaitBlock(node, context) { 6 + ); 7 + } 8 + 9 + - context.state.template.push(statement, block_close); 10 + + context.state.template.push(statement); 11 + } 12 + diff --git a/src/compiler/phases/3-transform/server/visitors/EachBlock.js b/src/compiler/phases/3-transform/server/visitors/EachBlock.js 13 + index 3c0a8c167696960efa186179da60c47eca0db2e2..8e96d467f6f20af90226ddb16db383e4e3be70ed 100644 14 + --- a/src/compiler/phases/3-transform/server/visitors/EachBlock.js 15 + +++ b/src/compiler/phases/3-transform/server/visitors/EachBlock.js 16 + @@ -47,21 +47,16 @@ export function EachBlock(node, context) { 17 + ); 18 + 19 + if (node.fallback) { 20 + - const open = b.stmt(b.call(b.id('$$renderer.push'), block_open)); 21 + - 22 + const fallback = /** @type {BlockStatement} */ (context.visit(node.fallback)); 23 + 24 + - fallback.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), block_open_else))); 25 + - 26 + block.body.push( 27 + b.if( 28 + b.binary('!==', b.member(array_id, 'length'), b.literal(0)), 29 + - b.block([open, for_loop]), 30 + + b.block([for_loop]), 31 + fallback 32 + ) 33 + ); 34 + } else { 35 + - state.template.push(block_open); 36 + block.body.push(for_loop); 37 + } 38 + 39 + @@ -71,10 +66,9 @@ export function EachBlock(node, context) { 40 + block, 41 + node.metadata.expression.blockers(), 42 + node.metadata.expression.has_await 43 + - ), 44 + - block_close 45 + + ) 46 + ); 47 + } else { 48 + - state.template.push(...block.body, block_close); 49 + + state.template.push(...block.body); 50 + } 51 + } 52 + diff --git a/src/compiler/phases/3-transform/server/visitors/Fragment.js b/src/compiler/phases/3-transform/server/visitors/Fragment.js 53 + index ef5bd985ae5d6bcae838a58f68145fd070b73179..bdc556ee8547d4bfccdee0e6c10260a6f9769fba 100644 54 + --- a/src/compiler/phases/3-transform/server/visitors/Fragment.js 55 + +++ b/src/compiler/phases/3-transform/server/visitors/Fragment.js 56 + @@ -36,11 +36,6 @@ export function Fragment(node, context) { 57 + context.visit(node, state); 58 + } 59 + 60 + - if (is_text_first) { 61 + - // insert `<!---->` to prevent this from being glued to the previous fragment 62 + - state.template.push(empty_comment); 63 + - } 64 + - 65 + process_children(trimmed, { ...context, state }); 66 + 67 + if (state.async_consts && state.async_consts.thunks.length > 0) { 68 + diff --git a/src/compiler/phases/3-transform/server/visitors/IfBlock.js b/src/compiler/phases/3-transform/server/visitors/IfBlock.js 69 + index e8418343be9b2fcba39add8762f5139e37cc7e11..3fccb04a180be2e499c2d71e3b6ac250bc7c30b6 100644 70 + --- a/src/compiler/phases/3-transform/server/visitors/IfBlock.js 71 + +++ b/src/compiler/phases/3-transform/server/visitors/IfBlock.js 72 + @@ -16,10 +16,6 @@ export function IfBlock(node, context) { 73 + ? /** @type {BlockStatement} */ (context.visit(node.alternate)) 74 + : b.block([]); 75 + 76 + - consequent.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), block_open))); 77 + - 78 + - alternate.body.unshift(b.stmt(b.call(b.id('$$renderer.push'), block_open_else))); 79 + - 80 + /** @type {Statement} */ 81 + let statement = b.if(test, consequent, alternate); 82 + 83 + @@ -35,5 +31,5 @@ export function IfBlock(node, context) { 84 + ); 85 + } 86 + 87 + - context.state.template.push(statement, block_close); 88 + + context.state.template.push(statement); 89 + } 90 + diff --git a/src/compiler/phases/3-transform/server/visitors/KeyBlock.js b/src/compiler/phases/3-transform/server/visitors/KeyBlock.js 91 + index 1396aa8fada3c5ccbc469a9983af646765bd35e2..02906b5eda4b4a7c2d90243a9d2e28bca606f4d9 100644 92 + --- a/src/compiler/phases/3-transform/server/visitors/KeyBlock.js 93 + +++ b/src/compiler/phases/3-transform/server/visitors/KeyBlock.js 94 + @@ -8,15 +8,7 @@ import { block_close, block_open, empty_comment } from './shared/utils.js'; 95 + * @param {ComponentContext} context 96 + */ 97 + export function KeyBlock(node, context) { 98 + - const is_async = node.metadata.expression.is_async(); 99 + - 100 + - if (is_async) context.state.template.push(block_open); 101 + - 102 + context.state.template.push( 103 + - empty_comment, 104 + /** @type {BlockStatement} */ (context.visit(node.fragment)), 105 + - empty_comment 106 + ); 107 + - 108 + - if (is_async) context.state.template.push(block_close); 109 + } 110 + diff --git a/src/compiler/phases/3-transform/server/visitors/RenderTag.js b/src/compiler/phases/3-transform/server/visitors/RenderTag.js 111 + index 6d7cef0d95a943d6251101289a821537b259162e..9b4576a8e571aed73046e84ebfa47c276eef7439 100644 112 + --- a/src/compiler/phases/3-transform/server/visitors/RenderTag.js 113 + +++ b/src/compiler/phases/3-transform/server/visitors/RenderTag.js 114 + @@ -44,8 +44,4 @@ export function RenderTag(node, context) { 115 + } 116 + 117 + context.state.template.push(statement); 118 + - 119 + - if (!context.state.skip_hydration_boundaries) { 120 + - context.state.template.push(empty_comment); 121 + - } 122 + } 123 + diff --git a/src/compiler/phases/3-transform/server/visitors/SlotElement.js b/src/compiler/phases/3-transform/server/visitors/SlotElement.js 124 + index d0f8e25d021a70589c6fd191139438a1a95bc45c..4455b964948500ab0f8e8468e7f7fd74139f1d1c 100644 125 + --- a/src/compiler/phases/3-transform/server/visitors/SlotElement.js 126 + +++ b/src/compiler/phases/3-transform/server/visitors/SlotElement.js 127 + @@ -73,5 +73,5 @@ export function SlotElement(node, context) { 128 + ) 129 + : b.stmt(slot); 130 + 131 + - context.state.template.push(block_open, statement, block_close); 132 + + context.state.template.push(statement); 133 + } 134 + diff --git a/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js b/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js 135 + index 8a30e765c23008f35ba7a5e43d3c0905f309f555..b5f66aea25087751a864ce515f5f021794ce3d01 100644 136 + --- a/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js 137 + +++ b/src/compiler/phases/3-transform/server/visitors/SvelteBoundary.js 138 + @@ -45,8 +45,8 @@ export function SvelteBoundary(node, context) { 139 + context.state.template.push( 140 + b.if( 141 + callee, 142 + - b.block(build_template([block_open_else, b.stmt(pending), block_close])), 143 + - b.block(build_template([block_open, block, block_close])) 144 + + b.block(build_template([b.stmt(pending)])), 145 + + b.block(build_template([block])) 146 + ) 147 + ); 148 + } else { 149 + @@ -62,10 +62,10 @@ export function SvelteBoundary(node, context) { 150 + b.id('$$renderer') 151 + ) 152 + : /** @type {BlockStatement} */ (context.visit(pending_snippet.body)); 153 + - context.state.template.push(block_open_else, pending, block_close); 154 + + context.state.template.push(pending); 155 + } 156 + } else { 157 + const block = /** @type {BlockStatement} */ (context.visit(node.fragment)); 158 + - context.state.template.push(block_open, block, block_close); 159 + + context.state.template.push(block); 160 + } 161 + } 162 + diff --git a/src/compiler/phases/3-transform/server/visitors/shared/component.js b/src/compiler/phases/3-transform/server/visitors/shared/component.js 163 + index a90b5e41dfb2e746a12c2dbd5f149c9fbcac5d54..846edda2d005f1b1abbfa64029ae90e12f2a2acc 100644 164 + --- a/src/compiler/phases/3-transform/server/visitors/shared/component.js 165 + +++ b/src/compiler/phases/3-transform/server/visitors/shared/component.js 166 + @@ -335,26 +335,13 @@ export function build_inline_component(node, expression, context) { 167 + statement = create_async_block( 168 + b.block([ 169 + optimiser.apply(), 170 + - dynamic && custom_css_props.length === 0 171 + - ? b.stmt(b.call('$$renderer.push', empty_comment)) 172 + - : b.empty, 173 + + b.empty, 174 + statement 175 + ]), 176 + optimiser.blockers(), 177 + optimiser.has_await 178 + ); 179 + - } else if (dynamic && custom_css_props.length === 0) { 180 + - context.state.template.push(empty_comment); 181 + } 182 + 183 + context.state.template.push(statement); 184 + - 185 + - if ( 186 + - !is_async && 187 + - !context.state.skip_hydration_boundaries && 188 + - custom_css_props.length === 0 && 189 + - optimiser.expressions.length === 0 190 + - ) { 191 + - context.state.template.push(empty_comment); 192 + - } 193 + } 194 + diff --git a/src/compiler/phases/3-transform/server/visitors/shared/utils.js b/src/compiler/phases/3-transform/server/visitors/shared/utils.js 195 + index 4736a7c5daf27c6c2dd7c8f0e5136d668f178a95..b2e94a6f257614ed9e2e85f3ff77fa4ed9e3debd 100644 196 + --- a/src/compiler/phases/3-transform/server/visitors/shared/utils.js 197 + +++ b/src/compiler/phases/3-transform/server/visitors/shared/utils.js 198 + @@ -116,6 +116,12 @@ export function build_template(template) { 199 + const statements = []; 200 + 201 + const flush = () => { 202 + + // Skip empty pushes 203 + + if (expressions.length === 0 && strings.every((s) => s === '')) { 204 + + strings = []; 205 + + return; 206 + + } 207 + + 208 + statements.push( 209 + b.stmt( 210 + b.call( 211 + @@ -326,9 +332,11 @@ export function create_push(expression, metadata, needs_hydration_markers = fals 212 + * @returns {Statement} 213 + */ 214 + export function call_component_renderer(body, component_fn_id) { 215 + - return b.stmt( 216 + - b.call('$$renderer.component', b.arrow([b.id('$$renderer')], body, false), component_fn_id) 217 + - ); 218 + + // Just emit the body directly - no component() wrapper needed for sync rendering 219 + + if (body.type === 'BlockStatement') { 220 + + return body; 221 + + } 222 + + return b.stmt(body); 223 + } 224 + 225 + /** 226 + diff --git a/src/internal/server/index.js b/src/internal/server/index.js 227 + index c0dbdbda14f6f6c98d47686e275f91034c1eaa6b..13b18b1625d48820f9960d473a8d0c1d07757f3f 100644 228 + --- a/src/internal/server/index.js 229 + +++ b/src/internal/server/index.js 230 + @@ -17,7 +17,7 @@ import { DEV } from 'esm-env'; 231 + import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN, BLOCK_OPEN_ELSE } from './hydration.js'; 232 + import { validate_store } from '../shared/validate.js'; 233 + import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js'; 234 + -import { Renderer } from './renderer.js'; 235 + +export { render } from './renderer.js'; 236 + 237 + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 238 + // https://infra.spec.whatwg.org/#noncharacter 239 + @@ -51,17 +51,6 @@ export function element(renderer, tag, attributes_fn = noop, children_fn = noop) 240 + renderer.push('<!---->'); 241 + } 242 + 243 + -/** 244 + - * Only available on the server and when compiling with the `server` option. 245 + - * Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app. 246 + - * @template {Record<string, any>} Props 247 + - * @param {Component<Props> | ComponentType<SvelteComponent<Props>>} component 248 + - * @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} [options] 249 + - * @returns {RenderOutput} 250 + - */ 251 + -export function render(component, options = {}) { 252 + - return Renderer.render(/** @type {Component<Props>} */ (component), options); 253 + -} 254 + 255 + /** 256 + * @param {string} hash 257 + @@ -411,12 +400,7 @@ export { await_block as await }; 258 + 259 + /** @param {any} array_like_or_iterator */ 260 + export function ensure_array_like(array_like_or_iterator) { 261 + - if (array_like_or_iterator) { 262 + - return array_like_or_iterator.length !== undefined 263 + - ? array_like_or_iterator 264 + - : Array.from(array_like_or_iterator); 265 + - } 266 + - return []; 267 + + return array_like_or_iterator; 268 + } 269 + 270 + /** 271 + diff --git a/src/internal/server/renderer.js b/src/internal/server/renderer.js 272 + index 0cfb1a7a93518729d04c26214140eaab29de4870..c36c956d1edff831a5d00f214184b882f96e0985 100644 273 + --- a/src/internal/server/renderer.js 274 + +++ b/src/internal/server/renderer.js 275 + @@ -1,741 +1,18 @@ 276 + /** @import { Component } from 'svelte' */ 277 + -/** @import { HydratableContext, RenderOutput, SSRContext, SyncRenderOutput } from './types.js' */ 278 + -/** @import { MaybePromise } from '#shared' */ 279 + -import { async_mode_flag } from '../flags/index.js'; 280 + -import { abort } from './abort-signal.js'; 281 + -import { pop, push, set_ssr_context, ssr_context, save } from './context.js'; 282 + -import * as e from './errors.js'; 283 + -import * as w from './warnings.js'; 284 + -import { BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js'; 285 + -import { attributes } from './index.js'; 286 + -import { get_render_context, with_render_context, init_render_context } from './render-context.js'; 287 + -import { DEV } from 'esm-env'; 288 + - 289 + -/** @typedef {'head' | 'body'} RendererType */ 290 + -/** @typedef {{ [key in RendererType]: string }} AccumulatedContent */ 291 + - 292 + -/** 293 + - * @typedef {string | Renderer} RendererItem 294 + - */ 295 + +/** @import { RenderOutput } from './types.js' */ 296 + 297 + /** 298 + - * Renderers are basically a tree of `string | Renderer`s, where each `Renderer` in the tree represents 299 + - * work that may or may not have completed. A renderer can be {@link collect}ed to aggregate the 300 + - * content from itself and all of its children, but this will throw if any of the children are 301 + - * performing asynchronous work. To asynchronously collect a renderer, just `await` it. 302 + - * 303 + - * The `string` values within a renderer are always associated with the {@link type} of that renderer. To switch types, 304 + - * call {@link child} with a different `type` argument. 305 + + * @template {Record<string, any>} Props 306 + + * @param {Component<Props>} component 307 + + * @param {{ props?: Omit<Props, '$$slots' | '$$events'> }} [options] 308 + + * @returns {RenderOutput} 309 + */ 310 + -export class Renderer { 311 + - /** 312 + - * The contents of the renderer. 313 + - * @type {RendererItem[]} 314 + - */ 315 + - #out = []; 316 + - 317 + - /** 318 + - * Any `onDestroy` callbacks registered during execution of this renderer. 319 + - * @type {(() => void)[] | undefined} 320 + - */ 321 + - #on_destroy = undefined; 322 + - 323 + - /** 324 + - * Whether this renderer is a component body. 325 + - * @type {boolean} 326 + - */ 327 + - #is_component_body = false; 328 + - 329 + - /** 330 + - * The type of string content that this renderer is accumulating. 331 + - * @type {RendererType} 332 + - */ 333 + - type; 334 + - 335 + - /** @type {Renderer | undefined} */ 336 + - #parent; 337 + - 338 + - /** 339 + - * Asynchronous work associated with this renderer 340 + - * @type {Promise<void> | undefined} 341 + - */ 342 + - promise = undefined; 343 + - 344 + - /** 345 + - * State which is associated with the content tree as a whole. 346 + - * It will be re-exposed, uncopied, on all children. 347 + - * @type {SSRState} 348 + - * @readonly 349 + - */ 350 + - global; 351 + - 352 + - /** 353 + - * State that is local to the branch it is declared in. 354 + - * It will be shallow-copied to all children. 355 + - * 356 + - * @type {{ select_value: string | undefined }} 357 + - */ 358 + - local; 359 + - 360 + - /** 361 + - * @param {SSRState} global 362 + - * @param {Renderer | undefined} [parent] 363 + - */ 364 + - constructor(global, parent) { 365 + - this.#parent = parent; 366 + - 367 + - this.global = global; 368 + - this.local = parent ? { ...parent.local } : { select_value: undefined }; 369 + - this.type = parent ? parent.type : 'body'; 370 + - } 371 + - 372 + - /** 373 + - * @param {(renderer: Renderer) => void} fn 374 + - */ 375 + - head(fn) { 376 + - const head = new Renderer(this.global, this); 377 + - head.type = 'head'; 378 + - 379 + - this.#out.push(head); 380 + - head.child(fn); 381 + - } 382 + - 383 + - /** 384 + - * @param {Array<Promise<void>>} blockers 385 + - * @param {(renderer: Renderer) => void} fn 386 + - */ 387 + - async_block(blockers, fn) { 388 + - this.#out.push(BLOCK_OPEN); 389 + - this.async(blockers, fn); 390 + - this.#out.push(BLOCK_CLOSE); 391 + - } 392 + - 393 + - /** 394 + - * @param {Array<Promise<void>>} blockers 395 + - * @param {(renderer: Renderer) => void} fn 396 + - */ 397 + - async(blockers, fn) { 398 + - let callback = fn; 399 + - 400 + - if (blockers.length > 0) { 401 + - const context = ssr_context; 402 + - 403 + - callback = (renderer) => { 404 + - return Promise.all(blockers).then(() => { 405 + - const previous_context = ssr_context; 406 + - 407 + - try { 408 + - set_ssr_context(context); 409 + - return fn(renderer); 410 + - } finally { 411 + - set_ssr_context(previous_context); 412 + - } 413 + - }); 414 + - }; 415 + - } 416 + - 417 + - this.child(callback); 418 + - } 419 + - 420 + - /** 421 + - * @param {Array<() => void>} thunks 422 + - */ 423 + - run(thunks) { 424 + - const context = ssr_context; 425 + - 426 + - let promise = Promise.resolve(thunks[0]()); 427 + - const promises = [promise]; 428 + - 429 + - for (const fn of thunks.slice(1)) { 430 + - promise = promise.then(() => { 431 + - const previous_context = ssr_context; 432 + - set_ssr_context(context); 433 + - 434 + - try { 435 + - return fn(); 436 + - } finally { 437 + - set_ssr_context(previous_context); 438 + - } 439 + - }); 440 + - 441 + - promises.push(promise); 442 + - } 443 + - 444 + - return promises; 445 + - } 446 + - 447 + - /** 448 + - * Create a child renderer. The child renderer inherits the state from the parent, 449 + - * but has its own content. 450 + - * @param {(renderer: Renderer) => MaybePromise<void>} fn 451 + - */ 452 + - child(fn) { 453 + - const child = new Renderer(this.global, this); 454 + - this.#out.push(child); 455 + - 456 + - const parent = ssr_context; 457 + - 458 + - set_ssr_context({ 459 + - ...ssr_context, 460 + - p: parent, 461 + - c: null, 462 + - r: child 463 + - }); 464 + - 465 + - const result = fn(child); 466 + - 467 + - set_ssr_context(parent); 468 + - 469 + - if (result instanceof Promise) { 470 + - if (child.global.mode === 'sync') { 471 + - e.await_invalid(); 472 + - } 473 + - // just to avoid unhandled promise rejections -- we'll end up throwing in `collect_async` if something fails 474 + - result.catch(() => {}); 475 + - child.promise = result; 476 + - } 477 + - 478 + - return child; 479 + - } 480 + - 481 + - /** 482 + - * Create a component renderer. The component renderer inherits the state from the parent, 483 + - * but has its own content. It is treated as an ordering boundary for ondestroy callbacks. 484 + - * @param {(renderer: Renderer) => MaybePromise<void>} fn 485 + - * @param {Function} [component_fn] 486 + - * @returns {void} 487 + - */ 488 + - component(fn, component_fn) { 489 + - push(component_fn); 490 + - const child = this.child(fn); 491 + - child.#is_component_body = true; 492 + - pop(); 493 + - } 494 + - 495 + - /** 496 + - * @param {Record<string, any>} attrs 497 + - * @param {(renderer: Renderer) => void} fn 498 + - * @param {string | undefined} [css_hash] 499 + - * @param {Record<string, boolean> | undefined} [classes] 500 + - * @param {Record<string, string> | undefined} [styles] 501 + - * @param {number | undefined} [flags] 502 + - * @returns {void} 503 + - */ 504 + - select(attrs, fn, css_hash, classes, styles, flags) { 505 + - const { value, ...select_attrs } = attrs; 506 + - 507 + - this.push(`<select${attributes(select_attrs, css_hash, classes, styles, flags)}>`); 508 + - this.child((renderer) => { 509 + - renderer.local.select_value = value; 510 + - fn(renderer); 511 + - }); 512 + - this.push('</select>'); 513 + - } 514 + - 515 + - /** 516 + - * @param {Record<string, any>} attrs 517 + - * @param {string | number | boolean | ((renderer: Renderer) => void)} body 518 + - * @param {string | undefined} [css_hash] 519 + - * @param {Record<string, boolean> | undefined} [classes] 520 + - * @param {Record<string, string> | undefined} [styles] 521 + - * @param {number | undefined} [flags] 522 + - */ 523 + - option(attrs, body, css_hash, classes, styles, flags) { 524 + - this.#out.push(`<option${attributes(attrs, css_hash, classes, styles, flags)}`); 525 + - 526 + - /** 527 + - * @param {Renderer} renderer 528 + - * @param {any} value 529 + - * @param {{ head?: string, body: any }} content 530 + - */ 531 + - const close = (renderer, value, { head, body }) => { 532 + - if ('value' in attrs) { 533 + - value = attrs.value; 534 + - } 535 + - 536 + - if (value === this.local.select_value) { 537 + - renderer.#out.push(' selected'); 538 + - } 539 + - 540 + - renderer.#out.push(`>${body}</option>`); 541 + - 542 + - // super edge case, but may as well handle it 543 + - if (head) { 544 + - renderer.head((child) => child.push(head)); 545 + - } 546 + - }; 547 + - 548 + - if (typeof body === 'function') { 549 + - this.child((renderer) => { 550 + - const r = new Renderer(this.global, this); 551 + - body(r); 552 + - 553 + - if (this.global.mode === 'async') { 554 + - return r.#collect_content_async().then((content) => { 555 + - close(renderer, content.body.replaceAll('<!---->', ''), content); 556 + - }); 557 + - } else { 558 + - const content = r.#collect_content(); 559 + - close(renderer, content.body.replaceAll('<!---->', ''), content); 560 + - } 561 + - }); 562 + - } else { 563 + - close(this, body, { body }); 564 + - } 565 + - } 566 + - 567 + - /** 568 + - * @param {(renderer: Renderer) => void} fn 569 + - */ 570 + - title(fn) { 571 + - const path = this.get_path(); 572 + - 573 + - /** @param {string} head */ 574 + - const close = (head) => { 575 + - this.global.set_title(head, path); 576 + - }; 577 + - 578 + - this.child((renderer) => { 579 + - const r = new Renderer(renderer.global, renderer); 580 + - fn(r); 581 + - 582 + - if (renderer.global.mode === 'async') { 583 + - return r.#collect_content_async().then((content) => { 584 + - close(content.head); 585 + - }); 586 + - } else { 587 + - const content = r.#collect_content(); 588 + - close(content.head); 589 + - } 590 + - }); 591 + - } 592 + - 593 + - /** 594 + - * @param {string | (() => Promise<string>)} content 595 + - */ 596 + - push(content) { 597 + - if (typeof content === 'function') { 598 + - this.child(async (renderer) => renderer.push(await content())); 599 + - } else { 600 + - this.#out.push(content); 601 + - } 602 + - } 603 + - 604 + - /** 605 + - * @param {() => void} fn 606 + - */ 607 + - on_destroy(fn) { 608 + - (this.#on_destroy ??= []).push(fn); 609 + - } 610 + - 611 + - /** 612 + - * @returns {number[]} 613 + - */ 614 + - get_path() { 615 + - return this.#parent ? [...this.#parent.get_path(), this.#parent.#out.indexOf(this)] : []; 616 + - } 617 + - 618 + - /** 619 + - * @deprecated this is needed for legacy component bindings 620 + - */ 621 + - copy() { 622 + - const copy = new Renderer(this.global, this.#parent); 623 + - copy.#out = this.#out.map((item) => (item instanceof Renderer ? item.copy() : item)); 624 + - copy.promise = this.promise; 625 + - return copy; 626 + - } 627 + - 628 + - /** 629 + - * @param {Renderer} other 630 + - * @deprecated this is needed for legacy component bindings 631 + - */ 632 + - subsume(other) { 633 + - if (this.global.mode !== other.global.mode) { 634 + - throw new Error( 635 + - "invariant: A renderer cannot switch modes. If you're seeing this, there's a compiler bug. File an issue!" 636 + - ); 637 + - } 638 + - 639 + - this.local = other.local; 640 + - this.#out = other.#out.map((item) => { 641 + - if (item instanceof Renderer) { 642 + - item.subsume(item); 643 + - } 644 + - return item; 645 + - }); 646 + - this.promise = other.promise; 647 + - this.type = other.type; 648 + - } 649 + - 650 + - get length() { 651 + - return this.#out.length; 652 + - } 653 + - 654 + - /** 655 + - * Only available on the server and when compiling with the `server` option. 656 + - * Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app. 657 + - * @template {Record<string, any>} Props 658 + - * @param {Component<Props>} component 659 + - * @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} [options] 660 + - * @returns {RenderOutput} 661 + - */ 662 + - static render(component, options = {}) { 663 + - /** @type {AccumulatedContent | undefined} */ 664 + - let sync; 665 + - /** @type {Promise<AccumulatedContent> | undefined} */ 666 + - let async; 667 + - 668 + - const result = /** @type {RenderOutput} */ ({}); 669 + - // making these properties non-enumerable so that console.logging 670 + - // doesn't trigger a sync render 671 + - Object.defineProperties(result, { 672 + - html: { 673 + - get: () => { 674 + - return (sync ??= Renderer.#render(component, options)).body; 675 + - } 676 + - }, 677 + - head: { 678 + - get: () => { 679 + - return (sync ??= Renderer.#render(component, options)).head; 680 + - } 681 + - }, 682 + - body: { 683 + - get: () => { 684 + - return (sync ??= Renderer.#render(component, options)).body; 685 + - } 686 + - }, 687 + - then: { 688 + - value: 689 + - /** 690 + - * this is not type-safe, but honestly it's the best I can do right now, and it's a straightforward function. 691 + - * 692 + - * @template TResult1 693 + - * @template [TResult2=never] 694 + - * @param { (value: SyncRenderOutput) => TResult1 } onfulfilled 695 + - * @param { (reason: unknown) => TResult2 } onrejected 696 + - */ 697 + - (onfulfilled, onrejected) => { 698 + - if (!async_mode_flag) { 699 + - const result = (sync ??= Renderer.#render(component, options)); 700 + - const user_result = onfulfilled({ 701 + - head: result.head, 702 + - body: result.body, 703 + - html: result.body 704 + - }); 705 + - return Promise.resolve(user_result); 706 + - } 707 + - async ??= init_render_context().then(() => 708 + - with_render_context(() => Renderer.#render_async(component, options)) 709 + - ); 710 + - return async.then((result) => { 711 + - Object.defineProperty(result, 'html', { 712 + - // eslint-disable-next-line getter-return 713 + - get: () => { 714 + - e.html_deprecated(); 715 + - } 716 + - }); 717 + - return onfulfilled(/** @type {SyncRenderOutput} */ (result)); 718 + - }, onrejected); 719 + - } 720 + - } 721 + - }); 722 + - 723 + - return result; 724 + - } 725 + - 726 + - /** 727 + - * Collect all of the `onDestroy` callbacks registered during rendering. In an async context, this is only safe to call 728 + - * after awaiting `collect_async`. 729 + - * 730 + - * Child renderers are "porous" and don't affect execution order, but component body renderers 731 + - * create ordering boundaries. Within a renderer, callbacks run in order until hitting a component boundary. 732 + - * @returns {Iterable<() => void>} 733 + - */ 734 + - *#collect_on_destroy() { 735 + - for (const component of this.#traverse_components()) { 736 + - yield* component.#collect_ondestroy(); 737 + - } 738 + - } 739 + - 740 + - /** 741 + - * Performs a depth-first search of renderers, yielding the deepest components first, then additional components as we backtrack up the tree. 742 + - * @returns {Iterable<Renderer>} 743 + - */ 744 + - *#traverse_components() { 745 + - for (const child of this.#out) { 746 + - if (typeof child !== 'string') { 747 + - yield* child.#traverse_components(); 748 + - } 749 + - } 750 + - if (this.#is_component_body) { 751 + - yield this; 752 + - } 753 + - } 754 + - 755 + - /** 756 + - * @returns {Iterable<() => void>} 757 + - */ 758 + - *#collect_ondestroy() { 759 + - if (this.#on_destroy) { 760 + - for (const fn of this.#on_destroy) { 761 + - yield fn; 762 + - } 763 + - } 764 + - for (const child of this.#out) { 765 + - if (child instanceof Renderer && !child.#is_component_body) { 766 + - yield* child.#collect_ondestroy(); 767 + - } 768 + - } 769 + - } 770 + - 771 + - /** 772 + - * Render a component. Throws if any of the children are performing asynchronous work. 773 + - * 774 + - * @template {Record<string, any>} Props 775 + - * @param {Component<Props>} component 776 + - * @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} options 777 + - * @returns {AccumulatedContent} 778 + - */ 779 + - static #render(component, options) { 780 + - var previous_context = ssr_context; 781 + - try { 782 + - const renderer = Renderer.#open_render('sync', component, options); 783 + - 784 + - const content = renderer.#collect_content(); 785 + - return Renderer.#close_render(content, renderer); 786 + - } finally { 787 + - abort(); 788 + - set_ssr_context(previous_context); 789 + - } 790 + - } 791 + - 792 + - /** 793 + - * Render a component. 794 + - * 795 + - * @template {Record<string, any>} Props 796 + - * @param {Component<Props>} component 797 + - * @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} options 798 + - * @returns {Promise<AccumulatedContent>} 799 + - */ 800 + - static async #render_async(component, options) { 801 + - const previous_context = ssr_context; 802 + - 803 + - try { 804 + - const renderer = Renderer.#open_render('async', component, options); 805 + - const content = await renderer.#collect_content_async(); 806 + - const hydratables = await renderer.#collect_hydratables(); 807 + - if (hydratables !== null) { 808 + - content.head = hydratables + content.head; 809 + - } 810 + - return Renderer.#close_render(content, renderer); 811 + - } finally { 812 + - set_ssr_context(previous_context); 813 + - abort(); 814 + - } 815 + - } 816 + - 817 + - /** 818 + - * Collect all of the code from the `out` array and return it as a string, or a promise resolving to a string. 819 + - * @param {AccumulatedContent} content 820 + - * @returns {AccumulatedContent} 821 + - */ 822 + - #collect_content(content = { head: '', body: '' }) { 823 + - for (const item of this.#out) { 824 + - if (typeof item === 'string') { 825 + - content[this.type] += item; 826 + - } else if (item instanceof Renderer) { 827 + - item.#collect_content(content); 828 + - } 829 + - } 830 + - 831 + - return content; 832 + - } 833 + - 834 + - /** 835 + - * Collect all of the code from the `out` array and return it as a string. 836 + - * @param {AccumulatedContent} content 837 + - * @returns {Promise<AccumulatedContent>} 838 + - */ 839 + - async #collect_content_async(content = { head: '', body: '' }) { 840 + - await this.promise; 841 + - 842 + - // no danger to sequentially awaiting stuff in here; all of the work is already kicked off 843 + - for (const item of this.#out) { 844 + - if (typeof item === 'string') { 845 + - content[this.type] += item; 846 + - } else if (item instanceof Renderer) { 847 + - await item.#collect_content_async(content); 848 + - } 849 + - } 850 + - 851 + - return content; 852 + - } 853 + - 854 + - async #collect_hydratables() { 855 + - const ctx = get_render_context().hydratable; 856 + - 857 + - for (const [_, key] of ctx.unresolved_promises) { 858 + - // this is a problem -- it means we've finished the render but we're still waiting on a promise to resolve so we can 859 + - // serialize it, so we're blocking the response on useless content. 860 + - w.unresolved_hydratable(key, ctx.lookup.get(key)?.stack ?? '<missing stack trace>'); 861 + - } 862 + - 863 + - for (const comparison of ctx.comparisons) { 864 + - // these reject if there's a mismatch 865 + - await comparison; 866 + - } 867 + - 868 + - return await Renderer.#hydratable_block(ctx); 869 + - } 870 + - 871 + - /** 872 + - * @template {Record<string, any>} Props 873 + - * @param {'sync' | 'async'} mode 874 + - * @param {import('svelte').Component<Props>} component 875 + - * @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any>; idPrefix?: string }} options 876 + - * @returns {Renderer} 877 + - */ 878 + - static #open_render(mode, component, options) { 879 + - const renderer = new Renderer( 880 + - new SSRState(mode, options.idPrefix ? options.idPrefix + '-' : '') 881 + - ); 882 + - 883 + - renderer.push(BLOCK_OPEN); 884 + - 885 + - if (options.context) { 886 + - push(); 887 + - /** @type {SSRContext} */ (ssr_context).c = options.context; 888 + - /** @type {SSRContext} */ (ssr_context).r = renderer; 889 + - } 890 + - 891 + - // @ts-expect-error 892 + - component(renderer, options.props ?? {}); 893 + - 894 + - if (options.context) { 895 + - pop(); 896 + - } 897 + - 898 + - renderer.push(BLOCK_CLOSE); 899 + - 900 + - return renderer; 901 + - } 902 + - 903 + - /** 904 + - * @param {AccumulatedContent} content 905 + - * @param {Renderer} renderer 906 + - */ 907 + - static #close_render(content, renderer) { 908 + - for (const cleanup of renderer.#collect_on_destroy()) { 909 + - cleanup(); 910 + - } 911 + - 912 + - let head = content.head + renderer.global.get_title(); 913 + - let body = content.body; 914 + - 915 + - for (const { hash, code } of renderer.global.css) { 916 + - head += `<style id="${hash}">${code}</style>`; 917 + - } 918 + - 919 + - return { 920 + - head, 921 + - body 922 + - }; 923 + - } 924 + - 925 + - /** 926 + - * @param {HydratableContext} ctx 927 + - */ 928 + - static async #hydratable_block(ctx) { 929 + - if (ctx.lookup.size === 0) { 930 + - return null; 931 + - } 932 + - 933 + - let entries = []; 934 + - let has_promises = false; 935 + - 936 + - for (const [k, v] of ctx.lookup) { 937 + - if (v.promises) { 938 + - has_promises = true; 939 + - for (const p of v.promises) await p; 940 + - } 941 + - 942 + - entries.push(`[${JSON.stringify(k)},${v.serialized}]`); 943 + - } 944 + - 945 + - let prelude = `const h = (window.__svelte ??= {}).h ??= new Map();`; 946 + - 947 + - if (has_promises) { 948 + - prelude = `const r = (v) => Promise.resolve(v); 949 + - ${prelude}`; 950 + - } 951 + - 952 + - // TODO csp -- have discussed but not implemented 953 + - return ` 954 + - <script> 955 + - { 956 + - ${prelude} 957 + - 958 + - for (const [k, v] of [ 959 + - ${entries.join(',\n\t\t\t\t\t')} 960 + - ]) { 961 + - h.set(k, v); 962 + - } 963 + - } 964 + - </script>`; 965 + - } 966 + +function render(component, options = {}) { 967 + + let out = ''; 968 + + const renderer = { push(s) { out += s; } }; 969 + + component(renderer, options.props ?? {}); 970 + + return { body: out }; 971 + } 972 + 973 + -export class SSRState { 974 + - /** @readonly @type {'sync' | 'async'} */ 975 + - mode; 976 + - 977 + - /** @readonly @type {() => string} */ 978 + - uid; 979 + - 980 + - /** @readonly @type {Set<{ hash: string; code: string }>} */ 981 + - css = new Set(); 982 + - 983 + - /** @type {{ path: number[], value: string }} */ 984 + - #title = { path: [], value: '' }; 985 + - 986 + - /** 987 + - * @param {'sync' | 'async'} mode 988 + - * @param {string} [id_prefix] 989 + - */ 990 + - constructor(mode, id_prefix = '') { 991 + - this.mode = mode; 992 + - 993 + - let uid = 1; 994 + - this.uid = () => `${id_prefix}s${uid++}`; 995 + - } 996 + - 997 + - get_title() { 998 + - return this.#title.value; 999 + - } 1000 + - 1001 + - /** 1002 + - * Performs a depth-first (lexicographic) comparison using the path. Rejects sets 1003 + - * from earlier than or equal to the current value. 1004 + - * @param {string} value 1005 + - * @param {number[]} path 1006 + - */ 1007 + - set_title(value, path) { 1008 + - const current = this.#title.path; 1009 + - 1010 + - let i = 0; 1011 + - let l = Math.min(path.length, current.length); 1012 + - 1013 + - // skip identical prefixes - [1, 2, 3, ...] === [1, 2, 3, ...] 1014 + - while (i < l && path[i] === current[i]) i += 1; 1015 + - 1016 + - if (path[i] === undefined) return; 1017 + - 1018 + - // replace title if 1019 + - // - incoming path is longer - [7, 8, 9] > [7, 8] 1020 + - // - incoming path is later - [7, 8, 9] > [7, 8, 8] 1021 + - if (current[i] === undefined || path[i] > current[i]) { 1022 + - this.#title.path = path; 1023 + - this.#title.value = value; 1024 + - } 1025 + - } 1026 + -} 1027 + +export { render }; 1028 + +export const Renderer = { render }; 1029 + diff --git a/src/internal/shared/attributes.js b/src/internal/shared/attributes.js 1030 + index 4ad550e8d612e42d1e719dcbfcbce383bbbbb27f..a12fe17a0efd135bd9fd4f1b1594c059a6d12e54 100644 1031 + --- a/src/internal/shared/attributes.js 1032 + +++ b/src/internal/shared/attributes.js 1033 + @@ -27,7 +27,7 @@ export function attr(name, value, is_boolean = false) { 1034 + is_boolean = true; 1035 + } 1036 + if (value == null || (!value && is_boolean)) return ''; 1037 + - const normalized = (name in replacements && replacements[name].get(value)) || value; 1038 + + const normalized = value; 1039 + const assignment = is_boolean ? '' : `="${escape_html(normalized, true)}"`; 1040 + return ` ${name}${assignment}`; 1041 + }
+2712 -1030
pnpm-lock.yaml
··· 1 - lockfileVersion: '6.0' 1 + lockfileVersion: '9.0' 2 2 3 3 settings: 4 - autoInstallPeers: false 4 + autoInstallPeers: true 5 5 excludeLinksFromLockfile: false 6 6 7 - dependencies: 8 - '@externdefs/bluesky-client': 9 - specifier: ^0.5.8 10 - version: 0.5.8 7 + catalogs: 8 + default: 9 + svelte: 10 + specifier: ^5.45.5 11 + version: 5.45.5 11 12 12 - devDependencies: 13 - '@babel/core': 14 - specifier: ^7.24.4 15 - version: 7.24.4 16 - '@babel/plugin-syntax-typescript': 17 - specifier: ^7.24.1 18 - version: 7.24.1(@babel/core@7.24.4) 19 - '@intrnl/jsx-to-string': 20 - specifier: ^0.1.6 21 - version: 0.1.6(@babel/core@7.24.4) 22 - '@rollup/plugin-babel': 23 - specifier: ^6.0.4 24 - version: 6.0.4(@babel/core@7.24.4) 25 - prettier: 26 - specifier: ^3.2.5 27 - version: 3.2.5 28 - prettier-plugin-css-order: 29 - specifier: ^2.1.2 30 - version: 2.1.2(prettier@3.2.5) 31 - typescript: 32 - specifier: ~5.4.5 33 - version: 5.4.5 34 - vite: 35 - specifier: ^5.2.8 36 - version: 5.2.8 37 - vite-plugin-dts: 38 - specifier: ^3.8.1 39 - version: 3.8.1(typescript@5.4.5)(vite@5.2.8) 13 + patchedDependencies: 14 + svelte: 15 + hash: 0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd 16 + path: patches/svelte.patch 40 17 41 - packages: 18 + importers: 42 19 43 - /@ampproject/remapping@2.3.0: 44 - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 45 - engines: {node: '>=6.0.0'} 46 - dependencies: 47 - '@jridgewell/gen-mapping': 0.3.5 48 - '@jridgewell/trace-mapping': 0.3.25 49 - dev: true 20 + .: 21 + devDependencies: 22 + '@changesets/cli': 23 + specifier: ^2.29.8 24 + version: 2.29.8(@types/node@24.10.1) 25 + prettier: 26 + specifier: ^3.7.4 27 + version: 3.7.4 28 + prettier-plugin-css-order: 29 + specifier: ^2.1.2 30 + version: 2.1.2(postcss@8.5.6)(prettier@3.7.4) 31 + prettier-plugin-svelte: 32 + specifier: ^3.4.0 33 + version: 3.4.0(prettier@3.7.4)(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd)) 34 + typescript: 35 + specifier: ~5.8.3 36 + version: 5.8.3 50 37 51 - /@babel/code-frame@7.24.2: 52 - resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} 53 - engines: {node: '>=6.9.0'} 38 + packages/bluesky-post-embed: 54 39 dependencies: 55 - '@babel/highlight': 7.24.2 56 - picocolors: 1.0.0 57 - dev: true 40 + '@atcute/bluesky': 41 + specifier: ^2.1.1 42 + version: 2.1.1(@atcute/client@3.1.0) 43 + '@atcute/bluesky-richtext-segmenter': 44 + specifier: ^2.0.4 45 + version: 2.0.4 46 + '@atcute/client': 47 + specifier: ^3.1.0 48 + version: 3.1.0 49 + devDependencies: 50 + '@preact/preset-vite': 51 + specifier: ^2.10.2 52 + version: 2.10.2(@babel/core@7.28.5)(preact@10.28.0)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 53 + '@tsconfig/svelte': 54 + specifier: ^5.0.6 55 + version: 5.0.6 56 + '@types/node': 57 + specifier: ^24.10.1 58 + version: 24.10.1 59 + internal: 60 + specifier: workspace:^ 61 + version: link:../internal 62 + preact: 63 + specifier: ^10.28.0 64 + version: 10.28.0 65 + svelte: 66 + specifier: 'catalog:' 67 + version: 5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd) 68 + svelte-check: 69 + specifier: ^4.3.4 70 + version: 4.3.4(picomatch@4.0.3)(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(typescript@5.8.3) 71 + vite: 72 + specifier: ^7.2.6 73 + version: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 74 + vite-plugin-dts: 75 + specifier: ^4.5.4 76 + version: 4.5.4(@types/node@24.10.1)(rollup@4.53.3)(typescript@5.8.3)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 58 77 59 - /@babel/compat-data@7.24.4: 60 - resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} 61 - engines: {node: '>=6.9.0'} 62 - dev: true 78 + packages/bluesky-profile-card-embed: 79 + dependencies: 80 + '@atcute/bluesky': 81 + specifier: ^2.1.1 82 + version: 2.1.1(@atcute/client@3.1.0) 83 + '@atcute/bluesky-richtext-parser': 84 + specifier: ^1.0.7 85 + version: 1.0.7 86 + '@atcute/client': 87 + specifier: ^3.1.0 88 + version: 3.1.0 89 + devDependencies: 90 + '@preact/preset-vite': 91 + specifier: ^2.10.2 92 + version: 2.10.2(@babel/core@7.28.5)(preact@10.28.0)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 93 + '@tsconfig/svelte': 94 + specifier: ^5.0.6 95 + version: 5.0.6 96 + '@types/node': 97 + specifier: ^24.10.1 98 + version: 24.10.1 99 + internal: 100 + specifier: workspace:^ 101 + version: link:../internal 102 + preact: 103 + specifier: ^10.28.0 104 + version: 10.28.0 105 + svelte: 106 + specifier: 'catalog:' 107 + version: 5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd) 108 + svelte-check: 109 + specifier: ^4.3.4 110 + version: 4.3.4(picomatch@4.0.3)(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(typescript@5.8.3) 111 + vite: 112 + specifier: ^7.2.6 113 + version: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 114 + vite-plugin-dts: 115 + specifier: ^4.5.4 116 + version: 4.5.4(@types/node@24.10.1)(rollup@4.53.3)(typescript@5.8.3)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 63 117 64 - /@babel/core@7.24.4: 65 - resolution: {integrity: sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==} 66 - engines: {node: '>=6.9.0'} 118 + packages/bluesky-profile-feed-embed: 67 119 dependencies: 68 - '@ampproject/remapping': 2.3.0 69 - '@babel/code-frame': 7.24.2 70 - '@babel/generator': 7.24.4 71 - '@babel/helper-compilation-targets': 7.23.6 72 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) 73 - '@babel/helpers': 7.24.4 74 - '@babel/parser': 7.24.4 75 - '@babel/template': 7.24.0 76 - '@babel/traverse': 7.24.1 77 - '@babel/types': 7.24.0 78 - convert-source-map: 2.0.0 79 - debug: 4.3.4 80 - gensync: 1.0.0-beta.2 81 - json5: 2.2.3 82 - semver: 6.3.1 83 - transitivePeerDependencies: 84 - - supports-color 85 - dev: true 120 + '@atcute/bluesky': 121 + specifier: ^2.1.1 122 + version: 2.1.1(@atcute/client@3.1.0) 123 + '@atcute/bluesky-richtext-segmenter': 124 + specifier: ^2.0.4 125 + version: 2.0.4 126 + '@atcute/client': 127 + specifier: ^3.1.0 128 + version: 3.1.0 129 + devDependencies: 130 + '@tsconfig/svelte': 131 + specifier: ^5.0.6 132 + version: 5.0.6 133 + '@types/node': 134 + specifier: ^24.10.1 135 + version: 24.10.1 136 + internal: 137 + specifier: workspace:^ 138 + version: link:../internal 139 + svelte: 140 + specifier: 'catalog:' 141 + version: 5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd) 142 + svelte-check: 143 + specifier: ^4.3.4 144 + version: 4.3.4(picomatch@4.0.3)(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(typescript@5.8.3) 145 + vite: 146 + specifier: ^7.2.6 147 + version: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 148 + vite-plugin-dts: 149 + specifier: ^4.5.4 150 + version: 4.5.4(@types/node@24.10.1)(rollup@4.53.3)(typescript@5.8.3)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 86 151 87 - /@babel/generator@7.24.4: 88 - resolution: {integrity: sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==} 89 - engines: {node: '>=6.9.0'} 152 + packages/internal: 153 + devDependencies: 154 + '@atcute/bluesky': 155 + specifier: ^2.1.1 156 + version: 2.1.1(@atcute/client@3.1.0) 157 + '@atcute/bluesky-richtext-parser': 158 + specifier: ^1.0.7 159 + version: 1.0.7 160 + '@atcute/bluesky-richtext-segmenter': 161 + specifier: ^2.0.4 162 + version: 2.0.4 163 + '@atcute/client': 164 + specifier: ^3.1.0 165 + version: 3.1.0 166 + '@tsconfig/svelte': 167 + specifier: ^5.0.6 168 + version: 5.0.6 169 + svelte: 170 + specifier: 'catalog:' 171 + version: 5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd) 172 + 173 + packages/svelte-site: 90 174 dependencies: 91 - '@babel/types': 7.24.0 92 - '@jridgewell/gen-mapping': 0.3.5 93 - '@jridgewell/trace-mapping': 0.3.25 94 - jsesc: 2.5.2 95 - dev: true 175 + '@atcute/bluesky': 176 + specifier: ^2.1.1 177 + version: 2.1.1(@atcute/client@3.1.0) 178 + '@atcute/client': 179 + specifier: ^3.1.0 180 + version: 3.1.0 181 + bluesky-post-embed: 182 + specifier: workspace:^ 183 + version: link:../bluesky-post-embed 184 + bluesky-profile-card-embed: 185 + specifier: workspace:^ 186 + version: link:../bluesky-profile-card-embed 187 + bluesky-profile-feed-embed: 188 + specifier: workspace:^ 189 + version: link:../bluesky-profile-feed-embed 190 + internal: 191 + specifier: workspace:^ 192 + version: link:../internal 193 + devDependencies: 194 + '@sveltejs/vite-plugin-svelte': 195 + specifier: ^6.2.1 196 + version: 6.2.1(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 197 + '@tsconfig/svelte': 198 + specifier: ^5.0.6 199 + version: 5.0.6 200 + svelte: 201 + specifier: ^5.45.5 202 + version: 5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd) 203 + svelte-check: 204 + specifier: ^4.3.4 205 + version: 4.3.4(picomatch@4.0.3)(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(typescript@5.8.3) 206 + terser: 207 + specifier: ^5.44.1 208 + version: 5.44.1 209 + tslib: 210 + specifier: ^2.8.1 211 + version: 2.8.1 212 + vite: 213 + specifier: ^7.2.6 214 + version: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 215 + 216 + packages: 217 + 218 + '@atcute/atproto@3.1.9': 219 + resolution: {integrity: sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w==} 220 + 221 + '@atcute/bluesky-richtext-parser@1.0.7': 222 + resolution: {integrity: sha512-nOvU699OXiGMbyswao7JJnY0C9WkwE7PVC/m5WWt0UN9fsXSOor9IZWw+v9SATp+94BTJoG38XyUomUaJnoQRA==} 223 + 224 + '@atcute/bluesky-richtext-segmenter@2.0.4': 225 + resolution: {integrity: sha512-6m5QEAv4lU3qTy5MeJXJRRG33acipYJnMW1T7W/KrMyThGhQ7jSTTh8Z48quElgivgX7MDj6o/ow1oLUsjsCKw==} 226 + 227 + '@atcute/bluesky@2.1.1': 228 + resolution: {integrity: sha512-wEZfFW58J6yC1SqHcVJOn4qbHENTTzjeCEWthRT5HvKovADLqk54HSMSAuXDMBUbintSTBr0khQNZQ3ZdgzDdQ==} 229 + peerDependencies: 230 + '@atcute/client': ^3.0.0 231 + 232 + '@atcute/bluesky@3.2.11': 233 + resolution: {integrity: sha512-AboS6y4t+zaxIq7E4noue10csSpIuk/Uwo30/l6GgGBDPXrd7STw8Yb5nGZQP+TdG/uC8/c2mm7UnY65SDOh6A==} 234 + 235 + '@atcute/client@3.1.0': 236 + resolution: {integrity: sha512-+rQPsHXSf0DUm8XoHoaH7Y2E8tIpbsW84djyPj7dqAyrFIjvGuJ1X1DvMufwbTIcmLerdy+dzl34iZcz/h3Vhg==} 237 + 238 + '@atcute/lexicons@1.2.5': 239 + resolution: {integrity: sha512-9yO9WdgxW8jZ7SbzUycH710z+JmsQ9W9n5S6i6eghYju32kkluFmgBeS47r8e8p2+Dv4DemS7o/3SUGsX9FR5Q==} 96 240 97 - /@babel/helper-compilation-targets@7.23.6: 98 - resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} 241 + '@babel/code-frame@7.27.1': 242 + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} 99 243 engines: {node: '>=6.9.0'} 100 - dependencies: 101 - '@babel/compat-data': 7.24.4 102 - '@babel/helper-validator-option': 7.23.5 103 - browserslist: 4.23.0 104 - lru-cache: 5.1.1 105 - semver: 6.3.1 106 - dev: true 107 244 108 - /@babel/helper-environment-visitor@7.22.20: 109 - resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} 245 + '@babel/compat-data@7.28.5': 246 + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} 110 247 engines: {node: '>=6.9.0'} 111 - dev: true 112 248 113 - /@babel/helper-function-name@7.23.0: 114 - resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} 249 + '@babel/core@7.28.5': 250 + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} 115 251 engines: {node: '>=6.9.0'} 116 - dependencies: 117 - '@babel/template': 7.24.0 118 - '@babel/types': 7.24.0 119 - dev: true 120 252 121 - /@babel/helper-hoist-variables@7.22.5: 122 - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} 253 + '@babel/generator@7.28.5': 254 + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} 123 255 engines: {node: '>=6.9.0'} 124 - dependencies: 125 - '@babel/types': 7.24.0 126 - dev: true 127 256 128 - /@babel/helper-module-imports@7.24.3: 129 - resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} 257 + '@babel/helper-annotate-as-pure@7.27.3': 258 + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} 130 259 engines: {node: '>=6.9.0'} 131 - dependencies: 132 - '@babel/types': 7.24.0 133 - dev: true 134 260 135 - /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.4): 136 - resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} 261 + '@babel/helper-compilation-targets@7.27.2': 262 + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} 137 263 engines: {node: '>=6.9.0'} 138 - peerDependencies: 139 - '@babel/core': ^7.0.0 140 - dependencies: 141 - '@babel/core': 7.24.4 142 - '@babel/helper-environment-visitor': 7.22.20 143 - '@babel/helper-module-imports': 7.24.3 144 - '@babel/helper-simple-access': 7.22.5 145 - '@babel/helper-split-export-declaration': 7.22.6 146 - '@babel/helper-validator-identifier': 7.22.20 147 - dev: true 148 264 149 - /@babel/helper-plugin-utils@7.24.0: 150 - resolution: {integrity: sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==} 265 + '@babel/helper-globals@7.28.0': 266 + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} 151 267 engines: {node: '>=6.9.0'} 152 - dev: true 153 268 154 - /@babel/helper-simple-access@7.22.5: 155 - resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} 269 + '@babel/helper-module-imports@7.27.1': 270 + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} 156 271 engines: {node: '>=6.9.0'} 157 - dependencies: 158 - '@babel/types': 7.24.0 159 - dev: true 160 272 161 - /@babel/helper-split-export-declaration@7.22.6: 162 - resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} 273 + '@babel/helper-module-transforms@7.28.3': 274 + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} 163 275 engines: {node: '>=6.9.0'} 164 - dependencies: 165 - '@babel/types': 7.24.0 166 - dev: true 276 + peerDependencies: 277 + '@babel/core': ^7.0.0 167 278 168 - /@babel/helper-string-parser@7.24.1: 169 - resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} 279 + '@babel/helper-plugin-utils@7.27.1': 280 + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} 170 281 engines: {node: '>=6.9.0'} 171 - dev: true 172 282 173 - /@babel/helper-validator-identifier@7.22.20: 174 - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} 283 + '@babel/helper-string-parser@7.27.1': 284 + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} 175 285 engines: {node: '>=6.9.0'} 176 - dev: true 177 286 178 - /@babel/helper-validator-option@7.23.5: 179 - resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} 287 + '@babel/helper-validator-identifier@7.28.5': 288 + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} 180 289 engines: {node: '>=6.9.0'} 181 - dev: true 182 290 183 - /@babel/helpers@7.24.4: 184 - resolution: {integrity: sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==} 291 + '@babel/helper-validator-option@7.27.1': 292 + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} 185 293 engines: {node: '>=6.9.0'} 186 - dependencies: 187 - '@babel/template': 7.24.0 188 - '@babel/traverse': 7.24.1 189 - '@babel/types': 7.24.0 190 - transitivePeerDependencies: 191 - - supports-color 192 - dev: true 193 294 194 - /@babel/highlight@7.24.2: 195 - resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} 295 + '@babel/helpers@7.28.4': 296 + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} 196 297 engines: {node: '>=6.9.0'} 197 - dependencies: 198 - '@babel/helper-validator-identifier': 7.22.20 199 - chalk: 2.4.2 200 - js-tokens: 4.0.0 201 - picocolors: 1.0.0 202 - dev: true 203 298 204 - /@babel/parser@7.24.4: 205 - resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} 299 + '@babel/parser@7.28.5': 300 + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} 206 301 engines: {node: '>=6.0.0'} 207 302 hasBin: true 208 - dependencies: 209 - '@babel/types': 7.24.0 210 - dev: true 211 303 212 - /@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.4): 213 - resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} 304 + '@babel/plugin-syntax-jsx@7.27.1': 305 + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} 214 306 engines: {node: '>=6.9.0'} 215 307 peerDependencies: 216 308 '@babel/core': ^7.0.0-0 217 - dependencies: 218 - '@babel/core': 7.24.4 219 - '@babel/helper-plugin-utils': 7.24.0 220 - dev: true 221 309 222 - /@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.4): 223 - resolution: {integrity: sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==} 310 + '@babel/plugin-transform-react-jsx-development@7.27.1': 311 + resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} 224 312 engines: {node: '>=6.9.0'} 225 313 peerDependencies: 226 314 '@babel/core': ^7.0.0-0 227 - dependencies: 228 - '@babel/core': 7.24.4 229 - '@babel/helper-plugin-utils': 7.24.0 230 - dev: true 231 315 232 - /@babel/template@7.24.0: 233 - resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} 316 + '@babel/plugin-transform-react-jsx@7.27.1': 317 + resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==} 234 318 engines: {node: '>=6.9.0'} 235 - dependencies: 236 - '@babel/code-frame': 7.24.2 237 - '@babel/parser': 7.24.4 238 - '@babel/types': 7.24.0 239 - dev: true 319 + peerDependencies: 320 + '@babel/core': ^7.0.0-0 240 321 241 - /@babel/traverse@7.24.1: 242 - resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==} 322 + '@babel/runtime@7.28.4': 323 + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} 243 324 engines: {node: '>=6.9.0'} 244 - dependencies: 245 - '@babel/code-frame': 7.24.2 246 - '@babel/generator': 7.24.4 247 - '@babel/helper-environment-visitor': 7.22.20 248 - '@babel/helper-function-name': 7.23.0 249 - '@babel/helper-hoist-variables': 7.22.5 250 - '@babel/helper-split-export-declaration': 7.22.6 251 - '@babel/parser': 7.24.4 252 - '@babel/types': 7.24.0 253 - debug: 4.3.4 254 - globals: 11.12.0 255 - transitivePeerDependencies: 256 - - supports-color 257 - dev: true 258 325 259 - /@babel/types@7.24.0: 260 - resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} 326 + '@babel/template@7.27.2': 327 + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} 328 + engines: {node: '>=6.9.0'} 329 + 330 + '@babel/traverse@7.28.5': 331 + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} 332 + engines: {node: '>=6.9.0'} 333 + 334 + '@babel/types@7.28.5': 335 + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} 261 336 engines: {node: '>=6.9.0'} 262 - dependencies: 263 - '@babel/helper-string-parser': 7.24.1 264 - '@babel/helper-validator-identifier': 7.22.20 265 - to-fast-properties: 2.0.0 266 - dev: true 337 + 338 + '@changesets/apply-release-plan@7.0.14': 339 + resolution: {integrity: sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==} 340 + 341 + '@changesets/assemble-release-plan@6.0.9': 342 + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} 343 + 344 + '@changesets/changelog-git@0.2.1': 345 + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} 346 + 347 + '@changesets/cli@2.29.8': 348 + resolution: {integrity: sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==} 349 + hasBin: true 350 + 351 + '@changesets/config@3.1.2': 352 + resolution: {integrity: sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog==} 353 + 354 + '@changesets/errors@0.2.0': 355 + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} 356 + 357 + '@changesets/get-dependents-graph@2.1.3': 358 + resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} 359 + 360 + '@changesets/get-release-plan@4.0.14': 361 + resolution: {integrity: sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g==} 362 + 363 + '@changesets/get-version-range-type@0.4.0': 364 + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} 365 + 366 + '@changesets/git@3.0.4': 367 + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} 368 + 369 + '@changesets/logger@0.1.1': 370 + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} 371 + 372 + '@changesets/parse@0.4.2': 373 + resolution: {integrity: sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==} 374 + 375 + '@changesets/pre@2.0.2': 376 + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} 377 + 378 + '@changesets/read@0.6.6': 379 + resolution: {integrity: sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==} 380 + 381 + '@changesets/should-skip-package@0.1.2': 382 + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} 383 + 384 + '@changesets/types@4.1.0': 385 + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} 386 + 387 + '@changesets/types@6.1.0': 388 + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} 389 + 390 + '@changesets/write@0.4.0': 391 + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} 267 392 268 - /@esbuild/aix-ppc64@0.20.2: 269 - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} 270 - engines: {node: '>=12'} 393 + '@esbuild/aix-ppc64@0.25.12': 394 + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} 395 + engines: {node: '>=18'} 271 396 cpu: [ppc64] 272 397 os: [aix] 273 - requiresBuild: true 274 - dev: true 275 - optional: true 276 398 277 - /@esbuild/android-arm64@0.20.2: 278 - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} 279 - engines: {node: '>=12'} 399 + '@esbuild/android-arm64@0.25.12': 400 + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} 401 + engines: {node: '>=18'} 280 402 cpu: [arm64] 281 403 os: [android] 282 - requiresBuild: true 283 - dev: true 284 - optional: true 285 404 286 - /@esbuild/android-arm@0.20.2: 287 - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} 288 - engines: {node: '>=12'} 405 + '@esbuild/android-arm@0.25.12': 406 + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} 407 + engines: {node: '>=18'} 289 408 cpu: [arm] 290 409 os: [android] 291 - requiresBuild: true 292 - dev: true 293 - optional: true 294 410 295 - /@esbuild/android-x64@0.20.2: 296 - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} 297 - engines: {node: '>=12'} 411 + '@esbuild/android-x64@0.25.12': 412 + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} 413 + engines: {node: '>=18'} 298 414 cpu: [x64] 299 415 os: [android] 300 - requiresBuild: true 301 - dev: true 302 - optional: true 303 416 304 - /@esbuild/darwin-arm64@0.20.2: 305 - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} 306 - engines: {node: '>=12'} 417 + '@esbuild/darwin-arm64@0.25.12': 418 + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} 419 + engines: {node: '>=18'} 307 420 cpu: [arm64] 308 421 os: [darwin] 309 - requiresBuild: true 310 - dev: true 311 - optional: true 312 422 313 - /@esbuild/darwin-x64@0.20.2: 314 - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} 315 - engines: {node: '>=12'} 423 + '@esbuild/darwin-x64@0.25.12': 424 + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} 425 + engines: {node: '>=18'} 316 426 cpu: [x64] 317 427 os: [darwin] 318 - requiresBuild: true 319 - dev: true 320 - optional: true 321 428 322 - /@esbuild/freebsd-arm64@0.20.2: 323 - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} 324 - engines: {node: '>=12'} 429 + '@esbuild/freebsd-arm64@0.25.12': 430 + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} 431 + engines: {node: '>=18'} 325 432 cpu: [arm64] 326 433 os: [freebsd] 327 - requiresBuild: true 328 - dev: true 329 - optional: true 330 434 331 - /@esbuild/freebsd-x64@0.20.2: 332 - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} 333 - engines: {node: '>=12'} 435 + '@esbuild/freebsd-x64@0.25.12': 436 + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} 437 + engines: {node: '>=18'} 334 438 cpu: [x64] 335 439 os: [freebsd] 336 - requiresBuild: true 337 - dev: true 338 - optional: true 339 440 340 - /@esbuild/linux-arm64@0.20.2: 341 - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} 342 - engines: {node: '>=12'} 441 + '@esbuild/linux-arm64@0.25.12': 442 + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} 443 + engines: {node: '>=18'} 343 444 cpu: [arm64] 344 445 os: [linux] 345 - requiresBuild: true 346 - dev: true 347 - optional: true 348 446 349 - /@esbuild/linux-arm@0.20.2: 350 - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} 351 - engines: {node: '>=12'} 447 + '@esbuild/linux-arm@0.25.12': 448 + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} 449 + engines: {node: '>=18'} 352 450 cpu: [arm] 353 451 os: [linux] 354 - requiresBuild: true 355 - dev: true 356 - optional: true 357 452 358 - /@esbuild/linux-ia32@0.20.2: 359 - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} 360 - engines: {node: '>=12'} 453 + '@esbuild/linux-ia32@0.25.12': 454 + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} 455 + engines: {node: '>=18'} 361 456 cpu: [ia32] 362 457 os: [linux] 363 - requiresBuild: true 364 - dev: true 365 - optional: true 366 458 367 - /@esbuild/linux-loong64@0.20.2: 368 - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} 369 - engines: {node: '>=12'} 459 + '@esbuild/linux-loong64@0.25.12': 460 + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} 461 + engines: {node: '>=18'} 370 462 cpu: [loong64] 371 463 os: [linux] 372 - requiresBuild: true 373 - dev: true 374 - optional: true 375 464 376 - /@esbuild/linux-mips64el@0.20.2: 377 - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} 378 - engines: {node: '>=12'} 465 + '@esbuild/linux-mips64el@0.25.12': 466 + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} 467 + engines: {node: '>=18'} 379 468 cpu: [mips64el] 380 469 os: [linux] 381 - requiresBuild: true 382 - dev: true 383 - optional: true 384 470 385 - /@esbuild/linux-ppc64@0.20.2: 386 - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} 387 - engines: {node: '>=12'} 471 + '@esbuild/linux-ppc64@0.25.12': 472 + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} 473 + engines: {node: '>=18'} 388 474 cpu: [ppc64] 389 475 os: [linux] 390 - requiresBuild: true 391 - dev: true 392 - optional: true 393 476 394 - /@esbuild/linux-riscv64@0.20.2: 395 - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} 396 - engines: {node: '>=12'} 477 + '@esbuild/linux-riscv64@0.25.12': 478 + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} 479 + engines: {node: '>=18'} 397 480 cpu: [riscv64] 398 481 os: [linux] 399 - requiresBuild: true 400 - dev: true 401 - optional: true 402 482 403 - /@esbuild/linux-s390x@0.20.2: 404 - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} 405 - engines: {node: '>=12'} 483 + '@esbuild/linux-s390x@0.25.12': 484 + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} 485 + engines: {node: '>=18'} 406 486 cpu: [s390x] 407 487 os: [linux] 408 - requiresBuild: true 409 - dev: true 410 - optional: true 411 488 412 - /@esbuild/linux-x64@0.20.2: 413 - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} 414 - engines: {node: '>=12'} 489 + '@esbuild/linux-x64@0.25.12': 490 + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} 491 + engines: {node: '>=18'} 415 492 cpu: [x64] 416 493 os: [linux] 417 - requiresBuild: true 418 - dev: true 419 - optional: true 420 494 421 - /@esbuild/netbsd-x64@0.20.2: 422 - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} 423 - engines: {node: '>=12'} 495 + '@esbuild/netbsd-arm64@0.25.12': 496 + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} 497 + engines: {node: '>=18'} 498 + cpu: [arm64] 499 + os: [netbsd] 500 + 501 + '@esbuild/netbsd-x64@0.25.12': 502 + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} 503 + engines: {node: '>=18'} 424 504 cpu: [x64] 425 505 os: [netbsd] 426 - requiresBuild: true 427 - dev: true 428 - optional: true 506 + 507 + '@esbuild/openbsd-arm64@0.25.12': 508 + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} 509 + engines: {node: '>=18'} 510 + cpu: [arm64] 511 + os: [openbsd] 429 512 430 - /@esbuild/openbsd-x64@0.20.2: 431 - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} 432 - engines: {node: '>=12'} 513 + '@esbuild/openbsd-x64@0.25.12': 514 + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} 515 + engines: {node: '>=18'} 433 516 cpu: [x64] 434 517 os: [openbsd] 435 - requiresBuild: true 436 - dev: true 437 - optional: true 518 + 519 + '@esbuild/openharmony-arm64@0.25.12': 520 + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} 521 + engines: {node: '>=18'} 522 + cpu: [arm64] 523 + os: [openharmony] 438 524 439 - /@esbuild/sunos-x64@0.20.2: 440 - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} 441 - engines: {node: '>=12'} 525 + '@esbuild/sunos-x64@0.25.12': 526 + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} 527 + engines: {node: '>=18'} 442 528 cpu: [x64] 443 529 os: [sunos] 444 - requiresBuild: true 445 - dev: true 446 - optional: true 447 530 448 - /@esbuild/win32-arm64@0.20.2: 449 - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} 450 - engines: {node: '>=12'} 531 + '@esbuild/win32-arm64@0.25.12': 532 + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} 533 + engines: {node: '>=18'} 451 534 cpu: [arm64] 452 535 os: [win32] 453 - requiresBuild: true 454 - dev: true 455 - optional: true 456 536 457 - /@esbuild/win32-ia32@0.20.2: 458 - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} 459 - engines: {node: '>=12'} 537 + '@esbuild/win32-ia32@0.25.12': 538 + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} 539 + engines: {node: '>=18'} 460 540 cpu: [ia32] 461 541 os: [win32] 462 - requiresBuild: true 463 - dev: true 464 - optional: true 465 542 466 - /@esbuild/win32-x64@0.20.2: 467 - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} 468 - engines: {node: '>=12'} 543 + '@esbuild/win32-x64@0.25.12': 544 + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} 545 + engines: {node: '>=18'} 469 546 cpu: [x64] 470 547 os: [win32] 471 - requiresBuild: true 472 - dev: true 473 - optional: true 474 548 475 - /@externdefs/bluesky-client@0.5.8: 476 - resolution: {integrity: sha512-RM7huV2fZlPscfv8TTsM7wDQZ7yw4doJvch5TSIkj4NCHXg+iTADmzv7FCuWNvrMwsAbqiWe+Uz2AsPuTWiMDA==} 477 - dev: false 549 + '@inquirer/external-editor@1.0.3': 550 + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} 551 + engines: {node: '>=18'} 552 + peerDependencies: 553 + '@types/node': '>=18' 554 + peerDependenciesMeta: 555 + '@types/node': 556 + optional: true 478 557 479 - /@intrnl/jsx-to-string@0.1.6(@babel/core@7.24.4): 480 - resolution: {integrity: sha512-ib6FWU0GcJdzsBv8zvFj43IDNRakWoC8JlpRDYBsOgIl3aXCR2PKazE6CnRJxmSvSHF9FQ5tgaLGFGSBGTjy1Q==} 481 - dependencies: 482 - '@babel/helper-plugin-utils': 7.24.0 483 - '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.4) 484 - '@babel/types': 7.24.0 485 - csstype: 3.1.3 486 - transitivePeerDependencies: 487 - - '@babel/core' 488 - dev: true 558 + '@isaacs/balanced-match@4.0.1': 559 + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} 560 + engines: {node: 20 || >=22} 489 561 490 - /@jridgewell/gen-mapping@0.3.5: 491 - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 492 - engines: {node: '>=6.0.0'} 493 - dependencies: 494 - '@jridgewell/set-array': 1.2.1 495 - '@jridgewell/sourcemap-codec': 1.4.15 496 - '@jridgewell/trace-mapping': 0.3.25 497 - dev: true 562 + '@isaacs/brace-expansion@5.0.0': 563 + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} 564 + engines: {node: 20 || >=22} 565 + 566 + '@jridgewell/gen-mapping@0.3.13': 567 + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} 568 + 569 + '@jridgewell/remapping@2.3.5': 570 + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} 498 571 499 - /@jridgewell/resolve-uri@3.1.2: 572 + '@jridgewell/resolve-uri@3.1.2': 500 573 resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 501 574 engines: {node: '>=6.0.0'} 502 - dev: true 503 575 504 - /@jridgewell/set-array@1.2.1: 505 - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 506 - engines: {node: '>=6.0.0'} 507 - dev: true 576 + '@jridgewell/source-map@0.3.11': 577 + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} 508 578 509 - /@jridgewell/sourcemap-codec@1.4.15: 510 - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 511 - dev: true 579 + '@jridgewell/sourcemap-codec@1.5.5': 580 + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 512 581 513 - /@jridgewell/trace-mapping@0.3.25: 514 - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 515 - dependencies: 516 - '@jridgewell/resolve-uri': 3.1.2 517 - '@jridgewell/sourcemap-codec': 1.4.15 518 - dev: true 582 + '@jridgewell/trace-mapping@0.3.31': 583 + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} 519 584 520 - /@microsoft/api-extractor-model@7.28.13: 521 - resolution: {integrity: sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==} 522 - dependencies: 523 - '@microsoft/tsdoc': 0.14.2 524 - '@microsoft/tsdoc-config': 0.16.2 525 - '@rushstack/node-core-library': 4.0.2 526 - transitivePeerDependencies: 527 - - '@types/node' 528 - dev: true 585 + '@manypkg/find-root@1.1.0': 586 + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} 587 + 588 + '@manypkg/get-packages@1.1.3': 589 + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} 590 + 591 + '@microsoft/api-extractor-model@7.32.1': 592 + resolution: {integrity: sha512-u4yJytMYiUAnhcNQcZDTh/tVtlrzKlyKrQnLOV+4Qr/5gV+cpufWzCYAB1Q23URFqD6z2RoL2UYncM9xJVGNKA==} 529 593 530 - /@microsoft/api-extractor@7.43.0: 531 - resolution: {integrity: sha512-GFhTcJpB+MI6FhvXEI9b2K0snulNLWHqC/BbcJtyNYcKUiw7l3Lgis5ApsYncJ0leALX7/of4XfmXk+maT111w==} 594 + '@microsoft/api-extractor@7.55.1': 595 + resolution: {integrity: sha512-l8Z+8qrLkZFM3HM95Dbpqs6G39fpCa7O5p8A7AkA6hSevxkgwsOlLrEuPv0ADOyj5dI1Af5WVDiwpKG/ya5G3w==} 532 596 hasBin: true 533 - dependencies: 534 - '@microsoft/api-extractor-model': 7.28.13 535 - '@microsoft/tsdoc': 0.14.2 536 - '@microsoft/tsdoc-config': 0.16.2 537 - '@rushstack/node-core-library': 4.0.2 538 - '@rushstack/rig-package': 0.5.2 539 - '@rushstack/terminal': 0.10.0 540 - '@rushstack/ts-command-line': 4.19.1 541 - lodash: 4.17.21 542 - minimatch: 3.0.8 543 - resolve: 1.22.8 544 - semver: 7.5.4 545 - source-map: 0.6.1 546 - typescript: 5.4.2 547 - transitivePeerDependencies: 548 - - '@types/node' 549 - dev: true 597 + 598 + '@microsoft/tsdoc-config@0.18.0': 599 + resolution: {integrity: sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==} 600 + 601 + '@microsoft/tsdoc@0.16.0': 602 + resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} 603 + 604 + '@nodelib/fs.scandir@2.1.5': 605 + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 606 + engines: {node: '>= 8'} 607 + 608 + '@nodelib/fs.stat@2.0.5': 609 + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 610 + engines: {node: '>= 8'} 611 + 612 + '@nodelib/fs.walk@1.2.8': 613 + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 614 + engines: {node: '>= 8'} 615 + 616 + '@preact/preset-vite@2.10.2': 617 + resolution: {integrity: sha512-K9wHlJOtkE+cGqlyQ5v9kL3Ge0Ql4LlIZjkUTL+1zf3nNdF88F9UZN6VTV8jdzBX9Fl7WSzeNMSDG7qECPmSmg==} 618 + peerDependencies: 619 + '@babel/core': 7.x 620 + vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x 621 + 622 + '@prefresh/babel-plugin@0.5.2': 623 + resolution: {integrity: sha512-AOl4HG6dAxWkJ5ndPHBgBa49oo/9bOiJuRDKHLSTyH+Fd9x00shTXpdiTj1W41l6oQIwUOAgJeHMn4QwIDpHkA==} 550 624 551 - /@microsoft/tsdoc-config@0.16.2: 552 - resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==} 553 - dependencies: 554 - '@microsoft/tsdoc': 0.14.2 555 - ajv: 6.12.6 556 - jju: 1.4.0 557 - resolve: 1.19.0 558 - dev: true 625 + '@prefresh/core@1.5.9': 626 + resolution: {integrity: sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==} 627 + peerDependencies: 628 + preact: ^10.0.0 || ^11.0.0-0 559 629 560 - /@microsoft/tsdoc@0.14.2: 561 - resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} 562 - dev: true 630 + '@prefresh/utils@1.2.1': 631 + resolution: {integrity: sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==} 563 632 564 - /@rollup/plugin-babel@6.0.4(@babel/core@7.24.4): 565 - resolution: {integrity: sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==} 566 - engines: {node: '>=14.0.0'} 633 + '@prefresh/vite@2.4.11': 634 + resolution: {integrity: sha512-/XjURQqdRiCG3NpMmWqE9kJwrg9IchIOWHzulCfqg2sRe/8oQ1g5De7xrk9lbqPIQLn7ntBkKdqWXIj4E9YXyg==} 567 635 peerDependencies: 568 - '@babel/core': ^7.0.0 569 - '@types/babel__core': ^7.1.9 570 - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 571 - peerDependenciesMeta: 572 - '@types/babel__core': 573 - optional: true 574 - rollup: 575 - optional: true 576 - dependencies: 577 - '@babel/core': 7.24.4 578 - '@babel/helper-module-imports': 7.24.3 579 - '@rollup/pluginutils': 5.1.0 580 - dev: true 636 + preact: ^10.4.0 || ^11.0.0-0 637 + vite: '>=2.0.0' 638 + 639 + '@rollup/pluginutils@4.2.1': 640 + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} 641 + engines: {node: '>= 8.0.0'} 581 642 582 - /@rollup/pluginutils@5.1.0: 583 - resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} 643 + '@rollup/pluginutils@5.3.0': 644 + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} 584 645 engines: {node: '>=14.0.0'} 585 646 peerDependencies: 586 647 rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 587 648 peerDependenciesMeta: 588 649 rollup: 589 650 optional: true 590 - dependencies: 591 - '@types/estree': 1.0.5 592 - estree-walker: 2.0.2 593 - picomatch: 2.3.1 594 - dev: true 595 651 596 - /@rollup/rollup-android-arm-eabi@4.14.2: 597 - resolution: {integrity: sha512-ahxSgCkAEk+P/AVO0vYr7DxOD3CwAQrT0Go9BJyGQ9Ef0QxVOfjDZMiF4Y2s3mLyPrjonchIMH/tbWHucJMykQ==} 652 + '@rollup/rollup-android-arm-eabi@4.53.3': 653 + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} 598 654 cpu: [arm] 599 655 os: [android] 600 - requiresBuild: true 601 - dev: true 602 - optional: true 603 656 604 - /@rollup/rollup-android-arm64@4.14.2: 605 - resolution: {integrity: sha512-lAarIdxZWbFSHFSDao9+I/F5jDaKyCqAPMq5HqnfpBw8dKDiCaaqM0lq5h1pQTLeIqueeay4PieGR5jGZMWprw==} 657 + '@rollup/rollup-android-arm64@4.53.3': 658 + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} 606 659 cpu: [arm64] 607 660 os: [android] 608 - requiresBuild: true 609 - dev: true 610 - optional: true 611 661 612 - /@rollup/rollup-darwin-arm64@4.14.2: 613 - resolution: {integrity: sha512-SWsr8zEUk82KSqquIMgZEg2GE5mCSfr9sE/thDROkX6pb3QQWPp8Vw8zOq2GyxZ2t0XoSIUlvHDkrf5Gmf7x3Q==} 662 + '@rollup/rollup-darwin-arm64@4.53.3': 663 + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} 614 664 cpu: [arm64] 615 665 os: [darwin] 616 - requiresBuild: true 617 - dev: true 618 - optional: true 619 666 620 - /@rollup/rollup-darwin-x64@4.14.2: 621 - resolution: {integrity: sha512-o/HAIrQq0jIxJAhgtIvV5FWviYK4WB0WwV91SLUnsliw1lSAoLsmgEEgRWzDguAFeUEUUoIWXiJrPqU7vGiVkA==} 667 + '@rollup/rollup-darwin-x64@4.53.3': 668 + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} 622 669 cpu: [x64] 623 670 os: [darwin] 624 - requiresBuild: true 625 - dev: true 626 - optional: true 671 + 672 + '@rollup/rollup-freebsd-arm64@4.53.3': 673 + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} 674 + cpu: [arm64] 675 + os: [freebsd] 676 + 677 + '@rollup/rollup-freebsd-x64@4.53.3': 678 + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} 679 + cpu: [x64] 680 + os: [freebsd] 681 + 682 + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': 683 + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} 684 + cpu: [arm] 685 + os: [linux] 627 686 628 - /@rollup/rollup-linux-arm-gnueabihf@4.14.2: 629 - resolution: {integrity: sha512-nwlJ65UY9eGq91cBi6VyDfArUJSKOYt5dJQBq8xyLhvS23qO+4Nr/RreibFHjP6t+5ap2ohZrUJcHv5zk5ju/g==} 687 + '@rollup/rollup-linux-arm-musleabihf@4.53.3': 688 + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} 630 689 cpu: [arm] 631 690 os: [linux] 632 - requiresBuild: true 633 - dev: true 634 - optional: true 635 691 636 - /@rollup/rollup-linux-arm64-gnu@4.14.2: 637 - resolution: {integrity: sha512-Pg5TxxO2IVlMj79+c/9G0LREC9SY3HM+pfAwX7zj5/cAuwrbfj2Wv9JbMHIdPCfQpYsI4g9mE+2Bw/3aeSs2rQ==} 692 + '@rollup/rollup-linux-arm64-gnu@4.53.3': 693 + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} 638 694 cpu: [arm64] 639 695 os: [linux] 640 - requiresBuild: true 641 - dev: true 642 - optional: true 643 696 644 - /@rollup/rollup-linux-arm64-musl@4.14.2: 645 - resolution: {integrity: sha512-cAOTjGNm84gc6tS02D1EXtG7tDRsVSDTBVXOLbj31DkwfZwgTPYZ6aafSU7rD/4R2a34JOwlF9fQayuTSkoclA==} 697 + '@rollup/rollup-linux-arm64-musl@4.53.3': 698 + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} 646 699 cpu: [arm64] 647 700 os: [linux] 648 - requiresBuild: true 649 - dev: true 650 - optional: true 701 + 702 + '@rollup/rollup-linux-loong64-gnu@4.53.3': 703 + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} 704 + cpu: [loong64] 705 + os: [linux] 651 706 652 - /@rollup/rollup-linux-powerpc64le-gnu@4.14.2: 653 - resolution: {integrity: sha512-4RyT6v1kXb7C0fn6zV33rvaX05P0zHoNzaXI/5oFHklfKm602j+N4mn2YvoezQViRLPnxP8M1NaY4s/5kXO5cw==} 707 + '@rollup/rollup-linux-ppc64-gnu@4.53.3': 708 + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} 654 709 cpu: [ppc64] 655 710 os: [linux] 656 - requiresBuild: true 657 - dev: true 658 - optional: true 659 711 660 - /@rollup/rollup-linux-riscv64-gnu@4.14.2: 661 - resolution: {integrity: sha512-KNUH6jC/vRGAKSorySTyc/yRYlCwN/5pnMjXylfBniwtJx5O7X17KG/0efj8XM3TZU7raYRXJFFReOzNmL1n1w==} 712 + '@rollup/rollup-linux-riscv64-gnu@4.53.3': 713 + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} 662 714 cpu: [riscv64] 663 715 os: [linux] 664 - requiresBuild: true 665 - dev: true 666 - optional: true 667 716 668 - /@rollup/rollup-linux-s390x-gnu@4.14.2: 669 - resolution: {integrity: sha512-xPV4y73IBEXToNPa3h5lbgXOi/v0NcvKxU0xejiFw6DtIYQqOTMhZ2DN18/HrrP0PmiL3rGtRG9gz1QE8vFKXQ==} 717 + '@rollup/rollup-linux-riscv64-musl@4.53.3': 718 + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} 719 + cpu: [riscv64] 720 + os: [linux] 721 + 722 + '@rollup/rollup-linux-s390x-gnu@4.53.3': 723 + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} 670 724 cpu: [s390x] 671 725 os: [linux] 672 - requiresBuild: true 673 - dev: true 674 - optional: true 675 726 676 - /@rollup/rollup-linux-x64-gnu@4.14.2: 677 - resolution: {integrity: sha512-QBhtr07iFGmF9egrPOWyO5wciwgtzKkYPNLVCFZTmr4TWmY0oY2Dm/bmhHjKRwZoGiaKdNcKhFtUMBKvlchH+Q==} 727 + '@rollup/rollup-linux-x64-gnu@4.53.3': 728 + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} 678 729 cpu: [x64] 679 730 os: [linux] 680 - requiresBuild: true 681 - dev: true 682 - optional: true 683 731 684 - /@rollup/rollup-linux-x64-musl@4.14.2: 685 - resolution: {integrity: sha512-8zfsQRQGH23O6qazZSFY5jP5gt4cFvRuKTpuBsC1ZnSWxV8ZKQpPqOZIUtdfMOugCcBvFGRa1pDC/tkf19EgBw==} 732 + '@rollup/rollup-linux-x64-musl@4.53.3': 733 + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} 686 734 cpu: [x64] 687 735 os: [linux] 688 - requiresBuild: true 689 - dev: true 690 - optional: true 691 736 692 - /@rollup/rollup-win32-arm64-msvc@4.14.2: 693 - resolution: {integrity: sha512-H4s8UjgkPnlChl6JF5empNvFHp77Jx+Wfy2EtmYPe9G22XV+PMuCinZVHurNe8ggtwoaohxARJZbaH/3xjB/FA==} 737 + '@rollup/rollup-openharmony-arm64@4.53.3': 738 + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} 739 + cpu: [arm64] 740 + os: [openharmony] 741 + 742 + '@rollup/rollup-win32-arm64-msvc@4.53.3': 743 + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} 694 744 cpu: [arm64] 695 745 os: [win32] 696 - requiresBuild: true 697 - dev: true 698 - optional: true 699 746 700 - /@rollup/rollup-win32-ia32-msvc@4.14.2: 701 - resolution: {integrity: sha512-djqpAjm/i8erWYF0K6UY4kRO3X5+T4TypIqw60Q8MTqSBaQNpNXDhxdjpZ3ikgb+wn99svA7jxcXpiyg9MUsdw==} 747 + '@rollup/rollup-win32-ia32-msvc@4.53.3': 748 + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} 702 749 cpu: [ia32] 703 750 os: [win32] 704 - requiresBuild: true 705 - dev: true 706 - optional: true 707 751 708 - /@rollup/rollup-win32-x64-msvc@4.14.2: 709 - resolution: {integrity: sha512-teAqzLT0yTYZa8ZP7zhFKEx4cotS8Tkk5XiqNMJhD4CpaWB1BHARE4Qy+RzwnXvSAYv+Q3jAqCVBS+PS+Yee8Q==} 752 + '@rollup/rollup-win32-x64-gnu@4.53.3': 753 + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} 754 + cpu: [x64] 755 + os: [win32] 756 + 757 + '@rollup/rollup-win32-x64-msvc@4.53.3': 758 + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} 710 759 cpu: [x64] 711 760 os: [win32] 712 - requiresBuild: true 713 - dev: true 714 - optional: true 761 + 762 + '@rushstack/node-core-library@5.19.0': 763 + resolution: {integrity: sha512-BxAopbeWBvNJ6VGiUL+5lbJXywTdsnMeOS8j57Cn/xY10r6sV/gbsTlfYKjzVCUBZATX2eRzJHSMCchsMTGN6A==} 764 + peerDependencies: 765 + '@types/node': '*' 766 + peerDependenciesMeta: 767 + '@types/node': 768 + optional: true 715 769 716 - /@rushstack/node-core-library@4.0.2: 717 - resolution: {integrity: sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==} 770 + '@rushstack/problem-matcher@0.1.1': 771 + resolution: {integrity: sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==} 718 772 peerDependencies: 719 773 '@types/node': '*' 720 774 peerDependenciesMeta: 721 775 '@types/node': 722 776 optional: true 723 - dependencies: 724 - fs-extra: 7.0.1 725 - import-lazy: 4.0.0 726 - jju: 1.4.0 727 - resolve: 1.22.8 728 - semver: 7.5.4 729 - z-schema: 5.0.5 730 - dev: true 731 777 732 - /@rushstack/rig-package@0.5.2: 733 - resolution: {integrity: sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==} 734 - dependencies: 735 - resolve: 1.22.8 736 - strip-json-comments: 3.1.1 737 - dev: true 778 + '@rushstack/rig-package@0.6.0': 779 + resolution: {integrity: sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==} 738 780 739 - /@rushstack/terminal@0.10.0: 740 - resolution: {integrity: sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==} 781 + '@rushstack/terminal@0.19.4': 782 + resolution: {integrity: sha512-f4XQk02CrKfrMgyOfhYd3qWI944dLC21S4I/LUhrlAP23GTMDNG6EK5effQtFkISwUKCgD9vMBrJZaPSUquxWQ==} 741 783 peerDependencies: 742 784 '@types/node': '*' 743 785 peerDependenciesMeta: 744 786 '@types/node': 745 787 optional: true 746 - dependencies: 747 - '@rushstack/node-core-library': 4.0.2 748 - supports-color: 8.1.1 749 - dev: true 788 + 789 + '@rushstack/ts-command-line@5.1.4': 790 + resolution: {integrity: sha512-H0I6VdJ6sOUbktDFpP2VW5N29w8v4hRoNZOQz02vtEi6ZTYL1Ju8u+TcFiFawUDrUsx/5MQTUhd79uwZZVwVlA==} 791 + 792 + '@standard-schema/spec@1.0.0': 793 + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} 750 794 751 - /@rushstack/ts-command-line@4.19.1: 752 - resolution: {integrity: sha512-J7H768dgcpG60d7skZ5uSSwyCZs/S2HrWP1Ds8d1qYAyaaeJmpmmLr9BVw97RjFzmQPOYnoXcKA4GkqDCkduQg==} 753 - dependencies: 754 - '@rushstack/terminal': 0.10.0 755 - '@types/argparse': 1.0.38 756 - argparse: 1.0.10 757 - string-argv: 0.3.2 758 - transitivePeerDependencies: 759 - - '@types/node' 760 - dev: true 795 + '@sveltejs/acorn-typescript@1.0.8': 796 + resolution: {integrity: sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==} 797 + peerDependencies: 798 + acorn: ^8.9.0 761 799 762 - /@types/argparse@1.0.38: 800 + '@sveltejs/vite-plugin-svelte-inspector@5.0.1': 801 + resolution: {integrity: sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==} 802 + engines: {node: ^20.19 || ^22.12 || >=24} 803 + peerDependencies: 804 + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 805 + svelte: ^5.0.0 806 + vite: ^6.3.0 || ^7.0.0 807 + 808 + '@sveltejs/vite-plugin-svelte@6.2.1': 809 + resolution: {integrity: sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==} 810 + engines: {node: ^20.19 || ^22.12 || >=24} 811 + peerDependencies: 812 + svelte: ^5.0.0 813 + vite: ^6.3.0 || ^7.0.0 814 + 815 + '@tsconfig/svelte@5.0.6': 816 + resolution: {integrity: sha512-yGxYL0I9eETH1/DR9qVJey4DAsCdeau4a9wYPKuXfEhm8lFO8wg+LLYJjIpAm6Fw7HSlhepPhYPDop75485yWQ==} 817 + 818 + '@types/argparse@1.0.38': 763 819 resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} 764 - dev: true 765 820 766 - /@types/estree@1.0.5: 767 - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} 768 - dev: true 821 + '@types/estree@1.0.8': 822 + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 769 823 770 - /@volar/language-core@1.11.1: 771 - resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} 772 - dependencies: 773 - '@volar/source-map': 1.11.1 774 - dev: true 824 + '@types/node@12.20.55': 825 + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} 775 826 776 - /@volar/source-map@1.11.1: 777 - resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==} 778 - dependencies: 779 - muggle-string: 0.3.1 780 - dev: true 827 + '@types/node@24.10.1': 828 + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} 781 829 782 - /@volar/typescript@1.11.1: 783 - resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==} 784 - dependencies: 785 - '@volar/language-core': 1.11.1 786 - path-browserify: 1.0.1 787 - dev: true 830 + '@volar/language-core@2.4.26': 831 + resolution: {integrity: sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A==} 788 832 789 - /@vue/compiler-core@3.4.21: 790 - resolution: {integrity: sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==} 791 - dependencies: 792 - '@babel/parser': 7.24.4 793 - '@vue/shared': 3.4.21 794 - entities: 4.5.0 795 - estree-walker: 2.0.2 796 - source-map-js: 1.2.0 797 - dev: true 833 + '@volar/source-map@2.4.26': 834 + resolution: {integrity: sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw==} 835 + 836 + '@volar/typescript@2.4.26': 837 + resolution: {integrity: sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA==} 838 + 839 + '@vue/compiler-core@3.5.25': 840 + resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==} 841 + 842 + '@vue/compiler-dom@3.5.25': 843 + resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==} 798 844 799 - /@vue/compiler-dom@3.4.21: 800 - resolution: {integrity: sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==} 801 - dependencies: 802 - '@vue/compiler-core': 3.4.21 803 - '@vue/shared': 3.4.21 804 - dev: true 845 + '@vue/compiler-vue2@2.7.16': 846 + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} 805 847 806 - /@vue/language-core@1.8.27(typescript@5.4.5): 807 - resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==} 848 + '@vue/language-core@2.2.0': 849 + resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} 808 850 peerDependencies: 809 851 typescript: '*' 810 852 peerDependenciesMeta: 811 853 typescript: 812 854 optional: true 813 - dependencies: 814 - '@volar/language-core': 1.11.1 815 - '@volar/source-map': 1.11.1 816 - '@vue/compiler-dom': 3.4.21 817 - '@vue/shared': 3.4.21 818 - computeds: 0.0.1 819 - minimatch: 9.0.4 820 - muggle-string: 0.3.1 821 - path-browserify: 1.0.1 822 - typescript: 5.4.5 823 - vue-template-compiler: 2.7.16 824 - dev: true 855 + 856 + '@vue/shared@3.5.25': 857 + resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==} 858 + 859 + acorn@8.15.0: 860 + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} 861 + engines: {node: '>=0.4.0'} 862 + hasBin: true 863 + 864 + ajv-draft-04@1.0.0: 865 + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} 866 + peerDependencies: 867 + ajv: ^8.5.0 868 + peerDependenciesMeta: 869 + ajv: 870 + optional: true 825 871 826 - /@vue/shared@3.4.21: 827 - resolution: {integrity: sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==} 828 - dev: true 872 + ajv-formats@3.0.1: 873 + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} 874 + peerDependencies: 875 + ajv: ^8.0.0 876 + peerDependenciesMeta: 877 + ajv: 878 + optional: true 829 879 830 - /ajv@6.12.6: 831 - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 832 - dependencies: 833 - fast-deep-equal: 3.1.3 834 - fast-json-stable-stringify: 2.1.0 835 - json-schema-traverse: 0.4.1 836 - uri-js: 4.4.1 837 - dev: true 880 + ajv@8.12.0: 881 + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} 882 + 883 + ajv@8.13.0: 884 + resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} 885 + 886 + alien-signals@0.4.14: 887 + resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} 888 + 889 + ansi-colors@4.1.3: 890 + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} 891 + engines: {node: '>=6'} 838 892 839 - /ansi-styles@3.2.1: 840 - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} 841 - engines: {node: '>=4'} 842 - dependencies: 843 - color-convert: 1.9.3 844 - dev: true 893 + ansi-regex@5.0.1: 894 + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 895 + engines: {node: '>=8'} 845 896 846 - /argparse@1.0.10: 897 + argparse@1.0.10: 847 898 resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} 848 - dependencies: 849 - sprintf-js: 1.0.3 850 - dev: true 899 + 900 + argparse@2.0.1: 901 + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 902 + 903 + aria-query@5.3.2: 904 + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} 905 + engines: {node: '>= 0.4'} 906 + 907 + array-union@2.1.0: 908 + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 909 + engines: {node: '>=8'} 851 910 852 - /balanced-match@1.0.2: 911 + axobject-query@4.1.0: 912 + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} 913 + engines: {node: '>= 0.4'} 914 + 915 + babel-plugin-transform-hook-names@1.0.2: 916 + resolution: {integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==} 917 + peerDependencies: 918 + '@babel/core': ^7.12.10 919 + 920 + balanced-match@1.0.2: 853 921 resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 854 - dev: true 922 + 923 + baseline-browser-mapping@2.9.0: 924 + resolution: {integrity: sha512-Mh++g+2LPfzZToywfE1BUzvZbfOY52Nil0rn9H1CPC5DJ7fX+Vir7nToBeoiSbB1zTNeGYbELEvJESujgGrzXw==} 925 + hasBin: true 926 + 927 + better-path-resolve@1.0.0: 928 + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} 929 + engines: {node: '>=4'} 855 930 856 - /brace-expansion@1.1.11: 857 - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 858 - dependencies: 859 - balanced-match: 1.0.2 860 - concat-map: 0.0.1 861 - dev: true 931 + boolbase@1.0.0: 932 + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} 933 + 934 + brace-expansion@2.0.2: 935 + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} 862 936 863 - /brace-expansion@2.0.1: 864 - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 865 - dependencies: 866 - balanced-match: 1.0.2 867 - dev: true 937 + braces@3.0.3: 938 + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 939 + engines: {node: '>=8'} 868 940 869 - /browserslist@4.23.0: 870 - resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} 941 + browserslist@4.28.1: 942 + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} 871 943 engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} 872 944 hasBin: true 873 - dependencies: 874 - caniuse-lite: 1.0.30001609 875 - electron-to-chromium: 1.4.735 876 - node-releases: 2.0.14 877 - update-browserslist-db: 1.0.13(browserslist@4.23.0) 878 - dev: true 945 + 946 + buffer-from@1.1.2: 947 + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 879 948 880 - /caniuse-lite@1.0.30001609: 881 - resolution: {integrity: sha512-JFPQs34lHKx1B5t1EpQpWH4c+29zIyn/haGsbpfq3suuV9v56enjFt23zqijxGTMwy1p/4H2tjnQMY+p1WoAyA==} 882 - dev: true 949 + caniuse-lite@1.0.30001759: 950 + resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} 883 951 884 - /chalk@2.4.2: 885 - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} 886 - engines: {node: '>=4'} 887 - dependencies: 888 - ansi-styles: 3.2.1 889 - escape-string-regexp: 1.0.5 890 - supports-color: 5.5.0 891 - dev: true 952 + chardet@2.1.1: 953 + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} 892 954 893 - /color-convert@1.9.3: 894 - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} 895 - dependencies: 896 - color-name: 1.1.3 897 - dev: true 955 + chokidar@4.0.3: 956 + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} 957 + engines: {node: '>= 14.16.0'} 898 958 899 - /color-name@1.1.3: 900 - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} 901 - dev: true 959 + ci-info@3.9.0: 960 + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} 961 + engines: {node: '>=8'} 962 + 963 + clsx@2.1.1: 964 + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} 965 + engines: {node: '>=6'} 966 + 967 + commander@2.20.3: 968 + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} 902 969 903 - /commander@9.5.0: 904 - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} 905 - engines: {node: ^12.20.0 || >=14} 906 - requiresBuild: true 907 - dev: true 908 - optional: true 970 + compare-versions@6.1.1: 971 + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} 909 972 910 - /computeds@0.0.1: 911 - resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} 912 - dev: true 973 + confbox@0.1.8: 974 + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} 913 975 914 - /concat-map@0.0.1: 915 - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 916 - dev: true 976 + confbox@0.2.2: 977 + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} 917 978 918 - /convert-source-map@2.0.0: 979 + convert-source-map@2.0.0: 919 980 resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} 920 - dev: true 981 + 982 + cross-spawn@7.0.6: 983 + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 984 + engines: {node: '>= 8'} 921 985 922 - /css-declaration-sorter@7.2.0: 923 - resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} 986 + css-declaration-sorter@7.3.0: 987 + resolution: {integrity: sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==} 924 988 engines: {node: ^14 || ^16 || >=18} 925 989 peerDependencies: 926 990 postcss: ^8.0.9 927 - dev: true 928 991 929 - /csstype@3.1.3: 930 - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 931 - dev: true 992 + css-select@5.2.2: 993 + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} 994 + 995 + css-what@6.2.2: 996 + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} 997 + engines: {node: '>= 6'} 932 998 933 - /de-indent@1.0.2: 999 + de-indent@1.0.2: 934 1000 resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} 935 - dev: true 936 1001 937 - /debug@4.3.4: 938 - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 1002 + debug@4.4.3: 1003 + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} 939 1004 engines: {node: '>=6.0'} 940 1005 peerDependencies: 941 1006 supports-color: '*' 942 1007 peerDependenciesMeta: 943 1008 supports-color: 944 1009 optional: true 945 - dependencies: 946 - ms: 2.1.2 947 - dev: true 1010 + 1011 + deepmerge@4.3.1: 1012 + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} 1013 + engines: {node: '>=0.10.0'} 1014 + 1015 + detect-indent@6.1.0: 1016 + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} 1017 + engines: {node: '>=8'} 1018 + 1019 + devalue@5.5.0: 1020 + resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==} 1021 + 1022 + diff@8.0.2: 1023 + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} 1024 + engines: {node: '>=0.3.1'} 1025 + 1026 + dir-glob@3.0.1: 1027 + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 1028 + engines: {node: '>=8'} 1029 + 1030 + dom-serializer@2.0.0: 1031 + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} 1032 + 1033 + domelementtype@2.3.0: 1034 + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} 1035 + 1036 + domhandler@5.0.3: 1037 + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} 1038 + engines: {node: '>= 4'} 1039 + 1040 + domutils@3.2.2: 1041 + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} 1042 + 1043 + electron-to-chromium@1.5.264: 1044 + resolution: {integrity: sha512-1tEf0nLgltC3iy9wtlYDlQDc5Rg9lEKVjEmIHJ21rI9OcqkvD45K1oyNIRA4rR1z3LgJ7KeGzEBojVcV6m4qjA==} 948 1045 949 - /electron-to-chromium@1.4.735: 950 - resolution: {integrity: sha512-pkYpvwg8VyOTQAeBqZ7jsmpCjko1Qc6We1ZtZCjRyYbT5v4AIUKDy5cQTRotQlSSZmMr8jqpEt6JtOj5k7lR7A==} 951 - dev: true 1046 + enquirer@2.4.1: 1047 + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} 1048 + engines: {node: '>=8.6'} 952 1049 953 - /entities@4.5.0: 1050 + entities@4.5.0: 954 1051 resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} 955 1052 engines: {node: '>=0.12'} 956 - dev: true 957 1053 958 - /esbuild@0.20.2: 959 - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} 960 - engines: {node: '>=12'} 1054 + esbuild@0.25.12: 1055 + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} 1056 + engines: {node: '>=18'} 961 1057 hasBin: true 962 - requiresBuild: true 963 - optionalDependencies: 964 - '@esbuild/aix-ppc64': 0.20.2 965 - '@esbuild/android-arm': 0.20.2 966 - '@esbuild/android-arm64': 0.20.2 967 - '@esbuild/android-x64': 0.20.2 968 - '@esbuild/darwin-arm64': 0.20.2 969 - '@esbuild/darwin-x64': 0.20.2 970 - '@esbuild/freebsd-arm64': 0.20.2 971 - '@esbuild/freebsd-x64': 0.20.2 972 - '@esbuild/linux-arm': 0.20.2 973 - '@esbuild/linux-arm64': 0.20.2 974 - '@esbuild/linux-ia32': 0.20.2 975 - '@esbuild/linux-loong64': 0.20.2 976 - '@esbuild/linux-mips64el': 0.20.2 977 - '@esbuild/linux-ppc64': 0.20.2 978 - '@esbuild/linux-riscv64': 0.20.2 979 - '@esbuild/linux-s390x': 0.20.2 980 - '@esbuild/linux-x64': 0.20.2 981 - '@esbuild/netbsd-x64': 0.20.2 982 - '@esbuild/openbsd-x64': 0.20.2 983 - '@esbuild/sunos-x64': 0.20.2 984 - '@esbuild/win32-arm64': 0.20.2 985 - '@esbuild/win32-ia32': 0.20.2 986 - '@esbuild/win32-x64': 0.20.2 987 - dev: true 988 1058 989 - /escalade@3.1.2: 990 - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} 1059 + escalade@3.2.0: 1060 + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 991 1061 engines: {node: '>=6'} 992 - dev: true 993 1062 994 - /escape-string-regexp@1.0.5: 995 - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} 996 - engines: {node: '>=0.8.0'} 997 - dev: true 1063 + esm-env@1.2.2: 1064 + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} 1065 + 1066 + esprima@4.0.1: 1067 + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} 1068 + engines: {node: '>=4'} 1069 + hasBin: true 1070 + 1071 + esrap@2.2.1: 1072 + resolution: {integrity: sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==} 998 1073 999 - /estree-walker@2.0.2: 1074 + estree-walker@2.0.2: 1000 1075 resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 1001 - dev: true 1002 1076 1003 - /fast-deep-equal@3.1.3: 1077 + exsolve@1.0.8: 1078 + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} 1079 + 1080 + extendable-error@0.1.7: 1081 + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} 1082 + 1083 + fast-deep-equal@3.1.3: 1004 1084 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 1005 - dev: true 1085 + 1086 + fast-glob@3.3.3: 1087 + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 1088 + engines: {node: '>=8.6.0'} 1089 + 1090 + fastq@1.19.1: 1091 + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} 1092 + 1093 + fdir@6.5.0: 1094 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 1095 + engines: {node: '>=12.0.0'} 1096 + peerDependencies: 1097 + picomatch: ^3 || ^4 1098 + peerDependenciesMeta: 1099 + picomatch: 1100 + optional: true 1101 + 1102 + fill-range@7.1.1: 1103 + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 1104 + engines: {node: '>=8'} 1105 + 1106 + find-up@4.1.0: 1107 + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} 1108 + engines: {node: '>=8'} 1006 1109 1007 - /fast-json-stable-stringify@2.1.0: 1008 - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 1009 - dev: true 1110 + fs-extra@11.3.2: 1111 + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} 1112 + engines: {node: '>=14.14'} 1010 1113 1011 - /fs-extra@7.0.1: 1114 + fs-extra@7.0.1: 1012 1115 resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} 1013 1116 engines: {node: '>=6 <7 || >=8'} 1014 - dependencies: 1015 - graceful-fs: 4.2.11 1016 - jsonfile: 4.0.0 1017 - universalify: 0.1.2 1018 - dev: true 1117 + 1118 + fs-extra@8.1.0: 1119 + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} 1120 + engines: {node: '>=6 <7 || >=8'} 1019 1121 1020 - /fsevents@2.3.3: 1122 + fsevents@2.3.3: 1021 1123 resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 1022 1124 engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 1023 1125 os: [darwin] 1024 - requiresBuild: true 1025 - dev: true 1026 - optional: true 1027 1126 1028 - /function-bind@1.1.2: 1127 + function-bind@1.1.2: 1029 1128 resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 1030 - dev: true 1031 1129 1032 - /gensync@1.0.0-beta.2: 1130 + gensync@1.0.0-beta.2: 1033 1131 resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} 1034 1132 engines: {node: '>=6.9.0'} 1035 - dev: true 1036 1133 1037 - /globals@11.12.0: 1038 - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} 1039 - engines: {node: '>=4'} 1040 - dev: true 1134 + glob-parent@5.1.2: 1135 + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 1136 + engines: {node: '>= 6'} 1041 1137 1042 - /graceful-fs@4.2.11: 1043 - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 1044 - dev: true 1138 + globby@11.1.0: 1139 + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 1140 + engines: {node: '>=10'} 1045 1141 1046 - /has-flag@3.0.0: 1047 - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} 1048 - engines: {node: '>=4'} 1049 - dev: true 1142 + graceful-fs@4.2.11: 1143 + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 1050 1144 1051 - /has-flag@4.0.0: 1145 + has-flag@4.0.0: 1052 1146 resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 1053 1147 engines: {node: '>=8'} 1054 - dev: true 1055 1148 1056 - /hasown@2.0.2: 1149 + hasown@2.0.2: 1057 1150 resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 1058 1151 engines: {node: '>= 0.4'} 1059 - dependencies: 1060 - function-bind: 1.1.2 1061 - dev: true 1062 1152 1063 - /he@1.2.0: 1153 + he@1.2.0: 1064 1154 resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} 1065 1155 hasBin: true 1066 - dev: true 1156 + 1157 + human-id@4.1.3: 1158 + resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} 1159 + hasBin: true 1160 + 1161 + iconv-lite@0.7.0: 1162 + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} 1163 + engines: {node: '>=0.10.0'} 1164 + 1165 + ignore@5.3.2: 1166 + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 1167 + engines: {node: '>= 4'} 1067 1168 1068 - /import-lazy@4.0.0: 1169 + import-lazy@4.0.0: 1069 1170 resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} 1070 1171 engines: {node: '>=8'} 1071 - dev: true 1172 + 1173 + is-core-module@2.16.1: 1174 + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} 1175 + engines: {node: '>= 0.4'} 1176 + 1177 + is-extglob@2.1.1: 1178 + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 1179 + engines: {node: '>=0.10.0'} 1180 + 1181 + is-glob@4.0.3: 1182 + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 1183 + engines: {node: '>=0.10.0'} 1184 + 1185 + is-number@7.0.0: 1186 + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 1187 + engines: {node: '>=0.12.0'} 1188 + 1189 + is-reference@3.0.3: 1190 + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} 1191 + 1192 + is-subdir@1.2.0: 1193 + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} 1194 + engines: {node: '>=4'} 1195 + 1196 + is-windows@1.0.2: 1197 + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} 1198 + engines: {node: '>=0.10.0'} 1072 1199 1073 - /is-core-module@2.13.1: 1074 - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} 1075 - dependencies: 1076 - hasown: 2.0.2 1077 - dev: true 1200 + isexe@2.0.0: 1201 + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1078 1202 1079 - /jju@1.4.0: 1203 + jju@1.4.0: 1080 1204 resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} 1081 - dev: true 1082 1205 1083 - /js-tokens@4.0.0: 1206 + js-tokens@4.0.0: 1084 1207 resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 1085 - dev: true 1208 + 1209 + js-yaml@3.14.2: 1210 + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} 1211 + hasBin: true 1086 1212 1087 - /jsesc@2.5.2: 1088 - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} 1089 - engines: {node: '>=4'} 1213 + js-yaml@4.1.1: 1214 + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 1090 1215 hasBin: true 1091 - dev: true 1216 + 1217 + jsesc@3.1.0: 1218 + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} 1219 + engines: {node: '>=6'} 1220 + hasBin: true 1092 1221 1093 - /json-schema-traverse@0.4.1: 1094 - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 1095 - dev: true 1222 + json-schema-traverse@1.0.0: 1223 + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} 1096 1224 1097 - /json5@2.2.3: 1225 + json5@2.2.3: 1098 1226 resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} 1099 1227 engines: {node: '>=6'} 1100 1228 hasBin: true 1101 - dev: true 1102 1229 1103 - /jsonfile@4.0.0: 1230 + jsonfile@4.0.0: 1104 1231 resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} 1105 - optionalDependencies: 1106 - graceful-fs: 4.2.11 1107 - dev: true 1232 + 1233 + jsonfile@6.2.0: 1234 + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} 1108 1235 1109 - /kolorist@1.8.0: 1236 + kolorist@1.8.0: 1110 1237 resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} 1111 - dev: true 1112 1238 1113 - /lodash.get@4.4.2: 1114 - resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} 1115 - dev: true 1239 + local-pkg@1.1.2: 1240 + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} 1241 + engines: {node: '>=14'} 1116 1242 1117 - /lodash.isequal@4.5.0: 1118 - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} 1119 - dev: true 1243 + locate-character@3.0.0: 1244 + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} 1245 + 1246 + locate-path@5.0.0: 1247 + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} 1248 + engines: {node: '>=8'} 1249 + 1250 + lodash.startcase@4.4.0: 1251 + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} 1120 1252 1121 - /lodash@4.17.21: 1253 + lodash@4.17.21: 1122 1254 resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 1123 - dev: true 1124 1255 1125 - /lru-cache@5.1.1: 1256 + lru-cache@5.1.1: 1126 1257 resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} 1127 - dependencies: 1128 - yallist: 3.1.1 1129 - dev: true 1130 1258 1131 - /lru-cache@6.0.0: 1259 + lru-cache@6.0.0: 1132 1260 resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 1133 1261 engines: {node: '>=10'} 1134 - dependencies: 1135 - yallist: 4.0.0 1136 - dev: true 1137 1262 1138 - /magic-string@0.30.9: 1139 - resolution: {integrity: sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==} 1140 - engines: {node: '>=12'} 1141 - dependencies: 1142 - '@jridgewell/sourcemap-codec': 1.4.15 1143 - dev: true 1263 + magic-string@0.30.21: 1264 + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} 1265 + 1266 + merge2@1.4.1: 1267 + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 1268 + engines: {node: '>= 8'} 1269 + 1270 + micromatch@4.0.8: 1271 + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 1272 + engines: {node: '>=8.6'} 1144 1273 1145 - /minimatch@3.0.8: 1146 - resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} 1147 - dependencies: 1148 - brace-expansion: 1.1.11 1149 - dev: true 1274 + minimatch@10.0.3: 1275 + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} 1276 + engines: {node: 20 || >=22} 1150 1277 1151 - /minimatch@9.0.4: 1152 - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} 1278 + minimatch@9.0.5: 1279 + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 1153 1280 engines: {node: '>=16 || 14 >=14.17'} 1154 - dependencies: 1155 - brace-expansion: 2.0.1 1156 - dev: true 1157 1281 1158 - /ms@2.1.2: 1159 - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1160 - dev: true 1282 + mlly@1.8.0: 1283 + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} 1161 1284 1162 - /muggle-string@0.3.1: 1163 - resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} 1164 - dev: true 1285 + mri@1.2.0: 1286 + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} 1287 + engines: {node: '>=4'} 1288 + 1289 + ms@2.1.3: 1290 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1291 + 1292 + muggle-string@0.4.1: 1293 + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} 1165 1294 1166 - /nanoid@3.3.7: 1167 - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} 1295 + nanoid@3.3.11: 1296 + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 1168 1297 engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1169 1298 hasBin: true 1170 - dev: true 1171 1299 1172 - /node-releases@2.0.14: 1173 - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} 1174 - dev: true 1300 + node-html-parser@6.1.13: 1301 + resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} 1175 1302 1176 - /path-browserify@1.0.1: 1303 + node-releases@2.0.27: 1304 + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} 1305 + 1306 + nth-check@2.1.1: 1307 + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} 1308 + 1309 + outdent@0.5.0: 1310 + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} 1311 + 1312 + p-filter@2.1.0: 1313 + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} 1314 + engines: {node: '>=8'} 1315 + 1316 + p-limit@2.3.0: 1317 + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} 1318 + engines: {node: '>=6'} 1319 + 1320 + p-locate@4.1.0: 1321 + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} 1322 + engines: {node: '>=8'} 1323 + 1324 + p-map@2.1.0: 1325 + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} 1326 + engines: {node: '>=6'} 1327 + 1328 + p-try@2.2.0: 1329 + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} 1330 + engines: {node: '>=6'} 1331 + 1332 + package-manager-detector@0.2.11: 1333 + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} 1334 + 1335 + path-browserify@1.0.1: 1177 1336 resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} 1178 - dev: true 1179 1337 1180 - /path-parse@1.0.7: 1338 + path-exists@4.0.0: 1339 + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1340 + engines: {node: '>=8'} 1341 + 1342 + path-key@3.1.1: 1343 + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1344 + engines: {node: '>=8'} 1345 + 1346 + path-parse@1.0.7: 1181 1347 resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1182 - dev: true 1348 + 1349 + path-type@4.0.0: 1350 + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 1351 + engines: {node: '>=8'} 1352 + 1353 + pathe@2.0.3: 1354 + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 1183 1355 1184 - /picocolors@1.0.0: 1185 - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 1186 - dev: true 1356 + picocolors@1.1.1: 1357 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 1187 1358 1188 - /picomatch@2.3.1: 1359 + picomatch@2.3.1: 1189 1360 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1190 1361 engines: {node: '>=8.6'} 1191 - dev: true 1362 + 1363 + picomatch@4.0.3: 1364 + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 1365 + engines: {node: '>=12'} 1192 1366 1193 - /postcss-less@6.0.0: 1367 + pify@4.0.1: 1368 + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} 1369 + engines: {node: '>=6'} 1370 + 1371 + pkg-types@1.3.1: 1372 + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} 1373 + 1374 + pkg-types@2.3.0: 1375 + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} 1376 + 1377 + postcss-less@6.0.0: 1194 1378 resolution: {integrity: sha512-FPX16mQLyEjLzEuuJtxA8X3ejDLNGGEG503d2YGZR5Ask1SpDN8KmZUMpzCvyalWRywAn1n1VOA5dcqfCLo5rg==} 1195 1379 engines: {node: '>=12'} 1196 1380 peerDependencies: 1197 1381 postcss: ^8.3.5 1198 - dev: true 1199 1382 1200 - /postcss-scss@4.0.9: 1383 + postcss-scss@4.0.9: 1201 1384 resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} 1202 1385 engines: {node: '>=12.0'} 1203 1386 peerDependencies: 1204 1387 postcss: ^8.4.29 1205 - dev: true 1206 1388 1207 - /postcss@8.4.38: 1208 - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} 1389 + postcss@8.5.6: 1390 + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 1209 1391 engines: {node: ^10 || ^12 || >=14} 1210 - dependencies: 1211 - nanoid: 3.3.7 1212 - picocolors: 1.0.0 1213 - source-map-js: 1.2.0 1214 - dev: true 1392 + 1393 + preact@10.28.0: 1394 + resolution: {integrity: sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==} 1215 1395 1216 - /prettier-plugin-css-order@2.1.2(prettier@3.2.5): 1396 + prettier-plugin-css-order@2.1.2: 1217 1397 resolution: {integrity: sha512-vomxPjHI6pOMYcBuouSJHxxQClJXaUpU9rsV9IAO2wrSTZILRRlrxAAR8t9UF6wtczLkLfNRFUwM+ZbGXOONUA==} 1218 1398 engines: {node: '>=16'} 1219 1399 peerDependencies: 1220 1400 prettier: 3.x 1221 - dependencies: 1222 - css-declaration-sorter: 7.2.0 1223 - postcss-less: 6.0.0 1224 - postcss-scss: 4.0.9 1225 - prettier: 3.2.5 1226 - transitivePeerDependencies: 1227 - - postcss 1228 - dev: true 1401 + 1402 + prettier-plugin-svelte@3.4.0: 1403 + resolution: {integrity: sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==} 1404 + peerDependencies: 1405 + prettier: ^3.0.0 1406 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 1407 + 1408 + prettier@2.8.8: 1409 + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} 1410 + engines: {node: '>=10.13.0'} 1411 + hasBin: true 1229 1412 1230 - /prettier@3.2.5: 1231 - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} 1413 + prettier@3.7.4: 1414 + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} 1232 1415 engines: {node: '>=14'} 1233 1416 hasBin: true 1234 - dev: true 1235 1417 1236 - /punycode@2.3.1: 1418 + punycode@2.3.1: 1237 1419 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1238 1420 engines: {node: '>=6'} 1239 - dev: true 1421 + 1422 + quansync@0.2.11: 1423 + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} 1424 + 1425 + queue-microtask@1.2.3: 1426 + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1427 + 1428 + read-yaml-file@1.1.0: 1429 + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} 1430 + engines: {node: '>=6'} 1431 + 1432 + readdirp@4.1.2: 1433 + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} 1434 + engines: {node: '>= 14.18.0'} 1435 + 1436 + require-from-string@2.0.2: 1437 + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} 1438 + engines: {node: '>=0.10.0'} 1240 1439 1241 - /resolve@1.19.0: 1242 - resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} 1243 - dependencies: 1244 - is-core-module: 2.13.1 1245 - path-parse: 1.0.7 1246 - dev: true 1440 + resolve-from@5.0.0: 1441 + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 1442 + engines: {node: '>=8'} 1247 1443 1248 - /resolve@1.22.8: 1249 - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} 1444 + resolve@1.22.11: 1445 + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} 1446 + engines: {node: '>= 0.4'} 1250 1447 hasBin: true 1251 - dependencies: 1252 - is-core-module: 2.13.1 1253 - path-parse: 1.0.7 1254 - supports-preserve-symlinks-flag: 1.0.0 1255 - dev: true 1448 + 1449 + reusify@1.1.0: 1450 + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 1451 + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1256 1452 1257 - /rollup@4.14.2: 1258 - resolution: {integrity: sha512-WkeoTWvuBoFjFAhsEOHKRoZ3r9GfTyhh7Vff1zwebEFLEFjT1lG3784xEgKiTa7E+e70vsC81roVL2MP4tgEEQ==} 1453 + rollup@4.53.3: 1454 + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} 1259 1455 engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1260 1456 hasBin: true 1261 - dependencies: 1262 - '@types/estree': 1.0.5 1263 - optionalDependencies: 1264 - '@rollup/rollup-android-arm-eabi': 4.14.2 1265 - '@rollup/rollup-android-arm64': 4.14.2 1266 - '@rollup/rollup-darwin-arm64': 4.14.2 1267 - '@rollup/rollup-darwin-x64': 4.14.2 1268 - '@rollup/rollup-linux-arm-gnueabihf': 4.14.2 1269 - '@rollup/rollup-linux-arm64-gnu': 4.14.2 1270 - '@rollup/rollup-linux-arm64-musl': 4.14.2 1271 - '@rollup/rollup-linux-powerpc64le-gnu': 4.14.2 1272 - '@rollup/rollup-linux-riscv64-gnu': 4.14.2 1273 - '@rollup/rollup-linux-s390x-gnu': 4.14.2 1274 - '@rollup/rollup-linux-x64-gnu': 4.14.2 1275 - '@rollup/rollup-linux-x64-musl': 4.14.2 1276 - '@rollup/rollup-win32-arm64-msvc': 4.14.2 1277 - '@rollup/rollup-win32-ia32-msvc': 4.14.2 1278 - '@rollup/rollup-win32-x64-msvc': 4.14.2 1279 - fsevents: 2.3.3 1280 - dev: true 1457 + 1458 + run-parallel@1.2.0: 1459 + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1460 + 1461 + sade@1.8.1: 1462 + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} 1463 + engines: {node: '>=6'} 1464 + 1465 + safer-buffer@2.1.2: 1466 + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 1281 1467 1282 - /semver@6.3.1: 1468 + semver@6.3.1: 1283 1469 resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1284 1470 hasBin: true 1285 - dev: true 1286 1471 1287 - /semver@7.5.4: 1472 + semver@7.5.4: 1288 1473 resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 1289 1474 engines: {node: '>=10'} 1290 1475 hasBin: true 1291 - dependencies: 1292 - lru-cache: 6.0.0 1293 - dev: true 1294 1476 1295 - /semver@7.6.0: 1296 - resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} 1477 + semver@7.7.3: 1478 + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} 1297 1479 engines: {node: '>=10'} 1298 1480 hasBin: true 1299 - dependencies: 1300 - lru-cache: 6.0.0 1301 - dev: true 1481 + 1482 + shebang-command@2.0.0: 1483 + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1484 + engines: {node: '>=8'} 1485 + 1486 + shebang-regex@3.0.0: 1487 + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1488 + engines: {node: '>=8'} 1489 + 1490 + signal-exit@4.1.0: 1491 + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 1492 + engines: {node: '>=14'} 1493 + 1494 + simple-code-frame@1.3.0: 1495 + resolution: {integrity: sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==} 1496 + 1497 + slash@3.0.0: 1498 + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1499 + engines: {node: '>=8'} 1302 1500 1303 - /source-map-js@1.2.0: 1304 - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} 1501 + source-map-js@1.2.1: 1502 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1305 1503 engines: {node: '>=0.10.0'} 1306 - dev: true 1504 + 1505 + source-map-support@0.5.21: 1506 + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} 1307 1507 1308 - /source-map@0.6.1: 1508 + source-map@0.6.1: 1309 1509 resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 1310 1510 engines: {node: '>=0.10.0'} 1311 - dev: true 1511 + 1512 + source-map@0.7.6: 1513 + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} 1514 + engines: {node: '>= 12'} 1312 1515 1313 - /sprintf-js@1.0.3: 1516 + spawndamnit@3.0.1: 1517 + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} 1518 + 1519 + sprintf-js@1.0.3: 1314 1520 resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} 1315 - dev: true 1521 + 1522 + stack-trace@1.0.0-pre2: 1523 + resolution: {integrity: sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==} 1524 + engines: {node: '>=16'} 1316 1525 1317 - /string-argv@0.3.2: 1526 + string-argv@0.3.2: 1318 1527 resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} 1319 1528 engines: {node: '>=0.6.19'} 1320 - dev: true 1321 1529 1322 - /strip-json-comments@3.1.1: 1323 - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1530 + strip-ansi@6.0.1: 1531 + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1324 1532 engines: {node: '>=8'} 1325 - dev: true 1326 1533 1327 - /supports-color@5.5.0: 1328 - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} 1534 + strip-bom@3.0.0: 1535 + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} 1329 1536 engines: {node: '>=4'} 1330 - dependencies: 1331 - has-flag: 3.0.0 1332 - dev: true 1333 1537 1334 - /supports-color@8.1.1: 1538 + strip-json-comments@3.1.1: 1539 + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1540 + engines: {node: '>=8'} 1541 + 1542 + supports-color@8.1.1: 1335 1543 resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} 1336 1544 engines: {node: '>=10'} 1337 - dependencies: 1338 - has-flag: 4.0.0 1339 - dev: true 1340 1545 1341 - /supports-preserve-symlinks-flag@1.0.0: 1546 + supports-preserve-symlinks-flag@1.0.0: 1342 1547 resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1343 1548 engines: {node: '>= 0.4'} 1344 - dev: true 1549 + 1550 + svelte-check@4.3.4: 1551 + resolution: {integrity: sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==} 1552 + engines: {node: '>= 18.0.0'} 1553 + hasBin: true 1554 + peerDependencies: 1555 + svelte: ^4.0.0 || ^5.0.0-next.0 1556 + typescript: '>=5.0.0' 1345 1557 1346 - /to-fast-properties@2.0.0: 1347 - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} 1348 - engines: {node: '>=4'} 1349 - dev: true 1558 + svelte@5.45.5: 1559 + resolution: {integrity: sha512-2074U+vObO5Zs8/qhxtBwdi6ZXNIhEBTzNmUFjiZexLxTdt9vq96D/0pnQELl6YcpLMD7pZ2dhXKByfGS8SAdg==} 1560 + engines: {node: '>=18'} 1350 1561 1351 - /typescript@5.4.2: 1352 - resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} 1562 + term-size@2.2.1: 1563 + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} 1564 + engines: {node: '>=8'} 1565 + 1566 + terser@5.44.1: 1567 + resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} 1568 + engines: {node: '>=10'} 1569 + hasBin: true 1570 + 1571 + tinyglobby@0.2.15: 1572 + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} 1573 + engines: {node: '>=12.0.0'} 1574 + 1575 + to-regex-range@5.0.1: 1576 + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1577 + engines: {node: '>=8.0'} 1578 + 1579 + tslib@2.8.1: 1580 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1581 + 1582 + typescript@5.8.2: 1583 + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} 1353 1584 engines: {node: '>=14.17'} 1354 1585 hasBin: true 1355 - dev: true 1356 1586 1357 - /typescript@5.4.5: 1358 - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} 1587 + typescript@5.8.3: 1588 + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 1359 1589 engines: {node: '>=14.17'} 1360 1590 hasBin: true 1361 - dev: true 1591 + 1592 + ufo@1.6.1: 1593 + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} 1362 1594 1363 - /universalify@0.1.2: 1595 + undici-types@7.16.0: 1596 + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} 1597 + 1598 + universalify@0.1.2: 1364 1599 resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} 1365 1600 engines: {node: '>= 4.0.0'} 1366 - dev: true 1601 + 1602 + universalify@2.0.1: 1603 + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} 1604 + engines: {node: '>= 10.0.0'} 1367 1605 1368 - /update-browserslist-db@1.0.13(browserslist@4.23.0): 1369 - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} 1606 + update-browserslist-db@1.2.1: 1607 + resolution: {integrity: sha512-R9NcHbbZ45RoWfTdhn1J9SS7zxNvlddv4YRrHTUaFdtjbmfncfedB45EC9IaqJQ97iAR1GZgOfyRQO+ExIF6EQ==} 1370 1608 hasBin: true 1371 1609 peerDependencies: 1372 1610 browserslist: '>= 4.21.0' 1373 - dependencies: 1374 - browserslist: 4.23.0 1375 - escalade: 3.1.2 1376 - picocolors: 1.0.0 1377 - dev: true 1378 1611 1379 - /uri-js@4.4.1: 1612 + uri-js@4.4.1: 1380 1613 resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1381 - dependencies: 1382 - punycode: 2.3.1 1383 - dev: true 1384 1614 1385 - /validator@13.11.0: 1386 - resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==} 1387 - engines: {node: '>= 0.10'} 1388 - dev: true 1389 - 1390 - /vite-plugin-dts@3.8.1(typescript@5.4.5)(vite@5.2.8): 1391 - resolution: {integrity: sha512-zEYyQxH7lKto1VTKZHF3ZZeOPkkJgnMrePY4VxDHfDSvDjmYMMfWjZxYmNwW8QxbaItWJQhhXY+geAbyNphI7g==} 1392 - engines: {node: ^14.18.0 || >=16.0.0} 1615 + vite-plugin-dts@4.5.4: 1616 + resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} 1393 1617 peerDependencies: 1394 1618 typescript: '*' 1395 1619 vite: '*' 1396 1620 peerDependenciesMeta: 1397 1621 vite: 1398 1622 optional: true 1399 - dependencies: 1400 - '@microsoft/api-extractor': 7.43.0 1401 - '@rollup/pluginutils': 5.1.0 1402 - '@vue/language-core': 1.8.27(typescript@5.4.5) 1403 - debug: 4.3.4 1404 - kolorist: 1.8.0 1405 - magic-string: 0.30.9 1406 - typescript: 5.4.5 1407 - vite: 5.2.8 1408 - vue-tsc: 1.8.27(typescript@5.4.5) 1409 - transitivePeerDependencies: 1410 - - '@types/node' 1411 - - rollup 1412 - - supports-color 1413 - dev: true 1623 + 1624 + vite-prerender-plugin@0.5.12: 1625 + resolution: {integrity: sha512-EiwhbMn+flg14EysbLTmZSzq8NGTxhytgK3bf4aGRF1evWLGwZiHiUJ1KZDvbxgKbMf2pG6fJWGEa3UZXOnR1g==} 1626 + peerDependencies: 1627 + vite: 5.x || 6.x || 7.x 1414 1628 1415 - /vite@5.2.8: 1416 - resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} 1417 - engines: {node: ^18.0.0 || >=20.0.0} 1629 + vite@7.2.6: 1630 + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} 1631 + engines: {node: ^20.19.0 || >=22.12.0} 1418 1632 hasBin: true 1419 1633 peerDependencies: 1420 - '@types/node': ^18.0.0 || >=20.0.0 1421 - less: '*' 1634 + '@types/node': ^20.19.0 || >=22.12.0 1635 + jiti: '>=1.21.0' 1636 + less: ^4.0.0 1422 1637 lightningcss: ^1.21.0 1423 - sass: '*' 1424 - stylus: '*' 1425 - sugarss: '*' 1426 - terser: ^5.4.0 1638 + sass: ^1.70.0 1639 + sass-embedded: ^1.70.0 1640 + stylus: '>=0.54.8' 1641 + sugarss: ^5.0.0 1642 + terser: ^5.16.0 1643 + tsx: ^4.8.1 1644 + yaml: ^2.4.2 1427 1645 peerDependenciesMeta: 1428 1646 '@types/node': 1647 + optional: true 1648 + jiti: 1429 1649 optional: true 1430 1650 less: 1431 1651 optional: true ··· 1433 1653 optional: true 1434 1654 sass: 1435 1655 optional: true 1656 + sass-embedded: 1657 + optional: true 1436 1658 stylus: 1437 1659 optional: true 1438 1660 sugarss: 1439 1661 optional: true 1440 1662 terser: 1441 1663 optional: true 1664 + tsx: 1665 + optional: true 1666 + yaml: 1667 + optional: true 1668 + 1669 + vitefu@1.1.1: 1670 + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} 1671 + peerDependencies: 1672 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 1673 + peerDependenciesMeta: 1674 + vite: 1675 + optional: true 1676 + 1677 + vscode-uri@3.1.0: 1678 + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} 1679 + 1680 + which@2.0.2: 1681 + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1682 + engines: {node: '>= 8'} 1683 + hasBin: true 1684 + 1685 + yallist@3.1.1: 1686 + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} 1687 + 1688 + yallist@4.0.0: 1689 + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1690 + 1691 + zimmerframe@1.1.4: 1692 + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} 1693 + 1694 + snapshots: 1695 + 1696 + '@atcute/atproto@3.1.9': 1442 1697 dependencies: 1443 - esbuild: 0.20.2 1444 - postcss: 8.4.38 1445 - rollup: 4.14.2 1698 + '@atcute/lexicons': 1.2.5 1699 + 1700 + '@atcute/bluesky-richtext-parser@1.0.7': {} 1701 + 1702 + '@atcute/bluesky-richtext-segmenter@2.0.4': 1703 + dependencies: 1704 + '@atcute/bluesky': 3.2.11 1705 + '@atcute/lexicons': 1.2.5 1706 + 1707 + '@atcute/bluesky@2.1.1(@atcute/client@3.1.0)': 1708 + dependencies: 1709 + '@atcute/client': 3.1.0 1710 + 1711 + '@atcute/bluesky@3.2.11': 1712 + dependencies: 1713 + '@atcute/atproto': 3.1.9 1714 + '@atcute/lexicons': 1.2.5 1715 + 1716 + '@atcute/client@3.1.0': {} 1717 + 1718 + '@atcute/lexicons@1.2.5': 1719 + dependencies: 1720 + '@standard-schema/spec': 1.0.0 1721 + esm-env: 1.2.2 1722 + 1723 + '@babel/code-frame@7.27.1': 1724 + dependencies: 1725 + '@babel/helper-validator-identifier': 7.28.5 1726 + js-tokens: 4.0.0 1727 + picocolors: 1.1.1 1728 + 1729 + '@babel/compat-data@7.28.5': {} 1730 + 1731 + '@babel/core@7.28.5': 1732 + dependencies: 1733 + '@babel/code-frame': 7.27.1 1734 + '@babel/generator': 7.28.5 1735 + '@babel/helper-compilation-targets': 7.27.2 1736 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) 1737 + '@babel/helpers': 7.28.4 1738 + '@babel/parser': 7.28.5 1739 + '@babel/template': 7.27.2 1740 + '@babel/traverse': 7.28.5 1741 + '@babel/types': 7.28.5 1742 + '@jridgewell/remapping': 2.3.5 1743 + convert-source-map: 2.0.0 1744 + debug: 4.4.3 1745 + gensync: 1.0.0-beta.2 1746 + json5: 2.2.3 1747 + semver: 6.3.1 1748 + transitivePeerDependencies: 1749 + - supports-color 1750 + 1751 + '@babel/generator@7.28.5': 1752 + dependencies: 1753 + '@babel/parser': 7.28.5 1754 + '@babel/types': 7.28.5 1755 + '@jridgewell/gen-mapping': 0.3.13 1756 + '@jridgewell/trace-mapping': 0.3.31 1757 + jsesc: 3.1.0 1758 + 1759 + '@babel/helper-annotate-as-pure@7.27.3': 1760 + dependencies: 1761 + '@babel/types': 7.28.5 1762 + 1763 + '@babel/helper-compilation-targets@7.27.2': 1764 + dependencies: 1765 + '@babel/compat-data': 7.28.5 1766 + '@babel/helper-validator-option': 7.27.1 1767 + browserslist: 4.28.1 1768 + lru-cache: 5.1.1 1769 + semver: 6.3.1 1770 + 1771 + '@babel/helper-globals@7.28.0': {} 1772 + 1773 + '@babel/helper-module-imports@7.27.1': 1774 + dependencies: 1775 + '@babel/traverse': 7.28.5 1776 + '@babel/types': 7.28.5 1777 + transitivePeerDependencies: 1778 + - supports-color 1779 + 1780 + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': 1781 + dependencies: 1782 + '@babel/core': 7.28.5 1783 + '@babel/helper-module-imports': 7.27.1 1784 + '@babel/helper-validator-identifier': 7.28.5 1785 + '@babel/traverse': 7.28.5 1786 + transitivePeerDependencies: 1787 + - supports-color 1788 + 1789 + '@babel/helper-plugin-utils@7.27.1': {} 1790 + 1791 + '@babel/helper-string-parser@7.27.1': {} 1792 + 1793 + '@babel/helper-validator-identifier@7.28.5': {} 1794 + 1795 + '@babel/helper-validator-option@7.27.1': {} 1796 + 1797 + '@babel/helpers@7.28.4': 1798 + dependencies: 1799 + '@babel/template': 7.27.2 1800 + '@babel/types': 7.28.5 1801 + 1802 + '@babel/parser@7.28.5': 1803 + dependencies: 1804 + '@babel/types': 7.28.5 1805 + 1806 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': 1807 + dependencies: 1808 + '@babel/core': 7.28.5 1809 + '@babel/helper-plugin-utils': 7.27.1 1810 + 1811 + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.5)': 1812 + dependencies: 1813 + '@babel/core': 7.28.5 1814 + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) 1815 + transitivePeerDependencies: 1816 + - supports-color 1817 + 1818 + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5)': 1819 + dependencies: 1820 + '@babel/core': 7.28.5 1821 + '@babel/helper-annotate-as-pure': 7.27.3 1822 + '@babel/helper-module-imports': 7.27.1 1823 + '@babel/helper-plugin-utils': 7.27.1 1824 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) 1825 + '@babel/types': 7.28.5 1826 + transitivePeerDependencies: 1827 + - supports-color 1828 + 1829 + '@babel/runtime@7.28.4': {} 1830 + 1831 + '@babel/template@7.27.2': 1832 + dependencies: 1833 + '@babel/code-frame': 7.27.1 1834 + '@babel/parser': 7.28.5 1835 + '@babel/types': 7.28.5 1836 + 1837 + '@babel/traverse@7.28.5': 1838 + dependencies: 1839 + '@babel/code-frame': 7.27.1 1840 + '@babel/generator': 7.28.5 1841 + '@babel/helper-globals': 7.28.0 1842 + '@babel/parser': 7.28.5 1843 + '@babel/template': 7.27.2 1844 + '@babel/types': 7.28.5 1845 + debug: 4.4.3 1846 + transitivePeerDependencies: 1847 + - supports-color 1848 + 1849 + '@babel/types@7.28.5': 1850 + dependencies: 1851 + '@babel/helper-string-parser': 7.27.1 1852 + '@babel/helper-validator-identifier': 7.28.5 1853 + 1854 + '@changesets/apply-release-plan@7.0.14': 1855 + dependencies: 1856 + '@changesets/config': 3.1.2 1857 + '@changesets/get-version-range-type': 0.4.0 1858 + '@changesets/git': 3.0.4 1859 + '@changesets/should-skip-package': 0.1.2 1860 + '@changesets/types': 6.1.0 1861 + '@manypkg/get-packages': 1.1.3 1862 + detect-indent: 6.1.0 1863 + fs-extra: 7.0.1 1864 + lodash.startcase: 4.4.0 1865 + outdent: 0.5.0 1866 + prettier: 2.8.8 1867 + resolve-from: 5.0.0 1868 + semver: 7.7.3 1869 + 1870 + '@changesets/assemble-release-plan@6.0.9': 1871 + dependencies: 1872 + '@changesets/errors': 0.2.0 1873 + '@changesets/get-dependents-graph': 2.1.3 1874 + '@changesets/should-skip-package': 0.1.2 1875 + '@changesets/types': 6.1.0 1876 + '@manypkg/get-packages': 1.1.3 1877 + semver: 7.7.3 1878 + 1879 + '@changesets/changelog-git@0.2.1': 1880 + dependencies: 1881 + '@changesets/types': 6.1.0 1882 + 1883 + '@changesets/cli@2.29.8(@types/node@24.10.1)': 1884 + dependencies: 1885 + '@changesets/apply-release-plan': 7.0.14 1886 + '@changesets/assemble-release-plan': 6.0.9 1887 + '@changesets/changelog-git': 0.2.1 1888 + '@changesets/config': 3.1.2 1889 + '@changesets/errors': 0.2.0 1890 + '@changesets/get-dependents-graph': 2.1.3 1891 + '@changesets/get-release-plan': 4.0.14 1892 + '@changesets/git': 3.0.4 1893 + '@changesets/logger': 0.1.1 1894 + '@changesets/pre': 2.0.2 1895 + '@changesets/read': 0.6.6 1896 + '@changesets/should-skip-package': 0.1.2 1897 + '@changesets/types': 6.1.0 1898 + '@changesets/write': 0.4.0 1899 + '@inquirer/external-editor': 1.0.3(@types/node@24.10.1) 1900 + '@manypkg/get-packages': 1.1.3 1901 + ansi-colors: 4.1.3 1902 + ci-info: 3.9.0 1903 + enquirer: 2.4.1 1904 + fs-extra: 7.0.1 1905 + mri: 1.2.0 1906 + p-limit: 2.3.0 1907 + package-manager-detector: 0.2.11 1908 + picocolors: 1.1.1 1909 + resolve-from: 5.0.0 1910 + semver: 7.7.3 1911 + spawndamnit: 3.0.1 1912 + term-size: 2.2.1 1913 + transitivePeerDependencies: 1914 + - '@types/node' 1915 + 1916 + '@changesets/config@3.1.2': 1917 + dependencies: 1918 + '@changesets/errors': 0.2.0 1919 + '@changesets/get-dependents-graph': 2.1.3 1920 + '@changesets/logger': 0.1.1 1921 + '@changesets/types': 6.1.0 1922 + '@manypkg/get-packages': 1.1.3 1923 + fs-extra: 7.0.1 1924 + micromatch: 4.0.8 1925 + 1926 + '@changesets/errors@0.2.0': 1927 + dependencies: 1928 + extendable-error: 0.1.7 1929 + 1930 + '@changesets/get-dependents-graph@2.1.3': 1931 + dependencies: 1932 + '@changesets/types': 6.1.0 1933 + '@manypkg/get-packages': 1.1.3 1934 + picocolors: 1.1.1 1935 + semver: 7.7.3 1936 + 1937 + '@changesets/get-release-plan@4.0.14': 1938 + dependencies: 1939 + '@changesets/assemble-release-plan': 6.0.9 1940 + '@changesets/config': 3.1.2 1941 + '@changesets/pre': 2.0.2 1942 + '@changesets/read': 0.6.6 1943 + '@changesets/types': 6.1.0 1944 + '@manypkg/get-packages': 1.1.3 1945 + 1946 + '@changesets/get-version-range-type@0.4.0': {} 1947 + 1948 + '@changesets/git@3.0.4': 1949 + dependencies: 1950 + '@changesets/errors': 0.2.0 1951 + '@manypkg/get-packages': 1.1.3 1952 + is-subdir: 1.2.0 1953 + micromatch: 4.0.8 1954 + spawndamnit: 3.0.1 1955 + 1956 + '@changesets/logger@0.1.1': 1957 + dependencies: 1958 + picocolors: 1.1.1 1959 + 1960 + '@changesets/parse@0.4.2': 1961 + dependencies: 1962 + '@changesets/types': 6.1.0 1963 + js-yaml: 4.1.1 1964 + 1965 + '@changesets/pre@2.0.2': 1966 + dependencies: 1967 + '@changesets/errors': 0.2.0 1968 + '@changesets/types': 6.1.0 1969 + '@manypkg/get-packages': 1.1.3 1970 + fs-extra: 7.0.1 1971 + 1972 + '@changesets/read@0.6.6': 1973 + dependencies: 1974 + '@changesets/git': 3.0.4 1975 + '@changesets/logger': 0.1.1 1976 + '@changesets/parse': 0.4.2 1977 + '@changesets/types': 6.1.0 1978 + fs-extra: 7.0.1 1979 + p-filter: 2.1.0 1980 + picocolors: 1.1.1 1981 + 1982 + '@changesets/should-skip-package@0.1.2': 1983 + dependencies: 1984 + '@changesets/types': 6.1.0 1985 + '@manypkg/get-packages': 1.1.3 1986 + 1987 + '@changesets/types@4.1.0': {} 1988 + 1989 + '@changesets/types@6.1.0': {} 1990 + 1991 + '@changesets/write@0.4.0': 1992 + dependencies: 1993 + '@changesets/types': 6.1.0 1994 + fs-extra: 7.0.1 1995 + human-id: 4.1.3 1996 + prettier: 2.8.8 1997 + 1998 + '@esbuild/aix-ppc64@0.25.12': 1999 + optional: true 2000 + 2001 + '@esbuild/android-arm64@0.25.12': 2002 + optional: true 2003 + 2004 + '@esbuild/android-arm@0.25.12': 2005 + optional: true 2006 + 2007 + '@esbuild/android-x64@0.25.12': 2008 + optional: true 2009 + 2010 + '@esbuild/darwin-arm64@0.25.12': 2011 + optional: true 2012 + 2013 + '@esbuild/darwin-x64@0.25.12': 2014 + optional: true 2015 + 2016 + '@esbuild/freebsd-arm64@0.25.12': 2017 + optional: true 2018 + 2019 + '@esbuild/freebsd-x64@0.25.12': 2020 + optional: true 2021 + 2022 + '@esbuild/linux-arm64@0.25.12': 2023 + optional: true 2024 + 2025 + '@esbuild/linux-arm@0.25.12': 2026 + optional: true 2027 + 2028 + '@esbuild/linux-ia32@0.25.12': 2029 + optional: true 2030 + 2031 + '@esbuild/linux-loong64@0.25.12': 2032 + optional: true 2033 + 2034 + '@esbuild/linux-mips64el@0.25.12': 2035 + optional: true 2036 + 2037 + '@esbuild/linux-ppc64@0.25.12': 2038 + optional: true 2039 + 2040 + '@esbuild/linux-riscv64@0.25.12': 2041 + optional: true 2042 + 2043 + '@esbuild/linux-s390x@0.25.12': 2044 + optional: true 2045 + 2046 + '@esbuild/linux-x64@0.25.12': 2047 + optional: true 2048 + 2049 + '@esbuild/netbsd-arm64@0.25.12': 2050 + optional: true 2051 + 2052 + '@esbuild/netbsd-x64@0.25.12': 2053 + optional: true 2054 + 2055 + '@esbuild/openbsd-arm64@0.25.12': 2056 + optional: true 2057 + 2058 + '@esbuild/openbsd-x64@0.25.12': 2059 + optional: true 2060 + 2061 + '@esbuild/openharmony-arm64@0.25.12': 2062 + optional: true 2063 + 2064 + '@esbuild/sunos-x64@0.25.12': 2065 + optional: true 2066 + 2067 + '@esbuild/win32-arm64@0.25.12': 2068 + optional: true 2069 + 2070 + '@esbuild/win32-ia32@0.25.12': 2071 + optional: true 2072 + 2073 + '@esbuild/win32-x64@0.25.12': 2074 + optional: true 2075 + 2076 + '@inquirer/external-editor@1.0.3(@types/node@24.10.1)': 2077 + dependencies: 2078 + chardet: 2.1.1 2079 + iconv-lite: 0.7.0 2080 + optionalDependencies: 2081 + '@types/node': 24.10.1 2082 + 2083 + '@isaacs/balanced-match@4.0.1': {} 2084 + 2085 + '@isaacs/brace-expansion@5.0.0': 2086 + dependencies: 2087 + '@isaacs/balanced-match': 4.0.1 2088 + 2089 + '@jridgewell/gen-mapping@0.3.13': 2090 + dependencies: 2091 + '@jridgewell/sourcemap-codec': 1.5.5 2092 + '@jridgewell/trace-mapping': 0.3.31 2093 + 2094 + '@jridgewell/remapping@2.3.5': 2095 + dependencies: 2096 + '@jridgewell/gen-mapping': 0.3.13 2097 + '@jridgewell/trace-mapping': 0.3.31 2098 + 2099 + '@jridgewell/resolve-uri@3.1.2': {} 2100 + 2101 + '@jridgewell/source-map@0.3.11': 2102 + dependencies: 2103 + '@jridgewell/gen-mapping': 0.3.13 2104 + '@jridgewell/trace-mapping': 0.3.31 2105 + 2106 + '@jridgewell/sourcemap-codec@1.5.5': {} 2107 + 2108 + '@jridgewell/trace-mapping@0.3.31': 2109 + dependencies: 2110 + '@jridgewell/resolve-uri': 3.1.2 2111 + '@jridgewell/sourcemap-codec': 1.5.5 2112 + 2113 + '@manypkg/find-root@1.1.0': 2114 + dependencies: 2115 + '@babel/runtime': 7.28.4 2116 + '@types/node': 12.20.55 2117 + find-up: 4.1.0 2118 + fs-extra: 8.1.0 2119 + 2120 + '@manypkg/get-packages@1.1.3': 2121 + dependencies: 2122 + '@babel/runtime': 7.28.4 2123 + '@changesets/types': 4.1.0 2124 + '@manypkg/find-root': 1.1.0 2125 + fs-extra: 8.1.0 2126 + globby: 11.1.0 2127 + read-yaml-file: 1.1.0 2128 + 2129 + '@microsoft/api-extractor-model@7.32.1(@types/node@24.10.1)': 2130 + dependencies: 2131 + '@microsoft/tsdoc': 0.16.0 2132 + '@microsoft/tsdoc-config': 0.18.0 2133 + '@rushstack/node-core-library': 5.19.0(@types/node@24.10.1) 2134 + transitivePeerDependencies: 2135 + - '@types/node' 2136 + 2137 + '@microsoft/api-extractor@7.55.1(@types/node@24.10.1)': 2138 + dependencies: 2139 + '@microsoft/api-extractor-model': 7.32.1(@types/node@24.10.1) 2140 + '@microsoft/tsdoc': 0.16.0 2141 + '@microsoft/tsdoc-config': 0.18.0 2142 + '@rushstack/node-core-library': 5.19.0(@types/node@24.10.1) 2143 + '@rushstack/rig-package': 0.6.0 2144 + '@rushstack/terminal': 0.19.4(@types/node@24.10.1) 2145 + '@rushstack/ts-command-line': 5.1.4(@types/node@24.10.1) 2146 + diff: 8.0.2 2147 + lodash: 4.17.21 2148 + minimatch: 10.0.3 2149 + resolve: 1.22.11 2150 + semver: 7.5.4 2151 + source-map: 0.6.1 2152 + typescript: 5.8.2 2153 + transitivePeerDependencies: 2154 + - '@types/node' 2155 + 2156 + '@microsoft/tsdoc-config@0.18.0': 2157 + dependencies: 2158 + '@microsoft/tsdoc': 0.16.0 2159 + ajv: 8.12.0 2160 + jju: 1.4.0 2161 + resolve: 1.22.11 2162 + 2163 + '@microsoft/tsdoc@0.16.0': {} 2164 + 2165 + '@nodelib/fs.scandir@2.1.5': 2166 + dependencies: 2167 + '@nodelib/fs.stat': 2.0.5 2168 + run-parallel: 1.2.0 2169 + 2170 + '@nodelib/fs.stat@2.0.5': {} 2171 + 2172 + '@nodelib/fs.walk@1.2.8': 2173 + dependencies: 2174 + '@nodelib/fs.scandir': 2.1.5 2175 + fastq: 1.19.1 2176 + 2177 + '@preact/preset-vite@2.10.2(@babel/core@7.28.5)(preact@10.28.0)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1))': 2178 + dependencies: 2179 + '@babel/core': 7.28.5 2180 + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5) 2181 + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.5) 2182 + '@prefresh/vite': 2.4.11(preact@10.28.0)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 2183 + '@rollup/pluginutils': 4.2.1 2184 + babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.28.5) 2185 + debug: 4.4.3 2186 + picocolors: 1.1.1 2187 + vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 2188 + vite-prerender-plugin: 0.5.12(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 2189 + transitivePeerDependencies: 2190 + - preact 2191 + - supports-color 2192 + 2193 + '@prefresh/babel-plugin@0.5.2': {} 2194 + 2195 + '@prefresh/core@1.5.9(preact@10.28.0)': 2196 + dependencies: 2197 + preact: 10.28.0 2198 + 2199 + '@prefresh/utils@1.2.1': {} 2200 + 2201 + '@prefresh/vite@2.4.11(preact@10.28.0)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1))': 2202 + dependencies: 2203 + '@babel/core': 7.28.5 2204 + '@prefresh/babel-plugin': 0.5.2 2205 + '@prefresh/core': 1.5.9(preact@10.28.0) 2206 + '@prefresh/utils': 1.2.1 2207 + '@rollup/pluginutils': 4.2.1 2208 + preact: 10.28.0 2209 + vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 2210 + transitivePeerDependencies: 2211 + - supports-color 2212 + 2213 + '@rollup/pluginutils@4.2.1': 2214 + dependencies: 2215 + estree-walker: 2.0.2 2216 + picomatch: 2.3.1 2217 + 2218 + '@rollup/pluginutils@5.3.0(rollup@4.53.3)': 2219 + dependencies: 2220 + '@types/estree': 1.0.8 2221 + estree-walker: 2.0.2 2222 + picomatch: 4.0.3 2223 + optionalDependencies: 2224 + rollup: 4.53.3 2225 + 2226 + '@rollup/rollup-android-arm-eabi@4.53.3': 2227 + optional: true 2228 + 2229 + '@rollup/rollup-android-arm64@4.53.3': 2230 + optional: true 2231 + 2232 + '@rollup/rollup-darwin-arm64@4.53.3': 2233 + optional: true 2234 + 2235 + '@rollup/rollup-darwin-x64@4.53.3': 2236 + optional: true 2237 + 2238 + '@rollup/rollup-freebsd-arm64@4.53.3': 2239 + optional: true 2240 + 2241 + '@rollup/rollup-freebsd-x64@4.53.3': 2242 + optional: true 2243 + 2244 + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': 2245 + optional: true 2246 + 2247 + '@rollup/rollup-linux-arm-musleabihf@4.53.3': 2248 + optional: true 2249 + 2250 + '@rollup/rollup-linux-arm64-gnu@4.53.3': 2251 + optional: true 2252 + 2253 + '@rollup/rollup-linux-arm64-musl@4.53.3': 2254 + optional: true 2255 + 2256 + '@rollup/rollup-linux-loong64-gnu@4.53.3': 2257 + optional: true 2258 + 2259 + '@rollup/rollup-linux-ppc64-gnu@4.53.3': 2260 + optional: true 2261 + 2262 + '@rollup/rollup-linux-riscv64-gnu@4.53.3': 2263 + optional: true 2264 + 2265 + '@rollup/rollup-linux-riscv64-musl@4.53.3': 2266 + optional: true 2267 + 2268 + '@rollup/rollup-linux-s390x-gnu@4.53.3': 2269 + optional: true 2270 + 2271 + '@rollup/rollup-linux-x64-gnu@4.53.3': 2272 + optional: true 2273 + 2274 + '@rollup/rollup-linux-x64-musl@4.53.3': 2275 + optional: true 2276 + 2277 + '@rollup/rollup-openharmony-arm64@4.53.3': 2278 + optional: true 2279 + 2280 + '@rollup/rollup-win32-arm64-msvc@4.53.3': 2281 + optional: true 2282 + 2283 + '@rollup/rollup-win32-ia32-msvc@4.53.3': 2284 + optional: true 2285 + 2286 + '@rollup/rollup-win32-x64-gnu@4.53.3': 2287 + optional: true 2288 + 2289 + '@rollup/rollup-win32-x64-msvc@4.53.3': 2290 + optional: true 2291 + 2292 + '@rushstack/node-core-library@5.19.0(@types/node@24.10.1)': 2293 + dependencies: 2294 + ajv: 8.13.0 2295 + ajv-draft-04: 1.0.0(ajv@8.13.0) 2296 + ajv-formats: 3.0.1(ajv@8.13.0) 2297 + fs-extra: 11.3.2 2298 + import-lazy: 4.0.0 2299 + jju: 1.4.0 2300 + resolve: 1.22.11 2301 + semver: 7.5.4 2302 + optionalDependencies: 2303 + '@types/node': 24.10.1 2304 + 2305 + '@rushstack/problem-matcher@0.1.1(@types/node@24.10.1)': 2306 + optionalDependencies: 2307 + '@types/node': 24.10.1 2308 + 2309 + '@rushstack/rig-package@0.6.0': 2310 + dependencies: 2311 + resolve: 1.22.11 2312 + strip-json-comments: 3.1.1 2313 + 2314 + '@rushstack/terminal@0.19.4(@types/node@24.10.1)': 2315 + dependencies: 2316 + '@rushstack/node-core-library': 5.19.0(@types/node@24.10.1) 2317 + '@rushstack/problem-matcher': 0.1.1(@types/node@24.10.1) 2318 + supports-color: 8.1.1 1446 2319 optionalDependencies: 1447 - fsevents: 2.3.3 1448 - dev: true 2320 + '@types/node': 24.10.1 2321 + 2322 + '@rushstack/ts-command-line@5.1.4(@types/node@24.10.1)': 2323 + dependencies: 2324 + '@rushstack/terminal': 0.19.4(@types/node@24.10.1) 2325 + '@types/argparse': 1.0.38 2326 + argparse: 1.0.10 2327 + string-argv: 0.3.2 2328 + transitivePeerDependencies: 2329 + - '@types/node' 1449 2330 1450 - /vue-template-compiler@2.7.16: 1451 - resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} 2331 + '@standard-schema/spec@1.0.0': {} 2332 + 2333 + '@sveltejs/acorn-typescript@1.0.8(acorn@8.15.0)': 2334 + dependencies: 2335 + acorn: 8.15.0 2336 + 2337 + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)))(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1))': 2338 + dependencies: 2339 + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 2340 + debug: 4.4.3 2341 + svelte: 5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd) 2342 + vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 2343 + transitivePeerDependencies: 2344 + - supports-color 2345 + 2346 + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1))': 2347 + dependencies: 2348 + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)))(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 2349 + debug: 4.4.3 2350 + deepmerge: 4.3.1 2351 + magic-string: 0.30.21 2352 + svelte: 5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd) 2353 + vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 2354 + vitefu: 1.1.1(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)) 2355 + transitivePeerDependencies: 2356 + - supports-color 2357 + 2358 + '@tsconfig/svelte@5.0.6': {} 2359 + 2360 + '@types/argparse@1.0.38': {} 2361 + 2362 + '@types/estree@1.0.8': {} 2363 + 2364 + '@types/node@12.20.55': {} 2365 + 2366 + '@types/node@24.10.1': 2367 + dependencies: 2368 + undici-types: 7.16.0 2369 + 2370 + '@volar/language-core@2.4.26': 2371 + dependencies: 2372 + '@volar/source-map': 2.4.26 2373 + 2374 + '@volar/source-map@2.4.26': {} 2375 + 2376 + '@volar/typescript@2.4.26': 2377 + dependencies: 2378 + '@volar/language-core': 2.4.26 2379 + path-browserify: 1.0.1 2380 + vscode-uri: 3.1.0 2381 + 2382 + '@vue/compiler-core@3.5.25': 2383 + dependencies: 2384 + '@babel/parser': 7.28.5 2385 + '@vue/shared': 3.5.25 2386 + entities: 4.5.0 2387 + estree-walker: 2.0.2 2388 + source-map-js: 1.2.1 2389 + 2390 + '@vue/compiler-dom@3.5.25': 2391 + dependencies: 2392 + '@vue/compiler-core': 3.5.25 2393 + '@vue/shared': 3.5.25 2394 + 2395 + '@vue/compiler-vue2@2.7.16': 1452 2396 dependencies: 1453 2397 de-indent: 1.0.2 1454 2398 he: 1.2.0 1455 - dev: true 2399 + 2400 + '@vue/language-core@2.2.0(typescript@5.8.3)': 2401 + dependencies: 2402 + '@volar/language-core': 2.4.26 2403 + '@vue/compiler-dom': 3.5.25 2404 + '@vue/compiler-vue2': 2.7.16 2405 + '@vue/shared': 3.5.25 2406 + alien-signals: 0.4.14 2407 + minimatch: 9.0.5 2408 + muggle-string: 0.4.1 2409 + path-browserify: 1.0.1 2410 + optionalDependencies: 2411 + typescript: 5.8.3 2412 + 2413 + '@vue/shared@3.5.25': {} 2414 + 2415 + acorn@8.15.0: {} 2416 + 2417 + ajv-draft-04@1.0.0(ajv@8.13.0): 2418 + optionalDependencies: 2419 + ajv: 8.13.0 2420 + 2421 + ajv-formats@3.0.1(ajv@8.13.0): 2422 + optionalDependencies: 2423 + ajv: 8.13.0 2424 + 2425 + ajv@8.12.0: 2426 + dependencies: 2427 + fast-deep-equal: 3.1.3 2428 + json-schema-traverse: 1.0.0 2429 + require-from-string: 2.0.2 2430 + uri-js: 4.4.1 2431 + 2432 + ajv@8.13.0: 2433 + dependencies: 2434 + fast-deep-equal: 3.1.3 2435 + json-schema-traverse: 1.0.0 2436 + require-from-string: 2.0.2 2437 + uri-js: 4.4.1 2438 + 2439 + alien-signals@0.4.14: {} 2440 + 2441 + ansi-colors@4.1.3: {} 2442 + 2443 + ansi-regex@5.0.1: {} 2444 + 2445 + argparse@1.0.10: 2446 + dependencies: 2447 + sprintf-js: 1.0.3 2448 + 2449 + argparse@2.0.1: {} 2450 + 2451 + aria-query@5.3.2: {} 2452 + 2453 + array-union@2.1.0: {} 2454 + 2455 + axobject-query@4.1.0: {} 2456 + 2457 + babel-plugin-transform-hook-names@1.0.2(@babel/core@7.28.5): 2458 + dependencies: 2459 + '@babel/core': 7.28.5 2460 + 2461 + balanced-match@1.0.2: {} 2462 + 2463 + baseline-browser-mapping@2.9.0: {} 2464 + 2465 + better-path-resolve@1.0.0: 2466 + dependencies: 2467 + is-windows: 1.0.2 2468 + 2469 + boolbase@1.0.0: {} 2470 + 2471 + brace-expansion@2.0.2: 2472 + dependencies: 2473 + balanced-match: 1.0.2 2474 + 2475 + braces@3.0.3: 2476 + dependencies: 2477 + fill-range: 7.1.1 2478 + 2479 + browserslist@4.28.1: 2480 + dependencies: 2481 + baseline-browser-mapping: 2.9.0 2482 + caniuse-lite: 1.0.30001759 2483 + electron-to-chromium: 1.5.264 2484 + node-releases: 2.0.27 2485 + update-browserslist-db: 1.2.1(browserslist@4.28.1) 2486 + 2487 + buffer-from@1.1.2: {} 2488 + 2489 + caniuse-lite@1.0.30001759: {} 2490 + 2491 + chardet@2.1.1: {} 2492 + 2493 + chokidar@4.0.3: 2494 + dependencies: 2495 + readdirp: 4.1.2 2496 + 2497 + ci-info@3.9.0: {} 2498 + 2499 + clsx@2.1.1: {} 2500 + 2501 + commander@2.20.3: {} 2502 + 2503 + compare-versions@6.1.1: {} 2504 + 2505 + confbox@0.1.8: {} 2506 + 2507 + confbox@0.2.2: {} 2508 + 2509 + convert-source-map@2.0.0: {} 2510 + 2511 + cross-spawn@7.0.6: 2512 + dependencies: 2513 + path-key: 3.1.1 2514 + shebang-command: 2.0.0 2515 + which: 2.0.2 2516 + 2517 + css-declaration-sorter@7.3.0(postcss@8.5.6): 2518 + dependencies: 2519 + postcss: 8.5.6 2520 + 2521 + css-select@5.2.2: 2522 + dependencies: 2523 + boolbase: 1.0.0 2524 + css-what: 6.2.2 2525 + domhandler: 5.0.3 2526 + domutils: 3.2.2 2527 + nth-check: 2.1.1 2528 + 2529 + css-what@6.2.2: {} 2530 + 2531 + de-indent@1.0.2: {} 2532 + 2533 + debug@4.4.3: 2534 + dependencies: 2535 + ms: 2.1.3 2536 + 2537 + deepmerge@4.3.1: {} 2538 + 2539 + detect-indent@6.1.0: {} 2540 + 2541 + devalue@5.5.0: {} 2542 + 2543 + diff@8.0.2: {} 2544 + 2545 + dir-glob@3.0.1: 2546 + dependencies: 2547 + path-type: 4.0.0 2548 + 2549 + dom-serializer@2.0.0: 2550 + dependencies: 2551 + domelementtype: 2.3.0 2552 + domhandler: 5.0.3 2553 + entities: 4.5.0 2554 + 2555 + domelementtype@2.3.0: {} 2556 + 2557 + domhandler@5.0.3: 2558 + dependencies: 2559 + domelementtype: 2.3.0 2560 + 2561 + domutils@3.2.2: 2562 + dependencies: 2563 + dom-serializer: 2.0.0 2564 + domelementtype: 2.3.0 2565 + domhandler: 5.0.3 2566 + 2567 + electron-to-chromium@1.5.264: {} 2568 + 2569 + enquirer@2.4.1: 2570 + dependencies: 2571 + ansi-colors: 4.1.3 2572 + strip-ansi: 6.0.1 2573 + 2574 + entities@4.5.0: {} 2575 + 2576 + esbuild@0.25.12: 2577 + optionalDependencies: 2578 + '@esbuild/aix-ppc64': 0.25.12 2579 + '@esbuild/android-arm': 0.25.12 2580 + '@esbuild/android-arm64': 0.25.12 2581 + '@esbuild/android-x64': 0.25.12 2582 + '@esbuild/darwin-arm64': 0.25.12 2583 + '@esbuild/darwin-x64': 0.25.12 2584 + '@esbuild/freebsd-arm64': 0.25.12 2585 + '@esbuild/freebsd-x64': 0.25.12 2586 + '@esbuild/linux-arm': 0.25.12 2587 + '@esbuild/linux-arm64': 0.25.12 2588 + '@esbuild/linux-ia32': 0.25.12 2589 + '@esbuild/linux-loong64': 0.25.12 2590 + '@esbuild/linux-mips64el': 0.25.12 2591 + '@esbuild/linux-ppc64': 0.25.12 2592 + '@esbuild/linux-riscv64': 0.25.12 2593 + '@esbuild/linux-s390x': 0.25.12 2594 + '@esbuild/linux-x64': 0.25.12 2595 + '@esbuild/netbsd-arm64': 0.25.12 2596 + '@esbuild/netbsd-x64': 0.25.12 2597 + '@esbuild/openbsd-arm64': 0.25.12 2598 + '@esbuild/openbsd-x64': 0.25.12 2599 + '@esbuild/openharmony-arm64': 0.25.12 2600 + '@esbuild/sunos-x64': 0.25.12 2601 + '@esbuild/win32-arm64': 0.25.12 2602 + '@esbuild/win32-ia32': 0.25.12 2603 + '@esbuild/win32-x64': 0.25.12 2604 + 2605 + escalade@3.2.0: {} 2606 + 2607 + esm-env@1.2.2: {} 2608 + 2609 + esprima@4.0.1: {} 2610 + 2611 + esrap@2.2.1: 2612 + dependencies: 2613 + '@jridgewell/sourcemap-codec': 1.5.5 2614 + 2615 + estree-walker@2.0.2: {} 2616 + 2617 + exsolve@1.0.8: {} 2618 + 2619 + extendable-error@0.1.7: {} 2620 + 2621 + fast-deep-equal@3.1.3: {} 2622 + 2623 + fast-glob@3.3.3: 2624 + dependencies: 2625 + '@nodelib/fs.stat': 2.0.5 2626 + '@nodelib/fs.walk': 1.2.8 2627 + glob-parent: 5.1.2 2628 + merge2: 1.4.1 2629 + micromatch: 4.0.8 2630 + 2631 + fastq@1.19.1: 2632 + dependencies: 2633 + reusify: 1.1.0 2634 + 2635 + fdir@6.5.0(picomatch@4.0.3): 2636 + optionalDependencies: 2637 + picomatch: 4.0.3 2638 + 2639 + fill-range@7.1.1: 2640 + dependencies: 2641 + to-regex-range: 5.0.1 2642 + 2643 + find-up@4.1.0: 2644 + dependencies: 2645 + locate-path: 5.0.0 2646 + path-exists: 4.0.0 2647 + 2648 + fs-extra@11.3.2: 2649 + dependencies: 2650 + graceful-fs: 4.2.11 2651 + jsonfile: 6.2.0 2652 + universalify: 2.0.1 2653 + 2654 + fs-extra@7.0.1: 2655 + dependencies: 2656 + graceful-fs: 4.2.11 2657 + jsonfile: 4.0.0 2658 + universalify: 0.1.2 2659 + 2660 + fs-extra@8.1.0: 2661 + dependencies: 2662 + graceful-fs: 4.2.11 2663 + jsonfile: 4.0.0 2664 + universalify: 0.1.2 2665 + 2666 + fsevents@2.3.3: 2667 + optional: true 2668 + 2669 + function-bind@1.1.2: {} 2670 + 2671 + gensync@1.0.0-beta.2: {} 2672 + 2673 + glob-parent@5.1.2: 2674 + dependencies: 2675 + is-glob: 4.0.3 2676 + 2677 + globby@11.1.0: 2678 + dependencies: 2679 + array-union: 2.1.0 2680 + dir-glob: 3.0.1 2681 + fast-glob: 3.3.3 2682 + ignore: 5.3.2 2683 + merge2: 1.4.1 2684 + slash: 3.0.0 2685 + 2686 + graceful-fs@4.2.11: {} 2687 + 2688 + has-flag@4.0.0: {} 2689 + 2690 + hasown@2.0.2: 2691 + dependencies: 2692 + function-bind: 1.1.2 2693 + 2694 + he@1.2.0: {} 2695 + 2696 + human-id@4.1.3: {} 2697 + 2698 + iconv-lite@0.7.0: 2699 + dependencies: 2700 + safer-buffer: 2.1.2 2701 + 2702 + ignore@5.3.2: {} 2703 + 2704 + import-lazy@4.0.0: {} 2705 + 2706 + is-core-module@2.16.1: 2707 + dependencies: 2708 + hasown: 2.0.2 2709 + 2710 + is-extglob@2.1.1: {} 2711 + 2712 + is-glob@4.0.3: 2713 + dependencies: 2714 + is-extglob: 2.1.1 2715 + 2716 + is-number@7.0.0: {} 2717 + 2718 + is-reference@3.0.3: 2719 + dependencies: 2720 + '@types/estree': 1.0.8 2721 + 2722 + is-subdir@1.2.0: 2723 + dependencies: 2724 + better-path-resolve: 1.0.0 2725 + 2726 + is-windows@1.0.2: {} 2727 + 2728 + isexe@2.0.0: {} 2729 + 2730 + jju@1.4.0: {} 2731 + 2732 + js-tokens@4.0.0: {} 2733 + 2734 + js-yaml@3.14.2: 2735 + dependencies: 2736 + argparse: 1.0.10 2737 + esprima: 4.0.1 2738 + 2739 + js-yaml@4.1.1: 2740 + dependencies: 2741 + argparse: 2.0.1 2742 + 2743 + jsesc@3.1.0: {} 2744 + 2745 + json-schema-traverse@1.0.0: {} 2746 + 2747 + json5@2.2.3: {} 2748 + 2749 + jsonfile@4.0.0: 2750 + optionalDependencies: 2751 + graceful-fs: 4.2.11 2752 + 2753 + jsonfile@6.2.0: 2754 + dependencies: 2755 + universalify: 2.0.1 2756 + optionalDependencies: 2757 + graceful-fs: 4.2.11 2758 + 2759 + kolorist@1.8.0: {} 2760 + 2761 + local-pkg@1.1.2: 2762 + dependencies: 2763 + mlly: 1.8.0 2764 + pkg-types: 2.3.0 2765 + quansync: 0.2.11 2766 + 2767 + locate-character@3.0.0: {} 2768 + 2769 + locate-path@5.0.0: 2770 + dependencies: 2771 + p-locate: 4.1.0 2772 + 2773 + lodash.startcase@4.4.0: {} 2774 + 2775 + lodash@4.17.21: {} 2776 + 2777 + lru-cache@5.1.1: 2778 + dependencies: 2779 + yallist: 3.1.1 2780 + 2781 + lru-cache@6.0.0: 2782 + dependencies: 2783 + yallist: 4.0.0 2784 + 2785 + magic-string@0.30.21: 2786 + dependencies: 2787 + '@jridgewell/sourcemap-codec': 1.5.5 2788 + 2789 + merge2@1.4.1: {} 2790 + 2791 + micromatch@4.0.8: 2792 + dependencies: 2793 + braces: 3.0.3 2794 + picomatch: 2.3.1 2795 + 2796 + minimatch@10.0.3: 2797 + dependencies: 2798 + '@isaacs/brace-expansion': 5.0.0 2799 + 2800 + minimatch@9.0.5: 2801 + dependencies: 2802 + brace-expansion: 2.0.2 2803 + 2804 + mlly@1.8.0: 2805 + dependencies: 2806 + acorn: 8.15.0 2807 + pathe: 2.0.3 2808 + pkg-types: 1.3.1 2809 + ufo: 1.6.1 2810 + 2811 + mri@1.2.0: {} 2812 + 2813 + ms@2.1.3: {} 2814 + 2815 + muggle-string@0.4.1: {} 2816 + 2817 + nanoid@3.3.11: {} 2818 + 2819 + node-html-parser@6.1.13: 2820 + dependencies: 2821 + css-select: 5.2.2 2822 + he: 1.2.0 2823 + 2824 + node-releases@2.0.27: {} 2825 + 2826 + nth-check@2.1.1: 2827 + dependencies: 2828 + boolbase: 1.0.0 2829 + 2830 + outdent@0.5.0: {} 2831 + 2832 + p-filter@2.1.0: 2833 + dependencies: 2834 + p-map: 2.1.0 2835 + 2836 + p-limit@2.3.0: 2837 + dependencies: 2838 + p-try: 2.2.0 2839 + 2840 + p-locate@4.1.0: 2841 + dependencies: 2842 + p-limit: 2.3.0 2843 + 2844 + p-map@2.1.0: {} 2845 + 2846 + p-try@2.2.0: {} 2847 + 2848 + package-manager-detector@0.2.11: 2849 + dependencies: 2850 + quansync: 0.2.11 2851 + 2852 + path-browserify@1.0.1: {} 2853 + 2854 + path-exists@4.0.0: {} 2855 + 2856 + path-key@3.1.1: {} 2857 + 2858 + path-parse@1.0.7: {} 2859 + 2860 + path-type@4.0.0: {} 2861 + 2862 + pathe@2.0.3: {} 2863 + 2864 + picocolors@1.1.1: {} 2865 + 2866 + picomatch@2.3.1: {} 2867 + 2868 + picomatch@4.0.3: {} 2869 + 2870 + pify@4.0.1: {} 2871 + 2872 + pkg-types@1.3.1: 2873 + dependencies: 2874 + confbox: 0.1.8 2875 + mlly: 1.8.0 2876 + pathe: 2.0.3 2877 + 2878 + pkg-types@2.3.0: 2879 + dependencies: 2880 + confbox: 0.2.2 2881 + exsolve: 1.0.8 2882 + pathe: 2.0.3 2883 + 2884 + postcss-less@6.0.0(postcss@8.5.6): 2885 + dependencies: 2886 + postcss: 8.5.6 2887 + 2888 + postcss-scss@4.0.9(postcss@8.5.6): 2889 + dependencies: 2890 + postcss: 8.5.6 2891 + 2892 + postcss@8.5.6: 2893 + dependencies: 2894 + nanoid: 3.3.11 2895 + picocolors: 1.1.1 2896 + source-map-js: 1.2.1 2897 + 2898 + preact@10.28.0: {} 2899 + 2900 + prettier-plugin-css-order@2.1.2(postcss@8.5.6)(prettier@3.7.4): 2901 + dependencies: 2902 + css-declaration-sorter: 7.3.0(postcss@8.5.6) 2903 + postcss-less: 6.0.0(postcss@8.5.6) 2904 + postcss-scss: 4.0.9(postcss@8.5.6) 2905 + prettier: 3.7.4 2906 + transitivePeerDependencies: 2907 + - postcss 2908 + 2909 + prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd)): 2910 + dependencies: 2911 + prettier: 3.7.4 2912 + svelte: 5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd) 2913 + 2914 + prettier@2.8.8: {} 2915 + 2916 + prettier@3.7.4: {} 2917 + 2918 + punycode@2.3.1: {} 2919 + 2920 + quansync@0.2.11: {} 2921 + 2922 + queue-microtask@1.2.3: {} 2923 + 2924 + read-yaml-file@1.1.0: 2925 + dependencies: 2926 + graceful-fs: 4.2.11 2927 + js-yaml: 3.14.2 2928 + pify: 4.0.1 2929 + strip-bom: 3.0.0 2930 + 2931 + readdirp@4.1.2: {} 2932 + 2933 + require-from-string@2.0.2: {} 2934 + 2935 + resolve-from@5.0.0: {} 2936 + 2937 + resolve@1.22.11: 2938 + dependencies: 2939 + is-core-module: 2.16.1 2940 + path-parse: 1.0.7 2941 + supports-preserve-symlinks-flag: 1.0.0 2942 + 2943 + reusify@1.1.0: {} 2944 + 2945 + rollup@4.53.3: 2946 + dependencies: 2947 + '@types/estree': 1.0.8 2948 + optionalDependencies: 2949 + '@rollup/rollup-android-arm-eabi': 4.53.3 2950 + '@rollup/rollup-android-arm64': 4.53.3 2951 + '@rollup/rollup-darwin-arm64': 4.53.3 2952 + '@rollup/rollup-darwin-x64': 4.53.3 2953 + '@rollup/rollup-freebsd-arm64': 4.53.3 2954 + '@rollup/rollup-freebsd-x64': 4.53.3 2955 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 2956 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 2957 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 2958 + '@rollup/rollup-linux-arm64-musl': 4.53.3 2959 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 2960 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 2961 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 2962 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 2963 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 2964 + '@rollup/rollup-linux-x64-gnu': 4.53.3 2965 + '@rollup/rollup-linux-x64-musl': 4.53.3 2966 + '@rollup/rollup-openharmony-arm64': 4.53.3 2967 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 2968 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 2969 + '@rollup/rollup-win32-x64-gnu': 4.53.3 2970 + '@rollup/rollup-win32-x64-msvc': 4.53.3 2971 + fsevents: 2.3.3 2972 + 2973 + run-parallel@1.2.0: 2974 + dependencies: 2975 + queue-microtask: 1.2.3 2976 + 2977 + sade@1.8.1: 2978 + dependencies: 2979 + mri: 1.2.0 2980 + 2981 + safer-buffer@2.1.2: {} 2982 + 2983 + semver@6.3.1: {} 2984 + 2985 + semver@7.5.4: 2986 + dependencies: 2987 + lru-cache: 6.0.0 2988 + 2989 + semver@7.7.3: {} 2990 + 2991 + shebang-command@2.0.0: 2992 + dependencies: 2993 + shebang-regex: 3.0.0 2994 + 2995 + shebang-regex@3.0.0: {} 2996 + 2997 + signal-exit@4.1.0: {} 2998 + 2999 + simple-code-frame@1.3.0: 3000 + dependencies: 3001 + kolorist: 1.8.0 3002 + 3003 + slash@3.0.0: {} 3004 + 3005 + source-map-js@1.2.1: {} 3006 + 3007 + source-map-support@0.5.21: 3008 + dependencies: 3009 + buffer-from: 1.1.2 3010 + source-map: 0.6.1 3011 + 3012 + source-map@0.6.1: {} 3013 + 3014 + source-map@0.7.6: {} 3015 + 3016 + spawndamnit@3.0.1: 3017 + dependencies: 3018 + cross-spawn: 7.0.6 3019 + signal-exit: 4.1.0 3020 + 3021 + sprintf-js@1.0.3: {} 3022 + 3023 + stack-trace@1.0.0-pre2: {} 3024 + 3025 + string-argv@0.3.2: {} 3026 + 3027 + strip-ansi@6.0.1: 3028 + dependencies: 3029 + ansi-regex: 5.0.1 3030 + 3031 + strip-bom@3.0.0: {} 3032 + 3033 + strip-json-comments@3.1.1: {} 3034 + 3035 + supports-color@8.1.1: 3036 + dependencies: 3037 + has-flag: 4.0.0 3038 + 3039 + supports-preserve-symlinks-flag@1.0.0: {} 3040 + 3041 + svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd))(typescript@5.8.3): 3042 + dependencies: 3043 + '@jridgewell/trace-mapping': 0.3.31 3044 + chokidar: 4.0.3 3045 + fdir: 6.5.0(picomatch@4.0.3) 3046 + picocolors: 1.1.1 3047 + sade: 1.8.1 3048 + svelte: 5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd) 3049 + typescript: 5.8.3 3050 + transitivePeerDependencies: 3051 + - picomatch 3052 + 3053 + svelte@5.45.5(patch_hash=0f942ae2231640ac09d0a1e9913071000fffa9bdfe69630d767130d415a78ecd): 3054 + dependencies: 3055 + '@jridgewell/remapping': 2.3.5 3056 + '@jridgewell/sourcemap-codec': 1.5.5 3057 + '@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0) 3058 + '@types/estree': 1.0.8 3059 + acorn: 8.15.0 3060 + aria-query: 5.3.2 3061 + axobject-query: 4.1.0 3062 + clsx: 2.1.1 3063 + devalue: 5.5.0 3064 + esm-env: 1.2.2 3065 + esrap: 2.2.1 3066 + is-reference: 3.0.3 3067 + locate-character: 3.0.0 3068 + magic-string: 0.30.21 3069 + zimmerframe: 1.1.4 3070 + 3071 + term-size@2.2.1: {} 3072 + 3073 + terser@5.44.1: 3074 + dependencies: 3075 + '@jridgewell/source-map': 0.3.11 3076 + acorn: 8.15.0 3077 + commander: 2.20.3 3078 + source-map-support: 0.5.21 1456 3079 1457 - /vue-tsc@1.8.27(typescript@5.4.5): 1458 - resolution: {integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==} 1459 - hasBin: true 1460 - peerDependencies: 1461 - typescript: '*' 3080 + tinyglobby@0.2.15: 1462 3081 dependencies: 1463 - '@volar/typescript': 1.11.1 1464 - '@vue/language-core': 1.8.27(typescript@5.4.5) 1465 - semver: 7.6.0 1466 - typescript: 5.4.5 1467 - dev: true 3082 + fdir: 6.5.0(picomatch@4.0.3) 3083 + picomatch: 4.0.3 1468 3084 1469 - /yallist@3.1.1: 1470 - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} 1471 - dev: true 3085 + to-regex-range@5.0.1: 3086 + dependencies: 3087 + is-number: 7.0.0 1472 3088 1473 - /yallist@4.0.0: 1474 - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1475 - dev: true 3089 + tslib@2.8.1: {} 1476 3090 1477 - /z-schema@5.0.5: 1478 - resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==} 1479 - engines: {node: '>=8.0.0'} 1480 - hasBin: true 3091 + typescript@5.8.2: {} 3092 + 3093 + typescript@5.8.3: {} 3094 + 3095 + ufo@1.6.1: {} 3096 + 3097 + undici-types@7.16.0: {} 3098 + 3099 + universalify@0.1.2: {} 3100 + 3101 + universalify@2.0.1: {} 3102 + 3103 + update-browserslist-db@1.2.1(browserslist@4.28.1): 3104 + dependencies: 3105 + browserslist: 4.28.1 3106 + escalade: 3.2.0 3107 + picocolors: 1.1.1 3108 + 3109 + uri-js@4.4.1: 1481 3110 dependencies: 1482 - lodash.get: 4.4.2 1483 - lodash.isequal: 4.5.0 1484 - validator: 13.11.0 3111 + punycode: 2.3.1 3112 + 3113 + vite-plugin-dts@4.5.4(@types/node@24.10.1)(rollup@4.53.3)(typescript@5.8.3)(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)): 3114 + dependencies: 3115 + '@microsoft/api-extractor': 7.55.1(@types/node@24.10.1) 3116 + '@rollup/pluginutils': 5.3.0(rollup@4.53.3) 3117 + '@volar/typescript': 2.4.26 3118 + '@vue/language-core': 2.2.0(typescript@5.8.3) 3119 + compare-versions: 6.1.1 3120 + debug: 4.4.3 3121 + kolorist: 1.8.0 3122 + local-pkg: 1.1.2 3123 + magic-string: 0.30.21 3124 + typescript: 5.8.3 1485 3125 optionalDependencies: 1486 - commander: 9.5.0 1487 - dev: true 3126 + vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 3127 + transitivePeerDependencies: 3128 + - '@types/node' 3129 + - rollup 3130 + - supports-color 3131 + 3132 + vite-prerender-plugin@0.5.12(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)): 3133 + dependencies: 3134 + kolorist: 1.8.0 3135 + magic-string: 0.30.21 3136 + node-html-parser: 6.1.13 3137 + simple-code-frame: 1.3.0 3138 + source-map: 0.7.6 3139 + stack-trace: 1.0.0-pre2 3140 + vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 3141 + 3142 + vite@7.2.6(@types/node@24.10.1)(terser@5.44.1): 3143 + dependencies: 3144 + esbuild: 0.25.12 3145 + fdir: 6.5.0(picomatch@4.0.3) 3146 + picomatch: 4.0.3 3147 + postcss: 8.5.6 3148 + rollup: 4.53.3 3149 + tinyglobby: 0.2.15 3150 + optionalDependencies: 3151 + '@types/node': 24.10.1 3152 + fsevents: 2.3.3 3153 + terser: 5.44.1 3154 + 3155 + vitefu@1.1.1(vite@7.2.6(@types/node@24.10.1)(terser@5.44.1)): 3156 + optionalDependencies: 3157 + vite: 7.2.6(@types/node@24.10.1)(terser@5.44.1) 3158 + 3159 + vscode-uri@3.1.0: {} 3160 + 3161 + which@2.0.2: 3162 + dependencies: 3163 + isexe: 2.0.0 3164 + 3165 + yallist@3.1.1: {} 3166 + 3167 + yallist@4.0.0: {} 3168 + 3169 + zimmerframe@1.1.4: {}
+9
pnpm-workspace.yaml
··· 1 + packages: 2 + - packages/internal 3 + - packages/svelte-site 4 + - packages/bluesky-post-embed 5 + - packages/bluesky-profile-card-embed 6 + - packages/bluesky-profile-feed-embed 7 + 8 + catalog: 9 + svelte: ^5.45.5
-17
src/index.ts
··· 1 - import '../lib/index.ts'; 2 - import type { BlueskyPost } from '../lib/index.ts'; 3 - 4 - import './style.css'; 5 - 6 - const dark = matchMedia('(prefers-color-scheme: dark)'); 7 - 8 - const update_theme = () => { 9 - const is_dark = dark.matches; 10 - 11 - for (const node of document.querySelectorAll<BlueskyPost>('bluesky-post')) { 12 - node.setAttribute('theme', !is_dark ? 'light' : 'dark'); 13 - } 14 - }; 15 - 16 - update_theme(); 17 - dark.addEventListener('change', update_theme);
-75
src/style.css
··· 1 - :root { 2 - --background-primary: #fafafa; 3 - --background-secondary: #e5e5e5; 4 - --text-primary: #000000; 5 - --text-link: #1d4ed8; 6 - --divider: #c8c8c8; 7 - } 8 - 9 - @media (prefers-color-scheme: dark) { 10 - :root { 11 - --background-primary: #0a0a0a; 12 - --background-secondary: #171717; 13 - --text-primary: #ffffff; 14 - --text-link: #60a5fa; 15 - --divider: #404040; 16 - } 17 - } 18 - 19 - html { 20 - background: var(--background-primary); 21 - color: var(--text-primary); 22 - color-scheme: light dark; 23 - font-size: 14px; 24 - line-height: 1.25rem; 25 - } 26 - 27 - body { 28 - margin: 24px auto; 29 - padding: 0 16px; 30 - max-width: 680px; 31 - } 32 - 33 - h1, 34 - h2, 35 - h3, 36 - h4, 37 - h5, 38 - h6, 39 - p { 40 - margin-block-start: 1.1rem; 41 - margin-block-end: 1.1rem; 42 - } 43 - 44 - h1 { 45 - font-size: 1.25rem; 46 - } 47 - h2 { 48 - font-size: 1.125rem; 49 - } 50 - 51 - a { 52 - color: var(--text-link); 53 - } 54 - 55 - pre { 56 - border-radius: 4px; 57 - background: var(--background-secondary); 58 - padding: 8px; 59 - overflow-x: auto; 60 - font-size: 12px; 61 - } 62 - 63 - bluesky-post { 64 - --font-size: 16px; 65 - margin: 16px auto; 66 - } 67 - 68 - .bluesky-post-fallback { 69 - margin: 16px 0; 70 - border-left: 3px solid var(--divider); 71 - padding: 4px 8px; 72 - } 73 - .bluesky-post-fallback p { 74 - margin: 0 0 8px 0; 75 - }
+19
themes/dark.css
··· 1 + .bluesky-embed { 2 + --font-size: 16px; 3 + --font-family: 4 + system-ui, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; 5 + --max-feed-height: 600px; 6 + } 7 + 8 + .bluesky-embed { 9 + --text-primary: #f1f3f5; 10 + --text-secondary: #8c9eb2; 11 + --text-link: #1083fe; 12 + --background-primary: #000000; 13 + --background-secondary: #212d3b; 14 + --divider: rgb(37, 51, 66); 15 + --divider-hover: rgb(66, 87, 108); 16 + --button: #208bfe; 17 + --button-text: #ffffff; 18 + --button-hover: #4ca2fe; 19 + }
+19
themes/dim.css
··· 1 + .bluesky-embed { 2 + --font-size: 16px; 3 + --font-family: 4 + system-ui, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; 5 + --max-feed-height: 600px; 6 + } 7 + 8 + .bluesky-embed { 9 + --text-primary: #f1f3f5; 10 + --text-secondary: #aebbc9; 11 + --text-link: #1083fe; 12 + --background-primary: #161e27; 13 + --background-secondary: #212d3b; 14 + --divider: #2e4052; 15 + --divider-hover: #4a6179; 16 + --button: #208bfe; 17 + --button-text: #ffffff; 18 + --button-hover: #4ca2fe; 19 + }
+19
themes/light.css
··· 1 + .bluesky-embed { 2 + --font-size: 16px; 3 + --font-family: 4 + system-ui, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; 5 + --max-feed-height: 600px; 6 + } 7 + 8 + .bluesky-embed { 9 + --text-primary: #000000; 10 + --text-secondary: #455668; 11 + --text-link: #1083fe; 12 + --background-primary: #ffffff; 13 + --background-secondary: #455668; 14 + --divider-hover: #a9b7c5; 15 + --divider: #d4dbe2; 16 + --button: #1083fe; 17 + --button-text: #ffffff; 18 + --button-hover: #0168d5; 19 + }
-25
tsconfig.json
··· 1 - { 2 - "compilerOptions": { 3 - "target": "ESNext", 4 - "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 - "types": [], 6 - "skipLibCheck": true, 7 - 8 - "module": "ESNext", 9 - "moduleResolution": "Bundler", 10 - "allowImportingTsExtensions": true, 11 - "resolveJsonModule": true, 12 - "noEmit": true, 13 - "jsx": "preserve", 14 - "jsxImportSource": "@intrnl/jsx-to-string", 15 - 16 - "incremental": true, 17 - "strict": true, 18 - "noUnusedLocals": true, 19 - "noUnusedParameters": true, 20 - "noFallthroughCasesInSwitch": true, 21 - "verbatimModuleSyntax": true, 22 - }, 23 - "include": ["src", "lib"], 24 - "references": [{ "path": "./tsconfig.node.json" }], 25 - }
-10
tsconfig.node.json
··· 1 - { 2 - "compilerOptions": { 3 - "composite": true, 4 - "skipLibCheck": true, 5 - "module": "ESNext", 6 - "moduleResolution": "bundler", 7 - "allowSyntheticDefaultImports": true 8 - }, 9 - "include": ["vite.config.ts"] 10 - }
-46
vite.config.ts
··· 1 - import { defineConfig } from 'vite'; 2 - 3 - import babel from '@rollup/plugin-babel'; 4 - import dts from 'vite-plugin-dts'; 5 - 6 - export default defineConfig({ 7 - base: '/', 8 - optimizeDeps: { 9 - include: ['@intrnl/jsx-to-string/runtime'], 10 - }, 11 - build: { 12 - sourcemap: true, 13 - target: 'esnext', 14 - minify: false, 15 - cssMinify: true, 16 - lib: { 17 - entry: { 18 - core: 'lib/core.tsx', 19 - element: 'lib/index.ts', 20 - }, 21 - formats: ['es'], 22 - }, 23 - rollupOptions: { 24 - external: [ 25 - '@externdefs/bluesky-client', 26 - '@externdefs/bluesky-client/lexicons', 27 - '@externdefs/bluesky-client/xrpc', 28 - ], 29 - }, 30 - }, 31 - esbuild: { 32 - target: 'es2022', 33 - }, 34 - plugins: [ 35 - { 36 - enforce: 'pre', 37 - ...babel({ 38 - babelrc: false, 39 - babelHelpers: 'bundled', 40 - extensions: ['.tsx'], 41 - plugins: [['@babel/plugin-syntax-typescript', { isTSX: true }], ['@intrnl/jsx-to-string/babel']], 42 - }), 43 - }, 44 - dts({ rollupTypes: true }), 45 - ], 46 - });