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

initial commit

+13
.gitignore
··· 1 + node_modules/ 2 + dist/ 3 + 4 + .npm-*.log 5 + .pnpm-*.log 6 + .yarn-*.log 7 + npm-*.log 8 + pnpm-*.log 9 + yarn-*.log 10 + 11 + *.local 12 + 13 + *.tsbuildinfo
+3
.prettierignore
··· 1 + pnpm-lock.yaml 2 + 3 + dist/
+25
.prettierrc
··· 1 + { 2 + "trailingComma": "all", 3 + "useTabs": true, 4 + "tabWidth": 2, 5 + "printWidth": 110, 6 + "semi": true, 7 + "singleQuote": true, 8 + "bracketSpacing": true, 9 + "plugins": ["prettier-plugin-svelte", "prettier-plugin-css-order"], 10 + "overrides": [ 11 + { 12 + "files": ["tsconfig.json", "jsconfig.json"], 13 + "options": { 14 + "parser": "jsonc" 15 + } 16 + }, 17 + { 18 + "files": ["*.md"], 19 + "options": { 20 + "printWidth": 100, 21 + "proseWrap": "always" 22 + } 23 + } 24 + ] 25 + }
+3
.vscode/extensions.json
··· 1 + { 2 + "recommendations": ["svelte.svelte-vscode"] 3 + }
+4
.vscode/settings.json
··· 1 + { 2 + "editor.defaultFormatter": "esbenp.prettier-vscode", 3 + "typescript.tsdk": "node_modules/typescript/lib" 4 + }
+17
LICENSE
··· 1 + Permission is hereby granted, free of charge, to any person obtaining a copy 2 + of this software and associated documentation files (the "Software"), to deal 3 + in the Software without restriction, including without limitation the rights 4 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 + copies of the Software, and to permit persons to whom the Software is 6 + furnished to do so, subject to the following conditions: 7 + 8 + The above copyright notice and this permission notice shall be included in all 9 + copies or substantial portions of the Software. 10 + 11 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 + SOFTWARE.
+12
README.md
··· 1 + # <bluesky-embed> 2 + 3 + > [!CAUTION] 4 + > This is a new, work-in-progress version of `bluesky-post-embed`, things aren't done yet. 5 + 6 + A custom element for embedding Bluesky posts. 7 + 8 + - **Lightweight**, the entire package + dependencies is only ~20 KB (~6 KB gzipped) 9 + - **Standalone**, no middleman involved, directly calls Bluesky's API 10 + - **Server-side rendering possible**, allows for no-JavaScript usage 11 + 12 + ![image](https://github.com/user-attachments/assets/fbe19e25-bcc8-4fd5-ac9d-568badbbb238)
+17
package.json
··· 1 + { 2 + "private": true, 3 + "scripts": { 4 + "fmt": "prettier --cache --write ." 5 + }, 6 + "devDependencies": { 7 + "prettier": "^3.4.1", 8 + "prettier-plugin-css-order": "^2.1.2", 9 + "prettier-plugin-svelte": "^3.3.2", 10 + "typescript": "^5.7.2" 11 + }, 12 + "pnpm": { 13 + "patchedDependencies": { 14 + "svelte": "patches/svelte.patch" 15 + } 16 + } 17 + }
+1
packages/bluesky-post-embed/.gitignore
··· 1 + themes/
+86
packages/bluesky-post-embed/README.md
··· 1 + # &lt;bluesky-post-embed> 2 + 3 + > [!CAUTION] 4 + > This is a new, work-in-progress version of `bluesky-post-embed`, things aren't done yet. 5 + 6 + A custom element for embedding Bluesky posts. 7 + 8 + ## Installation 9 + 10 + ### via npm 11 + 12 + ``` 13 + npm install bluesky-post-embed 14 + ``` 15 + 16 + then, import the package on your app. 17 + 18 + ```js 19 + import 'bluesky-post-embed'; 20 + 21 + import 'bluesky-post-embed/style.css'; 22 + import 'bluesky-post-embed/themes/light.css'; 23 + ``` 24 + 25 + ## Usage 26 + 27 + ```html 28 + <bluesky-post src="at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.feed.post/3kj2umze7zj2n"> 29 + <blockquote class="bluesky-post-fallback"> 30 + <p dir="auto">angel mode</p> 31 + — Paul Frazee (@pfrazee.com) 32 + <a href="https://bsky.app/profile/did:plc:ragtjsm2j2vknwkz3zp4oxrd/post/3kj2umze7zj2n" 33 + >January 16, 2024 at 9:11 AM</a 34 + > 35 + </blockquote> 36 + </bluesky-post> 37 + ``` 38 + 39 + ### Attributes 40 + 41 + - `src` **Required** 42 + AT-URI of the post record 43 + - `contextless` **Optional** 44 + Whether to show the post without any context (no parent reply) 45 + - `allow-unauthenticated` **Optional** 46 + Whether to allow unauthenticated viewing 47 + - `service-uri` **Optional** 48 + URL to an AppView service, defaults to `https://public.api.bsky.app` 49 + 50 + ### Events 51 + 52 + - `loaded` 53 + Fired when the embed has successfully loaded the post 54 + - `error` 55 + Fired when the embed fails to load the post 56 + 57 + ## SSR usage 58 + 59 + The embeds are powered by a static HTML renderer, this renderer can be used directly in your 60 + server-rendering framework of choice for a zero-JS experience. 61 + 62 + ```tsx 63 + import { fetchPost, renderPost } from 'bluesky-post-embed/core'; 64 + 65 + import 'bluesky-post-embed/style.css'; 66 + import 'bluesky-post-embed/themes/light.css'; 67 + 68 + // fetch the post 69 + const controller = new AbortController(); 70 + const data = await fetchPost({ 71 + src: `at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.feed.post/3kj2umze7zj2n`, 72 + signal: controller.signal, 73 + }); 74 + 75 + // render the post 76 + const html = renderPost(data); 77 + return ( 78 + <bluesky-post 79 + src={data.thread?.post.uri} 80 + dangerouslySetInnerHTML={{ __html: html }} 81 + ></bluesky-post> 82 + ); 83 + ``` 84 + 85 + Check out examples for [Astro](https://github.com/mary-ext/bluesky-embed-astro) and 86 + [SvelteKit](https://github.com/mary-ext/bluesky-embed-sveltekit).
+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>
+69
packages/bluesky-post-embed/lib/core.ts
··· 1 + import '@atcute/bluesky/lexicons'; 2 + 3 + import { simpleFetchHandler, XRPC, XRPCError } from '@atcute/client'; 4 + import { render } from 'svelte/server'; 5 + 6 + import { DEFAULT_APPVIEW_URL } from 'internal/utils/constants.js'; 7 + import type { PostData } from 'internal/types/post.js'; 8 + 9 + import BlueskyPost from './bluesky-post.svelte'; 10 + 11 + export type { PostData }; 12 + 13 + export interface PostFetchOptions { 14 + /** 15 + * AT-URI of the post in question 16 + */ 17 + uri: string; 18 + /** 19 + * Abort signal to cancel the request 20 + */ 21 + signal?: AbortSignal; 22 + /** 23 + * Whether to fetch post without context (no parent replies) 24 + * @default false 25 + */ 26 + contextless?: boolean; 27 + /** 28 + * Whether to allow unauthenticated viewing 29 + * @default false 30 + */ 31 + allowUnauthenticated?: boolean; 32 + /** 33 + * AppView service to use 34 + * @default "https://public.api.bsky.app" 35 + */ 36 + serviceUri?: string; 37 + } 38 + 39 + export const fetchPost = async (opts: PostFetchOptions): Promise<PostData> => { 40 + const rpc = new XRPC({ handler: simpleFetchHandler({ service: opts.serviceUri ?? DEFAULT_APPVIEW_URL }) }); 41 + const contextless = opts.contextless ?? false; 42 + 43 + const { data } = await rpc 44 + .get('app.bsky.feed.getPostThread', { 45 + signal: opts.signal, 46 + params: { 47 + uri: opts.uri, 48 + parentHeight: !contextless ? 2 : 1, 49 + depth: 0, 50 + }, 51 + }) 52 + .catch((err) => { 53 + if (err instanceof XRPCError) { 54 + if (err.kind === 'NotFound') { 55 + return { data: null }; 56 + } 57 + } 58 + 59 + return Promise.reject(err); 60 + }); 61 + 62 + const thread = data?.thread.$type === 'app.bsky.feed.defs#threadViewPost' ? data.thread : null; 63 + 64 + return { thread, contextless, allowUnauthenticated: opts.allowUnauthenticated ?? false }; 65 + }; 66 + 67 + export const renderPost = (data: PostData): string => { 68 + return render(BlueskyPost, { props: data }).body; 69 + };
+29
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 + 22 + const data = await fetchPost({ uri: src, contextless, allowUnauthenticated, serviceUri }); 23 + const html = renderPost(data); 24 + 25 + this.innerHTML = html; 26 + } 27 + } 28 + 29 + customElements.define('bluesky-post', BlueskyPost);
+43
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": "0.2.0-alpha.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-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", 26 + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json", 27 + "prepack": "pnpm run build; rsync -aHAX --delete ../../themes/ themes/" 28 + }, 29 + "dependencies": { 30 + "@atcute/bluesky": "^1.0.9", 31 + "@atcute/bluesky-richtext-segmenter": "^1.0.5", 32 + "@atcute/client": "^2.0.6", 33 + "svelte": "^5.3.1" 34 + }, 35 + "devDependencies": { 36 + "@tsconfig/svelte": "^5.0.4", 37 + "@types/node": "^22.10.1", 38 + "internal": "workspace:^", 39 + "svelte-check": "^4.1.0", 40 + "vite": "^6.0.2", 41 + "vite-plugin-dts": "^4.3.0" 42 + } 43 + }
+15
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 + }, 13 + "include": ["lib"], 14 + "references": [{ "path": "./tsconfig.node.json" }], 15 + }
+13
packages/bluesky-post-embed/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "types": ["node"], 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 + }
+102
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 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-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 + // nasty hacks to get smaller sizes 82 + let jsCode = js.code 83 + .replace(/<!--.*?-->/g, '') 84 + .replace(/\$\$slots: {.+?},?/g, '') 85 + .replace(/\$\$payload\.out \+= ["`]{2};|\$\.(push|pop)\(\);/g, '') 86 + .replace(/(?<=\$\$payload\.out \+= )`\${([a-zA-Z0-9_$.,()[\]\s]+?)}`(?=;)/, '$1'); 87 + 88 + if (css) { 89 + const cssId = `${id}.css`; 90 + jsCode = jsCode + `\nimport ${JSON.stringify(cssId)};\n`; 91 + stylesheets.set(cssId, css.code); 92 + } 93 + 94 + for (const warn of warnings) { 95 + this.warn(warn); 96 + } 97 + 98 + return { code: jsCode }; 99 + } 100 + }, 101 + }; 102 + }
+1
packages/bluesky-profile-feed-embed/.gitignore
··· 1 + themes/
+87
packages/bluesky-profile-feed-embed/README.md
··· 1 + # &lt;bluesky-profile-feed-embed> 2 + 3 + > [!CAUTION] 4 + > This is a new, work-in-progress version of `bluesky-post-embed`, things aren't done yet. 5 + 6 + A custom element for embedding Bluesky profile feeds. 7 + 8 + ## Installation 9 + 10 + ### via npm 11 + 12 + ``` 13 + npm install bluesky-profile-feed-embed 14 + ``` 15 + 16 + then, import the package on your app. 17 + 18 + ```js 19 + import 'bluesky-profile-feed-embed'; 20 + 21 + import 'bluesky-profile-feed-embed/style.css'; 22 + import 'bluesky-profile-feed-embed/themes/light.css'; 23 + ``` 24 + 25 + ## Usage 26 + 27 + ```html 28 + <bluesky-profile-feed actor="did:plc:ragtjsm2j2vknwkz3zp4oxrd" include-pins> 29 + <a 30 + target="_blank" 31 + href="https://bsky.app/profile/did:plc:ragtjsm2j2vknwkz3zp4oxrd" 32 + class="bluesky-profile-feed-fallback" 33 + > 34 + Posts by Paul Frazee (@pfrazee.com) 35 + </a> 36 + </bluesky-profile-feed> 37 + ``` 38 + 39 + ### Attributes 40 + 41 + - `actor` **Required** 42 + DID or handle of the account 43 + - `include-pins` **Optional** 44 + Whether to show pinned posts 45 + - `allow-unauthenticated` **Optional** 46 + Whether to allow unauthenticated viewing 47 + - `service-uri` **Optional** 48 + URL to an AppView service, defaults to `https://public.api.bsky.app` 49 + 50 + ### Events 51 + 52 + - `loaded` 53 + Fired when the embed has successfully loaded the post 54 + - `error` 55 + Fired when the embed fails to load the post 56 + 57 + ## SSR usage 58 + 59 + The embeds are powered by a static HTML renderer, this renderer can be used directly in your 60 + server-rendering framework of choice for a zero-JS experience. 61 + 62 + ```tsx 63 + import { fetchProfileFeed, renderProfileFeed } from 'bluesky-profile-feed-embed/core'; 64 + 65 + import 'bluesky-post-embed/style.css'; 66 + import 'bluesky-post-embed/themes/light.css'; 67 + 68 + // fetch the profile 69 + const controller = new AbortController(); 70 + const data = await fetchProfileFeed({ 71 + actor: `did:plc:ragtjsm2j2vknwkz3zp4oxrd`, 72 + includePins: true, 73 + signal: controller.signal, 74 + }); 75 + 76 + // render the profile 77 + const html = renderProfileFeed(data); 78 + return ( 79 + <bluesky-profile-feed 80 + src={data.thread?.post.uri} 81 + dangerouslySetInnerHTML={{ __html: html }} 82 + ></bluesky-profile-feed> 83 + ); 84 + ``` 85 + 86 + Check out examples for [Astro](https://github.com/mary-ext/bluesky-embed-astro) and 87 + [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>
+86
packages/bluesky-profile-feed-embed/lib/core.ts
··· 1 + import '@atcute/bluesky/lexicons'; 2 + 3 + import { simpleFetchHandler, XRPC, XRPCError } from '@atcute/client'; 4 + import { render } from 'svelte/server'; 5 + 6 + import { DEFAULT_APPVIEW_URL } from 'internal/utils/constants.js'; 7 + import type { ProfileFeedData } from 'internal/types/profile-feed.js'; 8 + 9 + import BlueskyProfileFeed from './bluesky-profile-feed.svelte'; 10 + 11 + export type { ProfileFeedData }; 12 + 13 + export interface ProfileFeedFetchOptions { 14 + /** 15 + * Handle or DID identifier of the user 16 + */ 17 + actor: string; 18 + /** 19 + * Abort signal to cancel the request 20 + */ 21 + signal?: AbortSignal; 22 + /** 23 + * Include pinned posts 24 + * @default false 25 + */ 26 + includePins?: boolean; 27 + /** 28 + * Allow unauthenticated viewing 29 + * @default false 30 + */ 31 + allowUnauthenticated?: boolean; 32 + /** 33 + * AppView service to use 34 + * @default "https://public.api.bsky.app" 35 + */ 36 + serviceUri?: string; 37 + } 38 + 39 + export const fetchProfileFeed = async (opts: ProfileFeedFetchOptions): Promise<ProfileFeedData> => { 40 + const actor = opts.actor; 41 + const allowUnauthenticated = opts.allowUnauthenticated ?? false; 42 + 43 + const rpc = new XRPC({ handler: simpleFetchHandler({ service: opts.serviceUri ?? DEFAULT_APPVIEW_URL }) }); 44 + 45 + const [{ data: profile }, { data: timeline }] = await Promise.all([ 46 + rpc 47 + .get('app.bsky.actor.getProfile', { 48 + signal: opts.signal, 49 + params: { actor }, 50 + }) 51 + .catch((err) => { 52 + if (err instanceof XRPCError) { 53 + if (err.kind === 'InvalidRequest' && err.description === 'Profile not found') { 54 + return { data: null }; 55 + } 56 + } 57 + 58 + return Promise.reject(err); 59 + }), 60 + rpc 61 + .get('app.bsky.feed.getAuthorFeed', { 62 + signal: opts.signal, 63 + params: { 64 + actor, 65 + includePins: opts.includePins, 66 + limit: 30, 67 + filter: 'posts_and_author_threads', 68 + }, 69 + }) 70 + .catch((err) => { 71 + if (err instanceof XRPCError) { 72 + if (err.kind === 'InvalidRequest' && err.description === 'Profile not found') { 73 + return { data: { feed: [] } }; 74 + } 75 + } 76 + 77 + return Promise.reject(err); 78 + }), 79 + ]); 80 + 81 + return { profile: profile, feed: timeline.feed, allowUnauthenticated }; 82 + }; 83 + 84 + export const renderProfileFeed = (data: ProfileFeedData): string => { 85 + return render(BlueskyProfileFeed, { props: data }).body; 86 + };
+29
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 + 22 + const data = await fetchProfileFeed({ actor, allowUnauthenticated, includePins, serviceUri }); 23 + const html = renderProfileFeed(data); 24 + 25 + this.innerHTML = html; 26 + } 27 + } 28 + 29 + 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": "0.2.0-alpha.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-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", 26 + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json", 27 + "prepack": "pnpm run build; rsync -aHAX --delete ../../themes/ themes/" 28 + }, 29 + "dependencies": { 30 + "@atcute/bluesky": "^1.0.9", 31 + "@atcute/bluesky-richtext-segmenter": "^1.0.5", 32 + "@atcute/client": "^2.0.6", 33 + "svelte": "^5.3.1" 34 + }, 35 + "devDependencies": { 36 + "@tsconfig/svelte": "^5.0.4", 37 + "@types/node": "^22.10.1", 38 + "internal": "workspace:^", 39 + "svelte-check": "^4.1.0", 40 + "vite": "^6.0.2", 41 + "vite-plugin-dts": "^4.3.0" 42 + } 43 + }
+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 + }
+13
packages/bluesky-profile-feed-embed/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "types": ["node"], 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 + }
+102
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-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 + // nasty hacks to get smaller sizes 82 + let jsCode = js.code 83 + .replace(/<!--.*?-->/g, '') 84 + .replace(/\$\$slots: {.+?},?/g, '') 85 + .replace(/\$\$payload\.out \+= ["`]{2};|\$\.(push|pop)\(\);/g, '') 86 + .replace(/(?<=\$\$payload\.out \+= )`\${([a-zA-Z0-9_$.,()[\]\s]+?)}`(?=;)/, '$1'); 87 + 88 + if (css) { 89 + const cssId = `${id}.css`; 90 + jsCode = jsCode + `\nimport ${JSON.stringify(cssId)};\n`; 91 + stylesheets.set(cssId, css.code); 92 + } 93 + 94 + for (const warn of warnings) { 95 + this.warn(warn); 96 + } 97 + 98 + return { code: jsCode }; 99 + } 100 + }, 101 + }; 102 + }
+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>
+119
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 { parseAtUri } from '../../utils/syntax/at-url'; 31 + 32 + import ExternalEmbed from './external-embed.svelte'; 33 + import FeedEmbed from './feed-embed.svelte'; 34 + import ImageEmbed from './image-embed.svelte'; 35 + import ListEmbed from './list-embed.svelte'; 36 + import QuoteEmbed from './quote-embed.svelte'; 37 + import StarterpackEmbed from './starterpack-embed.svelte'; 38 + import VideoEmbed from './video-embed.svelte'; 39 + 40 + type Embed = NonNullable<AppBskyFeedDefs.PostView['embed']>; 41 + type MediaEmbed = Brand.Union<AppBskyEmbedExternal.View | AppBskyEmbedImages.View | AppBskyEmbedVideo.View>; 42 + type RecordEmbed = AppBskyEmbedRecord.View; 43 + 44 + interface Props { 45 + post?: AppBskyFeedDefs.PostView; 46 + embed: Embed; 47 + large?: boolean; 48 + } 49 + 50 + const { post, embed, large = false }: Props = $props(); 51 + </script> 52 + 53 + <div class="embeds"> 54 + {#if embed.$type === 'app.bsky.embed.recordWithMedia#view'} 55 + {@render Media(embed.media)} 56 + {@render Record(embed.record)} 57 + {:else if embed.$type === 'app.bsky.embed.record#view'} 58 + {@render Record(embed)} 59 + {:else} 60 + {@render Media(embed)} 61 + {/if} 62 + </div> 63 + 64 + {#snippet Media(embed: MediaEmbed)} 65 + {#if embed.$type === 'app.bsky.embed.external#view'} 66 + <ExternalEmbed {embed} /> 67 + {:else if embed.$type === 'app.bsky.embed.images#view'} 68 + <ImageEmbed {embed} standalone /> 69 + {:else if embed.$type === 'app.bsky.embed.video#view'} 70 + <VideoEmbed {post} {embed} standalone /> 71 + {:else} 72 + {@render Message(`Unsupported media embed`)} 73 + {/if} 74 + {/snippet} 75 + 76 + {#snippet Record(embed: RecordEmbed)} 77 + {@const record = embed.record} 78 + 79 + {#if record.$type === 'app.bsky.embed.record#viewRecord'} 80 + <QuoteEmbed embed={record} {large} /> 81 + {:else if record.$type === 'app.bsky.feed.defs#generatorView'} 82 + <FeedEmbed embed={record} /> 83 + {:else if record.$type === 'app.bsky.graph.defs#listView'} 84 + <ListEmbed embed={record} /> 85 + {:else if record.$type === 'app.bsky.graph.defs#starterPackViewBasic'} 86 + <StarterpackEmbed embed={record} {large} /> 87 + {:else} 88 + {@const uri = parseAtUri(record.uri)} 89 + {@const resource = collectionToLabel(uri.collection)} 90 + 91 + {@const isUnavailable = 92 + resource && 93 + (record.$type === 'app.bsky.embed.record#viewNotFound' || 94 + record.$type === 'app.bsky.embed.record#viewBlocked' || 95 + record.$type === 'app.bsky.embed.record#viewDetached')} 96 + 97 + {@render Message(isUnavailable ? `This ${resource} is unavailable` : `Unsupported record embed`)} 98 + {/if} 99 + {/snippet} 100 + 101 + {#snippet Message(message: string)} 102 + <div class="message">{message}</div> 103 + {/snippet} 104 + 105 + <style> 106 + .embeds { 107 + display: flex; 108 + flex-direction: column; 109 + gap: 12px; 110 + margin: 12px 0 0 0; 111 + } 112 + 113 + .message { 114 + border: 1px solid var(--divider); 115 + border-radius: 6px; 116 + padding: 12px; 117 + color: var(--text-secondary); 118 + } 119 + </style>
+124
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 + background: #000000; 77 + aspect-ratio: 1.91; 78 + width: 100%; 79 + } 80 + 81 + .meta { 82 + padding: 12px; 83 + } 84 + 85 + .title { 86 + display: -webkit-box; 87 + overflow: hidden; 88 + font-weight: 700; 89 + white-space: pre-wrap; 90 + -webkit-box-orient: vertical; 91 + -webkit-line-clamp: 2; 92 + line-clamp: 2; 93 + overflow-wrap: break-word; 94 + 95 + &:empty { 96 + display: none; 97 + } 98 + } 99 + .description { 100 + display: -webkit-box; 101 + overflow: hidden; 102 + color: var(--text-secondary); 103 + font-size: calc(var(--font-size) * 0.8125); 104 + white-space: pre-wrap; 105 + -webkit-box-orient: vertical; 106 + -webkit-line-clamp: 2; 107 + line-clamp: 2; 108 + overflow-wrap: break-word; 109 + 110 + &:empty { 111 + display: none; 112 + } 113 + } 114 + 115 + .domain { 116 + display: flex; 117 + align-items: center; 118 + gap: 6px; 119 + margin: 6px 0 0 0; 120 + color: var(--text-secondary); 121 + font-weight: 500; 122 + font-size: calc(var(--font-size) * 0.75); 123 + } 124 + </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>
+155
packages/internal/components/embeds/image-embed.svelte
··· 1 + <script lang="ts"> 2 + import type { AppBskyEmbedImages } from '@atcute/client/lexicons'; 3 + 4 + interface Props { 5 + embed: AppBskyEmbedImages.View; 6 + borderless?: boolean; 7 + standalone?: boolean; 8 + } 9 + 10 + const { embed, borderless, standalone }: Props = $props(); 11 + 12 + const images = embed.images; 13 + const length = images.length; 14 + </script> 15 + 16 + <div 17 + class={'image-embed' + 18 + (!borderless ? ` is-bordered` : ``) + 19 + (standalone && length === 1 ? ` is-aligned` : ``)} 20 + > 21 + {#if length === 4} 22 + <div class="grid"> 23 + <div class="col"> 24 + <div class="item wide"> 25 + {@render Image(images[0])} 26 + </div> 27 + <div class="item wide"> 28 + {@render Image(images[1])} 29 + </div> 30 + </div> 31 + <div class="col"> 32 + <div class="item wide"> 33 + {@render Image(images[2])} 34 + </div> 35 + <div class="item wide"> 36 + {@render Image(images[3])} 37 + </div> 38 + </div> 39 + </div> 40 + {:else if length === 3} 41 + <div class="grid"> 42 + <div class="col square"> 43 + <div class="item"> 44 + {@render Image(images[0])} 45 + </div> 46 + </div> 47 + <div class="col square"> 48 + <div class="item"> 49 + {@render Image(images[1])} 50 + </div> 51 + <div class="item"> 52 + {@render Image(images[2])} 53 + </div> 54 + </div> 55 + </div> 56 + {:else if length === 2} 57 + <div class="grid"> 58 + <div class="col"> 59 + <div class="item square"> 60 + {@render Image(images[0])} 61 + </div> 62 + </div> 63 + <div class="col"> 64 + <div class="item square"> 65 + {@render Image(images[1])} 66 + </div> 67 + </div> 68 + </div> 69 + {:else if length === 1} 70 + {@const image = images[0]} 71 + {@const ratio = standalone && image.aspectRatio} 72 + 73 + <div 74 + class={`single-item` + (ratio ? ` is-standalone` : ``)} 75 + style={ratio ? `aspect-ratio: ${ratio.width}/${ratio.height}` : ``} 76 + > 77 + {@render Image(image)} 78 + 79 + {#if ratio} 80 + <div class="placeholder"></div> 81 + {/if} 82 + </div> 83 + {/if} 84 + </div> 85 + 86 + {#snippet Image(image: AppBskyEmbedImages.ViewImage)} 87 + <img loading="lazy" src={image.thumb} alt={image.alt} class="image" /> 88 + {/snippet} 89 + 90 + <style> 91 + .is-bordered { 92 + border: 1px solid var(--divider); 93 + border-radius: 6px; 94 + overflow: hidden; 95 + } 96 + .is-aligned { 97 + align-self: baseline; 98 + max-width: 100%; 99 + } 100 + 101 + .grid { 102 + display: flex; 103 + gap: 2px; 104 + } 105 + .col { 106 + display: flex; 107 + flex: 1; 108 + flex-direction: column; 109 + gap: 2px; 110 + } 111 + 112 + .square { 113 + aspect-ratio: 1; 114 + } 115 + .wide { 116 + aspect-ratio: 1.5; 117 + } 118 + 119 + .item { 120 + position: relative; 121 + flex-grow: 1; 122 + flex-shrink: 0; 123 + } 124 + 125 + .single-item { 126 + position: relative; 127 + aspect-ratio: 16 / 9; 128 + overflow: hidden; 129 + 130 + .image { 131 + object-fit: contain; 132 + } 133 + } 134 + .is-standalone { 135 + min-width: 64px; 136 + max-width: 100%; 137 + min-height: 64px; 138 + max-height: 320px; 139 + } 140 + 141 + .image { 142 + position: absolute; 143 + inset: 0; 144 + background: #000000; 145 + width: 100%; 146 + height: 100%; 147 + object-fit: cover; 148 + font-size: 0px; 149 + } 150 + 151 + .placeholder { 152 + width: 100vw; 153 + height: 100vh; 154 + } 155 + </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>
+210
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 { parseAtUri } from '../../utils/syntax/at-url'; 39 + 40 + import ImageEmbed from './image-embed.svelte'; 41 + import VideoEmbed from './video-embed.svelte'; 42 + 43 + interface Props { 44 + embed: AppBskyEmbedRecord.ViewRecord; 45 + large?: boolean; 46 + } 47 + 48 + const { embed: quote, large = false }: Props = $props(); 49 + 50 + const record = quote.value as AppBskyFeedPost.Record; 51 + const text = record.text.trim(); 52 + 53 + const author = quote.author; 54 + const authorName = author.displayName?.trim(); 55 + 56 + const embed = quote.embeds?.[0]; 57 + const image = getPostImage(embed); 58 + const video = getPostVideo(embed); 59 + 60 + const postUrl = getPostUrl(author.did, parseAtUri(quote.uri).rkey); 61 + </script> 62 + 63 + <a target="_blank" href={postUrl} class="quote-embed"> 64 + <div class="meta"> 65 + <div class="avatar-wrapper"> 66 + {#if author.avatar} 67 + <img loading="lazy" src={author.avatar} alt="" class="avatar" /> 68 + {/if} 69 + </div> 70 + 71 + <span class="name-wrapper"> 72 + {#if authorName} 73 + <bdi class="display-name-wrapper"> 74 + <span class="display-name">{authorName}</span> 75 + </bdi> 76 + {/if} 77 + 78 + <span class="handle">@{author.handle}</span> 79 + </span> 80 + 81 + <span aria-hidden="true" class="dot">·</span> 82 + 83 + <time datetime={record.createdAt} class="date"> 84 + {formatShortDate(record.createdAt)} 85 + </time> 86 + </div> 87 + 88 + {#if text} 89 + <div class="body"> 90 + {#if !large} 91 + {#if image} 92 + <div class="aside"> 93 + <ImageEmbed embed={image} /> 94 + </div> 95 + {:else if video} 96 + <div class="aside"> 97 + <VideoEmbed embed={video} /> 98 + </div> 99 + {/if} 100 + {/if} 101 + 102 + <p class="text">{text}</p> 103 + </div> 104 + {:else} 105 + <div class="divide"></div> 106 + {/if} 107 + 108 + {#if large || !text} 109 + {#if image} 110 + <ImageEmbed embed={image} borderless /> 111 + {:else if video} 112 + <VideoEmbed embed={video} borderless /> 113 + {/if} 114 + {/if} 115 + </a> 116 + 117 + <style> 118 + .quote-embed { 119 + display: block; 120 + border: 1px solid var(--divider); 121 + border-radius: 6px; 122 + overflow: hidden; 123 + 124 + &:hover { 125 + border-color: var(--divider-hover); 126 + } 127 + } 128 + 129 + .meta { 130 + display: flex; 131 + padding: 12px 12px 0 12px; 132 + color: var(--text-secondary); 133 + 134 + .avatar-wrapper { 135 + flex-shrink: 0; 136 + margin: 0 8px 0 0; 137 + border-radius: 9999px; 138 + background: var(--background-secondary); 139 + width: 20px; 140 + height: 20px; 141 + overflow: hidden; 142 + } 143 + .avatar { 144 + width: 100%; 145 + height: 100%; 146 + } 147 + 148 + .name-wrapper { 149 + display: flex; 150 + gap: 4px; 151 + max-width: 100%; 152 + overflow: hidden; 153 + text-overflow: ellipsis; 154 + white-space: nowrap; 155 + } 156 + .display-name-wrapper { 157 + overflow: hidden; 158 + text-overflow: ellipsis; 159 + } 160 + .display-name { 161 + color: var(--text-primary); 162 + font-weight: 700; 163 + } 164 + .handle { 165 + display: block; 166 + overflow: hidden; 167 + text-overflow: ellipsis; 168 + white-space: nowrap; 169 + } 170 + 171 + .dot { 172 + flex-shrink: 0; 173 + margin: 0 6px; 174 + } 175 + 176 + .date { 177 + white-space: nowrap; 178 + } 179 + } 180 + 181 + .body { 182 + display: flex; 183 + align-items: flex-start; 184 + } 185 + 186 + .aside { 187 + flex-grow: 1; 188 + flex-basis: 0; 189 + margin: 8px 0 12px 12px; 190 + max-width: 20%; 191 + } 192 + 193 + .text { 194 + display: -webkit-box; 195 + margin: 8px 12px 12px 12px; 196 + overflow: hidden; 197 + -webkit-box-orient: vertical; 198 + -webkit-line-clamp: 6; 199 + line-clamp: 6; 200 + flex-grow: 4; 201 + flex-basis: 0px; 202 + min-width: 0px; 203 + white-space: pre-wrap; 204 + overflow-wrap: break-word; 205 + } 206 + 207 + .divide { 208 + padding: 6px 0; 209 + } 210 + </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>
+113
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 + } 13 + 14 + const { post, embed: video, borderless = false, standalone = false }: Props = $props(); 15 + 16 + const ratio = standalone && video.aspectRatio; 17 + 18 + const postUrl = post && getPostUrl(post.author.did, parseAtUri(post.uri).rkey); 19 + </script> 20 + 21 + {#if standalone} 22 + <a 23 + target="_blank" 24 + href={postUrl} 25 + class={`video-embed` + (!borderless ? ` is-bordered` : ``) + (standalone ? ` is-standalone` : ``)} 26 + > 27 + <div class="constrainer" style={ratio ? `aspect-ratio: ${ratio.width}/${ratio.height}` : ``}> 28 + {@render Content()} 29 + </div> 30 + </a> 31 + {:else} 32 + <div 33 + class={`video-embed` + (!borderless ? ` is-bordered` : ``)} 34 + style={ratio ? `aspect-ratio: ${ratio.width}/${ratio.height}` : ``} 35 + > 36 + {@render Content()} 37 + </div> 38 + {/if} 39 + 40 + {#snippet Content()} 41 + <img loading="lazy" src={video.thumbnail} alt="" class="thumbnail" /> 42 + 43 + {#if ratio} 44 + <div class="placeholder"></div> 45 + {/if} 46 + 47 + <div class="play"> 48 + <!-- play --> 49 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 50 + <path fill="currentColor" d="M22 12 5 2v20l17-10Z" /> 51 + </svg> 52 + </div> 53 + {/snippet} 54 + 55 + <style> 56 + .video-embed { 57 + display: block; 58 + position: relative; 59 + aspect-ratio: 16 / 9; 60 + overflow: hidden; 61 + } 62 + .is-bordered { 63 + border: 1px solid var(--divider); 64 + border-radius: 6px; 65 + } 66 + .is-standalone { 67 + align-self: baseline; 68 + aspect-ratio: auto; 69 + max-width: 100%; 70 + } 71 + 72 + .constrainer { 73 + min-width: 64px; 74 + max-width: 100%; 75 + min-height: 64px; 76 + max-height: 320px; 77 + } 78 + 79 + .thumbnail { 80 + width: 100%; 81 + height: 100%; 82 + object-fit: cover; 83 + } 84 + .placeholder { 85 + width: 100vw; 86 + height: 100vh; 87 + } 88 + 89 + .play { 90 + display: grid; 91 + position: absolute; 92 + top: 50%; 93 + left: 50%; 94 + place-items: center; 95 + translate: -50% -50%; 96 + border-radius: 50%; 97 + background: rgba(64, 64, 64, 0.6); 98 + aspect-ratio: 1 / 1; 99 + height: 40%; 100 + max-height: 48px; 101 + color: #ffffff; 102 + font-size: 20px; 103 + 104 + .icon { 105 + width: 40%; 106 + height: 40%; 107 + } 108 + 109 + .is-standalone &:hover { 110 + background: rgba(64, 64, 64, 0.8); 111 + } 112 + } 113 + </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="flex min-w-0">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: 0.8125rem; 395 + line-height: 1.25rem; 396 + text-overflow: ellipsis; 397 + white-space: nowrap; 398 + } 399 + } 400 + </style>
+244
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 { formatCompactNumber, formatLongNumber } from '../utils/number'; 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 = false }: 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 replyCount = post.replyCount || 0; 28 + const likeCount = post.likeCount || 0; 29 + const repostCount = (post.repostCount || 0) + (post.quoteCount || 0); 30 + </script> 31 + 32 + <div class="highlighted-post"> 33 + <div class="meta"> 34 + <a href={authorUrl} target="_blank" class="avatar-wrapper"> 35 + {#if author.avatar} 36 + <!-- svelte-ignore a11y_missing_attribute --> 37 + <img loading="lazy" src={author.avatar} class="avatar" /> 38 + {/if} 39 + </a> 40 + 41 + <a href={authorUrl} target="_blank" class="name-wrapper"> 42 + {#if authorName} 43 + <bdi class="display-name-wrapper"> 44 + <span class="display-name">{authorName}</span> 45 + </bdi> 46 + {/if} 47 + <span class="handle">@{author.handle}</span> 48 + </a> 49 + </div> 50 + 51 + {#if !prev && record.reply} 52 + <p class="context"> 53 + {#if parent} 54 + {@const author = parent.author} 55 + 56 + Replying to 57 + <a target="_blank" href={getProfileUrl(author.did)} dir="auto"> 58 + {author.displayName?.trim() || `@${author.handle}`} 59 + </a> 60 + {:else} 61 + Replying to an unknown post 62 + {/if} 63 + </p> 64 + {/if} 65 + 66 + <RichTextRenderer text={record.text} facets={record.facets} large /> 67 + 68 + {#if post.embed} 69 + <Embeds {post} embed={post.embed} large /> 70 + {/if} 71 + 72 + <time datetime={record.createdAt} class="date"> 73 + {formatLongDate(record.createdAt)} 74 + </time> 75 + 76 + <div class="stats"> 77 + <span 78 + class="stat" 79 + title={likeCount === 1 ? `${formatLongNumber(likeCount)} like` : `${formatLongNumber(likeCount)} likes`} 80 + > 81 + <!-- heart-2 --> 82 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 83 + <path 84 + stroke="currentColor" 85 + stroke-width="2" 86 + d="M12 5.768c6.162-6.25 16.725 5.358 0 14.732C-4.725 11.126 5.838-.482 12 5.768Z" 87 + /> 88 + </svg> 89 + 90 + <span>{formatCompactNumber(likeCount)}</span> 91 + </span> 92 + 93 + <span 94 + class="stat" 95 + title={repostCount === 1 96 + ? `${formatLongNumber(repostCount)} repost` 97 + : `${formatLongNumber(repostCount)} reposts`} 98 + > 99 + <!-- arrows-repeat-right-left --> 100 + <svg class="icon" fill="none" viewBox="0 0 24 24"> 101 + <path 102 + stroke="currentColor" 103 + stroke-linecap="square" 104 + stroke-width="2" 105 + d="m17 3 3 3-3 3M7 21l-3-3 3-3m-2 3h15v-5M4 11V6h15" 106 + /> 107 + </svg> 108 + 109 + <span>{formatCompactNumber(repostCount)}</span> 110 + </span> 111 + 112 + <div class="gap"></div> 113 + 114 + <a href={postUrl} target="_blank" class="permalink"> 115 + <span> 116 + {!replyCount 117 + ? `View on Bluesky` 118 + : replyCount === 1 119 + ? `Read ${formatCompactNumber(replyCount)} reply on Bluesky` 120 + : `Read ${formatCompactNumber(replyCount)} replies on Bluesky`} 121 + </span> 122 + </a> 123 + </div> 124 + </div> 125 + 126 + <style> 127 + .highlighted-post { 128 + padding: 16px; 129 + } 130 + 131 + .meta { 132 + display: flex; 133 + align-items: center; 134 + margin: 0 0 12px 0; 135 + color: var(--text-secondary); 136 + 137 + .avatar-wrapper { 138 + display: block; 139 + flex-shrink: 0; 140 + margin: 0 12px 0 0; 141 + border-radius: 9999px; 142 + background: var(--background-secondary); 143 + width: 40px; 144 + height: 40px; 145 + overflow: hidden; 146 + 147 + &:hover { 148 + filter: brightness(0.85); 149 + } 150 + } 151 + .avatar { 152 + width: 100%; 153 + height: 100%; 154 + object-fit: cover; 155 + } 156 + 157 + .name-wrapper { 158 + display: block; 159 + max-width: 100%; 160 + overflow: hidden; 161 + color: inherit; 162 + text-overflow: ellipsis; 163 + white-space: nowrap; 164 + } 165 + .display-name-wrapper { 166 + overflow: hidden; 167 + text-overflow: ellipsis; 168 + 169 + .name-wrapper:hover & { 170 + text-decoration: underline; 171 + } 172 + } 173 + .display-name { 174 + color: var(--text-primary); 175 + font-weight: 700; 176 + } 177 + .handle { 178 + display: block; 179 + overflow: hidden; 180 + text-overflow: ellipsis; 181 + white-space: nowrap; 182 + } 183 + } 184 + 185 + .context { 186 + overflow: hidden; 187 + color: var(--text-secondary); 188 + font-size: calc(var(--font-size) * 0.8125); 189 + text-overflow: ellipsis; 190 + white-space: nowrap; 191 + 192 + a { 193 + color: inherit; 194 + font-weight: 500; 195 + 196 + &:hover { 197 + text-decoration: underline; 198 + } 199 + } 200 + } 201 + 202 + .date { 203 + display: flex; 204 + flex-wrap: wrap; 205 + align-items: center; 206 + gap: 8px; 207 + margin: 12px 0 0; 208 + border-bottom: 1px solid var(--divider); 209 + padding: 0 0 12px 0; 210 + color: var(--text-secondary); 211 + } 212 + 213 + .stats { 214 + display: flex; 215 + flex-wrap: wrap; 216 + align-items: center; 217 + gap: 8px 16px; 218 + margin: 0 0 -16px 0; 219 + padding: 12px 0; 220 + color: var(--text-secondary); 221 + 222 + .gap { 223 + flex: 1 1 auto; 224 + } 225 + 226 + .permalink { 227 + display: flex; 228 + align-items: center; 229 + gap: 4px; 230 + color: var(--text-link); 231 + font-weight: 700; 232 + 233 + &:hover { 234 + text-decoration: underline; 235 + } 236 + } 237 + } 238 + .stat { 239 + display: flex; 240 + align-items: center; 241 + gap: 8px; 242 + font-weight: 500; 243 + } 244 + </style>
+196
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 { parseAtUri } from '../utils/syntax/at-url'; 7 + 8 + import Embeds from './embeds/embeds.svelte'; 9 + import RichtextRenderer from './richtext-renderer.svelte'; 10 + 11 + interface Props { 12 + post: AppBskyFeedDefs.PostView; 13 + parent?: AppBskyFeedDefs.PostView | null; 14 + prev?: boolean; 15 + } 16 + 17 + const { post, parent, prev }: Props = $props(); 18 + 19 + const author = post.author; 20 + const authorUrl = getProfileUrl(author.did); 21 + const authorName = author.displayName?.trim(); 22 + 23 + const record = post.record as AppBskyFeedPost.Record; 24 + const postUrl = getPostUrl(author.did, parseAtUri(post.uri).rkey); 25 + </script> 26 + 27 + <div class="post"> 28 + <div class="aside"> 29 + <a target="_blank" href={authorUrl} class="avatar-wrapper"> 30 + {#if author.avatar} 31 + <img loading="lazy" src={author.avatar} alt="" class="avatar" /> 32 + {/if} 33 + </a> 34 + 35 + <div class="line"></div> 36 + </div> 37 + 38 + <div class="main"> 39 + <div class="meta"> 40 + <a href={authorUrl} target="_blank" class="name-wrapper"> 41 + {#if authorName} 42 + <bdi class="display-name-wrapper"> 43 + <span class="display-name">{authorName}</span> 44 + </bdi> 45 + {/if} 46 + 47 + <span class="handle">@{author.handle}</span> 48 + </a> 49 + 50 + <span aria-hidden="true" class="dot"> · </span> 51 + 52 + <a target="_blank" href={postUrl} title={formatLongDate(record.createdAt)} class="date"> 53 + <time datetime={record.createdAt}>{formatShortDate(record.createdAt)}</time> 54 + </a> 55 + </div> 56 + 57 + {#if !prev && record.reply} 58 + <p class="context"> 59 + {#if parent} 60 + {@const author = parent.author} 61 + 62 + Replying to 63 + <a target="_blank" href={getProfileUrl(author.did)} dir="auto"> 64 + {author.displayName?.trim() || `@${author.handle}`} 65 + </a> 66 + {:else} 67 + Replying to an unknown post 68 + {/if} 69 + </p> 70 + {/if} 71 + 72 + <RichtextRenderer text={record.text} facets={record.facets} /> 73 + 74 + {#if post.embed} 75 + <Embeds {post} embed={post.embed} /> 76 + {/if} 77 + </div> 78 + </div> 79 + 80 + <style> 81 + .post { 82 + display: flex; 83 + position: relative; 84 + gap: 12px; 85 + padding: 12px 16px 0 16px; 86 + } 87 + 88 + .aside { 89 + flex-shrink: 0; 90 + } 91 + 92 + .avatar-wrapper { 93 + display: block; 94 + border-radius: 9999px; 95 + background: var(--background-secondary); 96 + width: 40px; 97 + height: 40px; 98 + overflow: hidden; 99 + 100 + &:hover { 101 + filter: brightness(0.85); 102 + } 103 + } 104 + 105 + .avatar { 106 + width: 100%; 107 + height: 100%; 108 + object-fit: cover; 109 + } 110 + 111 + .line { 112 + position: absolute; 113 + top: 56px; 114 + bottom: -12px; 115 + left: 35px; 116 + border-left: 2px solid var(--divider); 117 + } 118 + 119 + .main { 120 + display: flex; 121 + flex-grow: 1; 122 + flex-direction: column; 123 + min-width: 0px; 124 + } 125 + 126 + .meta { 127 + display: flex; 128 + align-items: center; 129 + margin: 0 0 2px 0; 130 + color: var(--text-secondary); 131 + 132 + .name-wrapper { 133 + display: flex; 134 + gap: 4px; 135 + max-width: 100%; 136 + overflow: hidden; 137 + color: inherit; 138 + text-decoration: none; 139 + text-overflow: ellipsis; 140 + white-space: nowrap; 141 + } 142 + 143 + .display-name-wrapper { 144 + overflow: hidden; 145 + text-overflow: ellipsis; 146 + 147 + .name-wrapper:hover & { 148 + text-decoration: underline; 149 + } 150 + } 151 + 152 + .display-name { 153 + color: var(--text-primary); 154 + font-weight: 700; 155 + } 156 + 157 + .handle { 158 + display: block; 159 + overflow: hidden; 160 + text-overflow: ellipsis; 161 + white-space: nowrap; 162 + } 163 + 164 + .dot { 165 + flex-shrink: 0; 166 + margin: 0 6px; 167 + } 168 + 169 + .date { 170 + color: inherit; 171 + text-decoration: none; 172 + white-space: nowrap; 173 + 174 + &:hover { 175 + text-decoration: underline; 176 + } 177 + } 178 + } 179 + 180 + .context { 181 + overflow: hidden; 182 + color: var(--text-secondary); 183 + font-size: calc(var(--font-size) * 0.8125); 184 + text-overflow: ellipsis; 185 + white-space: nowrap; 186 + 187 + a { 188 + color: inherit; 189 + font-weight: 500; 190 + 191 + &:hover { 192 + text-decoration: underline; 193 + } 194 + } 195 + } 196 + </style>
+67
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 + <a target="_blank" href={getProfileUrl(profile.did)} class="follow-button">Follow on Bluesky</a> 17 + </div> 18 + 19 + <style> 20 + .profile-feed-header { 21 + display: flex; 22 + justify-content: space-between; 23 + align-items: center; 24 + gap: 16px; 25 + container-type: inline-size; 26 + border-bottom: 1px solid var(--divider); 27 + padding: 12px 16px; 28 + } 29 + 30 + .title { 31 + padding: 4px 0; 32 + min-width: 0; 33 + overflow: hidden; 34 + font-weight: 600; 35 + font-size: calc(var(--font-size) * 1); 36 + line-height: calc(var(--font-size) * 1.5); 37 + text-overflow: ellipsis; 38 + white-space: nowrap; 39 + 40 + &:hover { 41 + text-decoration: underline; 42 + } 43 + } 44 + 45 + .follow-button { 46 + display: flex; 47 + flex-shrink: 0; 48 + justify-content: center; 49 + align-items: center; 50 + margin: 0 -4px 0 0; 51 + border-radius: 6px; 52 + background: var(--button); 53 + padding: 0 16px; 54 + height: 32px; 55 + color: var(--button-text); 56 + font-weight: 500; 57 + line-height: 1; 58 + 59 + &:hover { 60 + background: var(--button-hover); 61 + } 62 + 63 + @container (max-width: 360px) { 64 + display: none; 65 + } 66 + } 67 + </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>
+24
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-segmenter": "^1.0.5", 14 + "@atcute/client": "^2.0.6", 15 + "svelte": "^5.3.1" 16 + }, 17 + "devDependencies": { 18 + "@atcute/bluesky": "^1.0.9", 19 + "@atcute/bluesky-richtext-segmenter": "^1.0.5", 20 + "@atcute/client": "^2.0.6", 21 + "@tsconfig/svelte": "^5.0.4", 22 + "svelte": "^5.3.1" 23 + } 24 + }
+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 + }
+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 + };
+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 {}
+86
patches/svelte.patch
··· 1 + diff --git a/src/internal/server/index.js b/src/internal/server/index.js 2 + index 615a49fbd4c0279b1b4c28c2845293001922307e..13d7cd09da61d418337fd73b08007b549809b049 100644 3 + --- a/src/internal/server/index.js 4 + +++ b/src/internal/server/index.js 5 + @@ -94,50 +94,13 @@ export let on_destroy = []; 6 + * @param {{ props?: Omit<Props, '$$slots' | '$$events'>; context?: Map<any, any> }} [options] 7 + * @returns {RenderOutput} 8 + */ 9 + -export function render(component, options = {}) { 10 + +export function render(component, options) { 11 + /** @type {Payload} */ 12 + - const payload = { out: '', css: new Set(), head: { title: '', out: '' } }; 13 + - 14 + - const prev_on_destroy = on_destroy; 15 + - on_destroy = []; 16 + - payload.out += BLOCK_OPEN; 17 + - 18 + - let reset_reset_element; 19 + - 20 + - if (DEV) { 21 + - // prevent parent/child element state being corrupted by a bad render 22 + - reset_reset_element = reset_elements(); 23 + - } 24 + - 25 + - if (options.context) { 26 + - push(); 27 + - /** @type {Component} */ (current_component).c = options.context; 28 + - } 29 + - 30 + - // @ts-expect-error 31 + - component(payload, options.props ?? {}, {}, {}); 32 + - 33 + - if (options.context) { 34 + - pop(); 35 + - } 36 + - 37 + - if (reset_reset_element) { 38 + - reset_reset_element(); 39 + - } 40 + - 41 + - payload.out += BLOCK_CLOSE; 42 + - for (const cleanup of on_destroy) cleanup(); 43 + - on_destroy = prev_on_destroy; 44 + - 45 + - let head = payload.head.out + payload.head.title; 46 + - 47 + - for (const { hash, code } of payload.css) { 48 + - head += `<style id="${hash}">${code}</style>`; 49 + - } 50 + + const payload = { out: '' }; 51 + + component(payload, options?.props ?? {}); 52 + 53 + return { 54 + - head, 55 + - html: payload.out, 56 + + head: '', 57 + body: payload.out 58 + }; 59 + } 60 + @@ -492,12 +455,7 @@ export { await_block as await }; 61 + 62 + /** @param {any} array_like_or_iterator */ 63 + export function ensure_array_like(array_like_or_iterator) { 64 + - if (array_like_or_iterator) { 65 + - return array_like_or_iterator.length !== undefined 66 + - ? array_like_or_iterator 67 + - : Array.from(array_like_or_iterator); 68 + - } 69 + - return []; 70 + + return array_like_or_iterator; 71 + } 72 + 73 + /** 74 + diff --git a/src/internal/shared/attributes.js b/src/internal/shared/attributes.js 75 + index 867d6ba5d37888ecad4c7b7c92e07b96bed92dca..fbe57a65b1d1edb10d6a7555cc32a394c7dedf9b 100644 76 + --- a/src/internal/shared/attributes.js 77 + +++ b/src/internal/shared/attributes.js 78 + @@ -22,7 +22,7 @@ const replacements = { 79 + */ 80 + export function attr(name, value, is_boolean = false) { 81 + if (value == null || (!value && is_boolean) || (value === '' && name === 'class')) return ''; 82 + - const normalized = (name in replacements && replacements[name].get(value)) || value; 83 + + const normalized = value; 84 + const assignment = is_boolean ? '' : `="${escape_html(normalized, true)}"`; 85 + return ` ${name}${assignment}`; 86 + }
+1579
pnpm-lock.yaml
··· 1 + lockfileVersion: '9.0' 2 + 3 + settings: 4 + autoInstallPeers: true 5 + excludeLinksFromLockfile: false 6 + 7 + patchedDependencies: 8 + svelte: 9 + hash: 6qynve6ufonlwufsl6x7wujmdu 10 + path: patches/svelte.patch 11 + 12 + importers: 13 + 14 + .: 15 + devDependencies: 16 + prettier: 17 + specifier: ^3.4.1 18 + version: 3.4.1 19 + prettier-plugin-css-order: 20 + specifier: ^2.1.2 21 + version: 2.1.2(postcss@8.4.49)(prettier@3.4.1) 22 + prettier-plugin-svelte: 23 + specifier: ^3.3.2 24 + version: 3.3.2(prettier@3.4.1)(svelte@5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu)) 25 + typescript: 26 + specifier: ^5.7.2 27 + version: 5.7.2 28 + 29 + packages/bluesky-post-embed: 30 + dependencies: 31 + '@atcute/bluesky': 32 + specifier: ^1.0.9 33 + version: 1.0.9(@atcute/client@2.0.6) 34 + '@atcute/bluesky-richtext-segmenter': 35 + specifier: ^1.0.5 36 + version: 1.0.5(@atcute/bluesky@1.0.9(@atcute/client@2.0.6))(@atcute/client@2.0.6) 37 + '@atcute/client': 38 + specifier: ^2.0.6 39 + version: 2.0.6 40 + svelte: 41 + specifier: ^5.3.1 42 + version: 5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu) 43 + devDependencies: 44 + '@tsconfig/svelte': 45 + specifier: ^5.0.4 46 + version: 5.0.4 47 + '@types/node': 48 + specifier: ^22.10.1 49 + version: 22.10.1 50 + internal: 51 + specifier: workspace:^ 52 + version: link:../internal 53 + svelte-check: 54 + specifier: ^4.1.0 55 + version: 4.1.0(picomatch@4.0.2)(svelte@5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu))(typescript@5.7.2) 56 + vite: 57 + specifier: ^6.0.2 58 + version: 6.0.2(@types/node@22.10.1) 59 + vite-plugin-dts: 60 + specifier: ^4.3.0 61 + version: 4.3.0(@types/node@22.10.1)(rollup@4.28.0)(typescript@5.7.2)(vite@6.0.2(@types/node@22.10.1)) 62 + 63 + packages/bluesky-profile-feed-embed: 64 + dependencies: 65 + '@atcute/bluesky': 66 + specifier: ^1.0.9 67 + version: 1.0.9(@atcute/client@2.0.6) 68 + '@atcute/bluesky-richtext-segmenter': 69 + specifier: ^1.0.5 70 + version: 1.0.5(@atcute/bluesky@1.0.9(@atcute/client@2.0.6))(@atcute/client@2.0.6) 71 + '@atcute/client': 72 + specifier: ^2.0.6 73 + version: 2.0.6 74 + svelte: 75 + specifier: ^5.3.1 76 + version: 5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu) 77 + devDependencies: 78 + '@tsconfig/svelte': 79 + specifier: ^5.0.4 80 + version: 5.0.4 81 + '@types/node': 82 + specifier: ^22.10.1 83 + version: 22.10.1 84 + internal: 85 + specifier: workspace:^ 86 + version: link:../internal 87 + svelte-check: 88 + specifier: ^4.1.0 89 + version: 4.1.0(picomatch@4.0.2)(svelte@5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu))(typescript@5.7.2) 90 + vite: 91 + specifier: ^6.0.2 92 + version: 6.0.2(@types/node@22.10.1) 93 + vite-plugin-dts: 94 + specifier: ^4.3.0 95 + version: 4.3.0(@types/node@22.10.1)(rollup@4.28.0)(typescript@5.7.2)(vite@6.0.2(@types/node@22.10.1)) 96 + 97 + packages/internal: 98 + devDependencies: 99 + '@atcute/bluesky': 100 + specifier: ^1.0.9 101 + version: 1.0.9(@atcute/client@2.0.6) 102 + '@atcute/bluesky-richtext-segmenter': 103 + specifier: ^1.0.5 104 + version: 1.0.5(@atcute/bluesky@1.0.9(@atcute/client@2.0.6))(@atcute/client@2.0.6) 105 + '@atcute/client': 106 + specifier: ^2.0.6 107 + version: 2.0.6 108 + '@tsconfig/svelte': 109 + specifier: ^5.0.4 110 + version: 5.0.4 111 + svelte: 112 + specifier: ^5.3.1 113 + version: 5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu) 114 + 115 + packages: 116 + 117 + '@ampproject/remapping@2.3.0': 118 + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 119 + engines: {node: '>=6.0.0'} 120 + 121 + '@atcute/bluesky-richtext-segmenter@1.0.5': 122 + resolution: {integrity: sha512-D0FfmJVwppky9naL1OGKcQjtgO0lDLhkG4iCQHpShuHhEZ9FUdf3eUb/eQpVRNJGaJ4ShjEOHA6FlAizcjMkGQ==} 123 + peerDependencies: 124 + '@atcute/bluesky': ^1.0.0 125 + '@atcute/client': ^1.0.0 || ^2.0.0 126 + 127 + '@atcute/bluesky@1.0.9': 128 + resolution: {integrity: sha512-06UbqlnREobZB5vVlstJXsJJVNBPr/RhauVVWQk9k8eIfzyiV9uxklc5olv+wULld+iBL6OQItnTEyZPv8QFLw==} 129 + peerDependencies: 130 + '@atcute/client': ^1.0.0 || ^2.0.0 131 + 132 + '@atcute/client@2.0.6': 133 + resolution: {integrity: sha512-mhdqEicGUx0s5HTFOLpz91rcLS9j/g63de0nmAqv7blhU3j+xBf4le54qr2YIXNfnReZI7EwLYLX/YIBez4LGA==} 134 + 135 + '@babel/helper-string-parser@7.25.9': 136 + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} 137 + engines: {node: '>=6.9.0'} 138 + 139 + '@babel/helper-validator-identifier@7.25.9': 140 + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} 141 + engines: {node: '>=6.9.0'} 142 + 143 + '@babel/parser@7.26.2': 144 + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} 145 + engines: {node: '>=6.0.0'} 146 + hasBin: true 147 + 148 + '@babel/types@7.26.0': 149 + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} 150 + engines: {node: '>=6.9.0'} 151 + 152 + '@esbuild/aix-ppc64@0.24.0': 153 + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} 154 + engines: {node: '>=18'} 155 + cpu: [ppc64] 156 + os: [aix] 157 + 158 + '@esbuild/android-arm64@0.24.0': 159 + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} 160 + engines: {node: '>=18'} 161 + cpu: [arm64] 162 + os: [android] 163 + 164 + '@esbuild/android-arm@0.24.0': 165 + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} 166 + engines: {node: '>=18'} 167 + cpu: [arm] 168 + os: [android] 169 + 170 + '@esbuild/android-x64@0.24.0': 171 + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} 172 + engines: {node: '>=18'} 173 + cpu: [x64] 174 + os: [android] 175 + 176 + '@esbuild/darwin-arm64@0.24.0': 177 + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} 178 + engines: {node: '>=18'} 179 + cpu: [arm64] 180 + os: [darwin] 181 + 182 + '@esbuild/darwin-x64@0.24.0': 183 + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} 184 + engines: {node: '>=18'} 185 + cpu: [x64] 186 + os: [darwin] 187 + 188 + '@esbuild/freebsd-arm64@0.24.0': 189 + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} 190 + engines: {node: '>=18'} 191 + cpu: [arm64] 192 + os: [freebsd] 193 + 194 + '@esbuild/freebsd-x64@0.24.0': 195 + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} 196 + engines: {node: '>=18'} 197 + cpu: [x64] 198 + os: [freebsd] 199 + 200 + '@esbuild/linux-arm64@0.24.0': 201 + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} 202 + engines: {node: '>=18'} 203 + cpu: [arm64] 204 + os: [linux] 205 + 206 + '@esbuild/linux-arm@0.24.0': 207 + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} 208 + engines: {node: '>=18'} 209 + cpu: [arm] 210 + os: [linux] 211 + 212 + '@esbuild/linux-ia32@0.24.0': 213 + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} 214 + engines: {node: '>=18'} 215 + cpu: [ia32] 216 + os: [linux] 217 + 218 + '@esbuild/linux-loong64@0.24.0': 219 + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} 220 + engines: {node: '>=18'} 221 + cpu: [loong64] 222 + os: [linux] 223 + 224 + '@esbuild/linux-mips64el@0.24.0': 225 + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} 226 + engines: {node: '>=18'} 227 + cpu: [mips64el] 228 + os: [linux] 229 + 230 + '@esbuild/linux-ppc64@0.24.0': 231 + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} 232 + engines: {node: '>=18'} 233 + cpu: [ppc64] 234 + os: [linux] 235 + 236 + '@esbuild/linux-riscv64@0.24.0': 237 + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} 238 + engines: {node: '>=18'} 239 + cpu: [riscv64] 240 + os: [linux] 241 + 242 + '@esbuild/linux-s390x@0.24.0': 243 + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} 244 + engines: {node: '>=18'} 245 + cpu: [s390x] 246 + os: [linux] 247 + 248 + '@esbuild/linux-x64@0.24.0': 249 + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} 250 + engines: {node: '>=18'} 251 + cpu: [x64] 252 + os: [linux] 253 + 254 + '@esbuild/netbsd-x64@0.24.0': 255 + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} 256 + engines: {node: '>=18'} 257 + cpu: [x64] 258 + os: [netbsd] 259 + 260 + '@esbuild/openbsd-arm64@0.24.0': 261 + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} 262 + engines: {node: '>=18'} 263 + cpu: [arm64] 264 + os: [openbsd] 265 + 266 + '@esbuild/openbsd-x64@0.24.0': 267 + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} 268 + engines: {node: '>=18'} 269 + cpu: [x64] 270 + os: [openbsd] 271 + 272 + '@esbuild/sunos-x64@0.24.0': 273 + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} 274 + engines: {node: '>=18'} 275 + cpu: [x64] 276 + os: [sunos] 277 + 278 + '@esbuild/win32-arm64@0.24.0': 279 + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} 280 + engines: {node: '>=18'} 281 + cpu: [arm64] 282 + os: [win32] 283 + 284 + '@esbuild/win32-ia32@0.24.0': 285 + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} 286 + engines: {node: '>=18'} 287 + cpu: [ia32] 288 + os: [win32] 289 + 290 + '@esbuild/win32-x64@0.24.0': 291 + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} 292 + engines: {node: '>=18'} 293 + cpu: [x64] 294 + os: [win32] 295 + 296 + '@jridgewell/gen-mapping@0.3.5': 297 + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 298 + engines: {node: '>=6.0.0'} 299 + 300 + '@jridgewell/resolve-uri@3.1.2': 301 + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 302 + engines: {node: '>=6.0.0'} 303 + 304 + '@jridgewell/set-array@1.2.1': 305 + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 306 + engines: {node: '>=6.0.0'} 307 + 308 + '@jridgewell/sourcemap-codec@1.5.0': 309 + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 310 + 311 + '@jridgewell/trace-mapping@0.3.25': 312 + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 313 + 314 + '@microsoft/api-extractor-model@7.30.0': 315 + resolution: {integrity: sha512-26/LJZBrsWDKAkOWRiQbdVgcfd1F3nyJnAiJzsAgpouPk7LtOIj7PK9aJtBaw/pUXrkotEg27RrT+Jm/q0bbug==} 316 + 317 + '@microsoft/api-extractor@7.48.0': 318 + resolution: {integrity: sha512-FMFgPjoilMUWeZXqYRlJ3gCVRhB7WU/HN88n8OLqEsmsG4zBdX/KQdtJfhq95LQTQ++zfu0Em1LLb73NqRCLYQ==} 319 + hasBin: true 320 + 321 + '@microsoft/tsdoc-config@0.17.1': 322 + resolution: {integrity: sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==} 323 + 324 + '@microsoft/tsdoc@0.15.1': 325 + resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} 326 + 327 + '@rollup/pluginutils@5.1.3': 328 + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} 329 + engines: {node: '>=14.0.0'} 330 + peerDependencies: 331 + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 332 + peerDependenciesMeta: 333 + rollup: 334 + optional: true 335 + 336 + '@rollup/rollup-android-arm-eabi@4.28.0': 337 + resolution: {integrity: sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==} 338 + cpu: [arm] 339 + os: [android] 340 + 341 + '@rollup/rollup-android-arm64@4.28.0': 342 + resolution: {integrity: sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==} 343 + cpu: [arm64] 344 + os: [android] 345 + 346 + '@rollup/rollup-darwin-arm64@4.28.0': 347 + resolution: {integrity: sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==} 348 + cpu: [arm64] 349 + os: [darwin] 350 + 351 + '@rollup/rollup-darwin-x64@4.28.0': 352 + resolution: {integrity: sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==} 353 + cpu: [x64] 354 + os: [darwin] 355 + 356 + '@rollup/rollup-freebsd-arm64@4.28.0': 357 + resolution: {integrity: sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==} 358 + cpu: [arm64] 359 + os: [freebsd] 360 + 361 + '@rollup/rollup-freebsd-x64@4.28.0': 362 + resolution: {integrity: sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==} 363 + cpu: [x64] 364 + os: [freebsd] 365 + 366 + '@rollup/rollup-linux-arm-gnueabihf@4.28.0': 367 + resolution: {integrity: sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==} 368 + cpu: [arm] 369 + os: [linux] 370 + 371 + '@rollup/rollup-linux-arm-musleabihf@4.28.0': 372 + resolution: {integrity: sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==} 373 + cpu: [arm] 374 + os: [linux] 375 + 376 + '@rollup/rollup-linux-arm64-gnu@4.28.0': 377 + resolution: {integrity: sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==} 378 + cpu: [arm64] 379 + os: [linux] 380 + 381 + '@rollup/rollup-linux-arm64-musl@4.28.0': 382 + resolution: {integrity: sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==} 383 + cpu: [arm64] 384 + os: [linux] 385 + 386 + '@rollup/rollup-linux-powerpc64le-gnu@4.28.0': 387 + resolution: {integrity: sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==} 388 + cpu: [ppc64] 389 + os: [linux] 390 + 391 + '@rollup/rollup-linux-riscv64-gnu@4.28.0': 392 + resolution: {integrity: sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==} 393 + cpu: [riscv64] 394 + os: [linux] 395 + 396 + '@rollup/rollup-linux-s390x-gnu@4.28.0': 397 + resolution: {integrity: sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==} 398 + cpu: [s390x] 399 + os: [linux] 400 + 401 + '@rollup/rollup-linux-x64-gnu@4.28.0': 402 + resolution: {integrity: sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==} 403 + cpu: [x64] 404 + os: [linux] 405 + 406 + '@rollup/rollup-linux-x64-musl@4.28.0': 407 + resolution: {integrity: sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==} 408 + cpu: [x64] 409 + os: [linux] 410 + 411 + '@rollup/rollup-win32-arm64-msvc@4.28.0': 412 + resolution: {integrity: sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==} 413 + cpu: [arm64] 414 + os: [win32] 415 + 416 + '@rollup/rollup-win32-ia32-msvc@4.28.0': 417 + resolution: {integrity: sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==} 418 + cpu: [ia32] 419 + os: [win32] 420 + 421 + '@rollup/rollup-win32-x64-msvc@4.28.0': 422 + resolution: {integrity: sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==} 423 + cpu: [x64] 424 + os: [win32] 425 + 426 + '@rushstack/node-core-library@5.10.0': 427 + resolution: {integrity: sha512-2pPLCuS/3x7DCd7liZkqOewGM0OzLyCacdvOe8j6Yrx9LkETGnxul1t7603bIaB8nUAooORcct9fFDOQMbWAgw==} 428 + peerDependencies: 429 + '@types/node': '*' 430 + peerDependenciesMeta: 431 + '@types/node': 432 + optional: true 433 + 434 + '@rushstack/rig-package@0.5.3': 435 + resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==} 436 + 437 + '@rushstack/terminal@0.14.3': 438 + resolution: {integrity: sha512-csXbZsAdab/v8DbU1sz7WC2aNaKArcdS/FPmXMOXEj/JBBZMvDK0+1b4Qao0kkG0ciB1Qe86/Mb68GjH6/TnMw==} 439 + peerDependencies: 440 + '@types/node': '*' 441 + peerDependenciesMeta: 442 + '@types/node': 443 + optional: true 444 + 445 + '@rushstack/ts-command-line@4.23.1': 446 + resolution: {integrity: sha512-40jTmYoiu/xlIpkkRsVfENtBq4CW3R4azbL0Vmda+fMwHWqss6wwf/Cy/UJmMqIzpfYc2OTnjYP1ZLD3CmyeCA==} 447 + 448 + '@tsconfig/svelte@5.0.4': 449 + resolution: {integrity: sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q==} 450 + 451 + '@types/argparse@1.0.38': 452 + resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} 453 + 454 + '@types/estree@1.0.6': 455 + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 456 + 457 + '@types/node@22.10.1': 458 + resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} 459 + 460 + '@volar/language-core@2.4.10': 461 + resolution: {integrity: sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==} 462 + 463 + '@volar/source-map@2.4.10': 464 + resolution: {integrity: sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==} 465 + 466 + '@volar/typescript@2.4.10': 467 + resolution: {integrity: sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==} 468 + 469 + '@vue/compiler-core@3.5.13': 470 + resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} 471 + 472 + '@vue/compiler-dom@3.5.13': 473 + resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} 474 + 475 + '@vue/compiler-vue2@2.7.16': 476 + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} 477 + 478 + '@vue/language-core@2.1.6': 479 + resolution: {integrity: sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==} 480 + peerDependencies: 481 + typescript: '*' 482 + peerDependenciesMeta: 483 + typescript: 484 + optional: true 485 + 486 + '@vue/shared@3.5.13': 487 + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} 488 + 489 + acorn-typescript@1.4.13: 490 + resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==} 491 + peerDependencies: 492 + acorn: '>=8.9.0' 493 + 494 + acorn@8.14.0: 495 + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} 496 + engines: {node: '>=0.4.0'} 497 + hasBin: true 498 + 499 + ajv-draft-04@1.0.0: 500 + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} 501 + peerDependencies: 502 + ajv: ^8.5.0 503 + peerDependenciesMeta: 504 + ajv: 505 + optional: true 506 + 507 + ajv-formats@3.0.1: 508 + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} 509 + peerDependencies: 510 + ajv: ^8.0.0 511 + peerDependenciesMeta: 512 + ajv: 513 + optional: true 514 + 515 + ajv@8.12.0: 516 + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} 517 + 518 + ajv@8.13.0: 519 + resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} 520 + 521 + argparse@1.0.10: 522 + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} 523 + 524 + aria-query@5.3.2: 525 + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} 526 + engines: {node: '>= 0.4'} 527 + 528 + axobject-query@4.1.0: 529 + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} 530 + engines: {node: '>= 0.4'} 531 + 532 + balanced-match@1.0.2: 533 + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 534 + 535 + brace-expansion@1.1.11: 536 + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 537 + 538 + brace-expansion@2.0.1: 539 + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 540 + 541 + chokidar@4.0.1: 542 + resolution: {integrity: sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==} 543 + engines: {node: '>= 14.16.0'} 544 + 545 + compare-versions@6.1.1: 546 + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} 547 + 548 + computeds@0.0.1: 549 + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} 550 + 551 + concat-map@0.0.1: 552 + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 553 + 554 + confbox@0.1.8: 555 + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} 556 + 557 + css-declaration-sorter@7.2.0: 558 + resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} 559 + engines: {node: ^14 || ^16 || >=18} 560 + peerDependencies: 561 + postcss: ^8.0.9 562 + 563 + de-indent@1.0.2: 564 + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} 565 + 566 + debug@4.3.7: 567 + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} 568 + engines: {node: '>=6.0'} 569 + peerDependencies: 570 + supports-color: '*' 571 + peerDependenciesMeta: 572 + supports-color: 573 + optional: true 574 + 575 + entities@4.5.0: 576 + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} 577 + engines: {node: '>=0.12'} 578 + 579 + esbuild@0.24.0: 580 + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} 581 + engines: {node: '>=18'} 582 + hasBin: true 583 + 584 + esm-env@1.2.1: 585 + resolution: {integrity: sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==} 586 + 587 + esrap@1.2.3: 588 + resolution: {integrity: sha512-ZlQmCCK+n7SGoqo7DnfKaP1sJZa49P01/dXzmjCASSo04p72w8EksT2NMK8CEX8DhKsfJXANioIw8VyHNsBfvQ==} 589 + 590 + estree-walker@2.0.2: 591 + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 592 + 593 + fast-deep-equal@3.1.3: 594 + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 595 + 596 + fdir@6.4.2: 597 + resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} 598 + peerDependencies: 599 + picomatch: ^3 || ^4 600 + peerDependenciesMeta: 601 + picomatch: 602 + optional: true 603 + 604 + fs-extra@7.0.1: 605 + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} 606 + engines: {node: '>=6 <7 || >=8'} 607 + 608 + fsevents@2.3.3: 609 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 610 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 611 + os: [darwin] 612 + 613 + function-bind@1.1.2: 614 + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 615 + 616 + graceful-fs@4.2.11: 617 + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 618 + 619 + has-flag@4.0.0: 620 + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 621 + engines: {node: '>=8'} 622 + 623 + hasown@2.0.2: 624 + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 625 + engines: {node: '>= 0.4'} 626 + 627 + he@1.2.0: 628 + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} 629 + hasBin: true 630 + 631 + import-lazy@4.0.0: 632 + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} 633 + engines: {node: '>=8'} 634 + 635 + is-core-module@2.15.1: 636 + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} 637 + engines: {node: '>= 0.4'} 638 + 639 + is-reference@3.0.3: 640 + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} 641 + 642 + jju@1.4.0: 643 + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} 644 + 645 + json-schema-traverse@1.0.0: 646 + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} 647 + 648 + jsonfile@4.0.0: 649 + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} 650 + 651 + kolorist@1.8.0: 652 + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} 653 + 654 + local-pkg@0.5.1: 655 + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} 656 + engines: {node: '>=14'} 657 + 658 + locate-character@3.0.0: 659 + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} 660 + 661 + lodash@4.17.21: 662 + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 663 + 664 + lru-cache@6.0.0: 665 + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 666 + engines: {node: '>=10'} 667 + 668 + magic-string@0.30.14: 669 + resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} 670 + 671 + minimatch@3.0.8: 672 + resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} 673 + 674 + minimatch@9.0.5: 675 + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 676 + engines: {node: '>=16 || 14 >=14.17'} 677 + 678 + mlly@1.7.3: 679 + resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} 680 + 681 + mri@1.2.0: 682 + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} 683 + engines: {node: '>=4'} 684 + 685 + ms@2.1.3: 686 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 687 + 688 + muggle-string@0.4.1: 689 + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} 690 + 691 + nanoid@3.3.8: 692 + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} 693 + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 694 + hasBin: true 695 + 696 + path-browserify@1.0.1: 697 + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} 698 + 699 + path-parse@1.0.7: 700 + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 701 + 702 + pathe@1.1.2: 703 + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} 704 + 705 + picocolors@1.1.1: 706 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 707 + 708 + picomatch@4.0.2: 709 + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 710 + engines: {node: '>=12'} 711 + 712 + pkg-types@1.2.1: 713 + resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} 714 + 715 + postcss-less@6.0.0: 716 + resolution: {integrity: sha512-FPX16mQLyEjLzEuuJtxA8X3ejDLNGGEG503d2YGZR5Ask1SpDN8KmZUMpzCvyalWRywAn1n1VOA5dcqfCLo5rg==} 717 + engines: {node: '>=12'} 718 + peerDependencies: 719 + postcss: ^8.3.5 720 + 721 + postcss-scss@4.0.9: 722 + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} 723 + engines: {node: '>=12.0'} 724 + peerDependencies: 725 + postcss: ^8.4.29 726 + 727 + postcss@8.4.49: 728 + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} 729 + engines: {node: ^10 || ^12 || >=14} 730 + 731 + prettier-plugin-css-order@2.1.2: 732 + resolution: {integrity: sha512-vomxPjHI6pOMYcBuouSJHxxQClJXaUpU9rsV9IAO2wrSTZILRRlrxAAR8t9UF6wtczLkLfNRFUwM+ZbGXOONUA==} 733 + engines: {node: '>=16'} 734 + peerDependencies: 735 + prettier: 3.x 736 + 737 + prettier-plugin-svelte@3.3.2: 738 + resolution: {integrity: sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==} 739 + peerDependencies: 740 + prettier: ^3.0.0 741 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 742 + 743 + prettier@3.4.1: 744 + resolution: {integrity: sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==} 745 + engines: {node: '>=14'} 746 + hasBin: true 747 + 748 + punycode@2.3.1: 749 + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 750 + engines: {node: '>=6'} 751 + 752 + readdirp@4.0.2: 753 + resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} 754 + engines: {node: '>= 14.16.0'} 755 + 756 + require-from-string@2.0.2: 757 + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} 758 + engines: {node: '>=0.10.0'} 759 + 760 + resolve@1.22.8: 761 + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} 762 + hasBin: true 763 + 764 + rollup@4.28.0: 765 + resolution: {integrity: sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==} 766 + engines: {node: '>=18.0.0', npm: '>=8.0.0'} 767 + hasBin: true 768 + 769 + sade@1.8.1: 770 + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} 771 + engines: {node: '>=6'} 772 + 773 + semver@7.5.4: 774 + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 775 + engines: {node: '>=10'} 776 + hasBin: true 777 + 778 + source-map-js@1.2.1: 779 + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 780 + engines: {node: '>=0.10.0'} 781 + 782 + source-map@0.6.1: 783 + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 784 + engines: {node: '>=0.10.0'} 785 + 786 + sprintf-js@1.0.3: 787 + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} 788 + 789 + string-argv@0.3.2: 790 + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} 791 + engines: {node: '>=0.6.19'} 792 + 793 + strip-json-comments@3.1.1: 794 + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 795 + engines: {node: '>=8'} 796 + 797 + supports-color@8.1.1: 798 + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} 799 + engines: {node: '>=10'} 800 + 801 + supports-preserve-symlinks-flag@1.0.0: 802 + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 803 + engines: {node: '>= 0.4'} 804 + 805 + svelte-check@4.1.0: 806 + resolution: {integrity: sha512-AflEZYqI578KuDZcpcorPSf597LStxlkN7XqXi38u09zlHODVKd7c+7OuubGzbhgGRUqNTdQCZ+Ga96iRXEf2g==} 807 + engines: {node: '>= 18.0.0'} 808 + hasBin: true 809 + peerDependencies: 810 + svelte: ^4.0.0 || ^5.0.0-next.0 811 + typescript: '>=5.0.0' 812 + 813 + svelte@5.3.1: 814 + resolution: {integrity: sha512-Y6PXppQhIZZ0HLZKj6UMV/VZPJbHiK98K8A5M7mJ+PGrz4erUmuDRUa8l7aw4La++Vl51YWzLUuuB0FZ7JPfnw==} 815 + engines: {node: '>=18'} 816 + 817 + typescript@5.4.2: 818 + resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} 819 + engines: {node: '>=14.17'} 820 + hasBin: true 821 + 822 + typescript@5.7.2: 823 + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} 824 + engines: {node: '>=14.17'} 825 + hasBin: true 826 + 827 + ufo@1.5.4: 828 + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} 829 + 830 + undici-types@6.20.0: 831 + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 832 + 833 + universalify@0.1.2: 834 + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} 835 + engines: {node: '>= 4.0.0'} 836 + 837 + uri-js@4.4.1: 838 + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 839 + 840 + vite-plugin-dts@4.3.0: 841 + resolution: {integrity: sha512-LkBJh9IbLwL6/rxh0C1/bOurDrIEmRE7joC+jFdOEEciAFPbpEKOLSAr5nNh5R7CJ45cMbksTrFfy52szzC5eA==} 842 + engines: {node: ^14.18.0 || >=16.0.0} 843 + peerDependencies: 844 + typescript: '*' 845 + vite: '*' 846 + peerDependenciesMeta: 847 + vite: 848 + optional: true 849 + 850 + vite@6.0.2: 851 + resolution: {integrity: sha512-XdQ+VsY2tJpBsKGs0wf3U/+azx8BBpYRHFAyKm5VeEZNOJZRB63q7Sc8Iup3k0TrN3KO6QgyzFf+opSbfY1y0g==} 852 + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 853 + hasBin: true 854 + peerDependencies: 855 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 856 + jiti: '>=1.21.0' 857 + less: '*' 858 + lightningcss: ^1.21.0 859 + sass: '*' 860 + sass-embedded: '*' 861 + stylus: '*' 862 + sugarss: '*' 863 + terser: ^5.16.0 864 + tsx: ^4.8.1 865 + yaml: ^2.4.2 866 + peerDependenciesMeta: 867 + '@types/node': 868 + optional: true 869 + jiti: 870 + optional: true 871 + less: 872 + optional: true 873 + lightningcss: 874 + optional: true 875 + sass: 876 + optional: true 877 + sass-embedded: 878 + optional: true 879 + stylus: 880 + optional: true 881 + sugarss: 882 + optional: true 883 + terser: 884 + optional: true 885 + tsx: 886 + optional: true 887 + yaml: 888 + optional: true 889 + 890 + vscode-uri@3.0.8: 891 + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} 892 + 893 + yallist@4.0.0: 894 + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 895 + 896 + zimmerframe@1.1.2: 897 + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} 898 + 899 + snapshots: 900 + 901 + '@ampproject/remapping@2.3.0': 902 + dependencies: 903 + '@jridgewell/gen-mapping': 0.3.5 904 + '@jridgewell/trace-mapping': 0.3.25 905 + 906 + '@atcute/bluesky-richtext-segmenter@1.0.5(@atcute/bluesky@1.0.9(@atcute/client@2.0.6))(@atcute/client@2.0.6)': 907 + dependencies: 908 + '@atcute/bluesky': 1.0.9(@atcute/client@2.0.6) 909 + '@atcute/client': 2.0.6 910 + 911 + '@atcute/bluesky@1.0.9(@atcute/client@2.0.6)': 912 + dependencies: 913 + '@atcute/client': 2.0.6 914 + 915 + '@atcute/client@2.0.6': {} 916 + 917 + '@babel/helper-string-parser@7.25.9': {} 918 + 919 + '@babel/helper-validator-identifier@7.25.9': {} 920 + 921 + '@babel/parser@7.26.2': 922 + dependencies: 923 + '@babel/types': 7.26.0 924 + 925 + '@babel/types@7.26.0': 926 + dependencies: 927 + '@babel/helper-string-parser': 7.25.9 928 + '@babel/helper-validator-identifier': 7.25.9 929 + 930 + '@esbuild/aix-ppc64@0.24.0': 931 + optional: true 932 + 933 + '@esbuild/android-arm64@0.24.0': 934 + optional: true 935 + 936 + '@esbuild/android-arm@0.24.0': 937 + optional: true 938 + 939 + '@esbuild/android-x64@0.24.0': 940 + optional: true 941 + 942 + '@esbuild/darwin-arm64@0.24.0': 943 + optional: true 944 + 945 + '@esbuild/darwin-x64@0.24.0': 946 + optional: true 947 + 948 + '@esbuild/freebsd-arm64@0.24.0': 949 + optional: true 950 + 951 + '@esbuild/freebsd-x64@0.24.0': 952 + optional: true 953 + 954 + '@esbuild/linux-arm64@0.24.0': 955 + optional: true 956 + 957 + '@esbuild/linux-arm@0.24.0': 958 + optional: true 959 + 960 + '@esbuild/linux-ia32@0.24.0': 961 + optional: true 962 + 963 + '@esbuild/linux-loong64@0.24.0': 964 + optional: true 965 + 966 + '@esbuild/linux-mips64el@0.24.0': 967 + optional: true 968 + 969 + '@esbuild/linux-ppc64@0.24.0': 970 + optional: true 971 + 972 + '@esbuild/linux-riscv64@0.24.0': 973 + optional: true 974 + 975 + '@esbuild/linux-s390x@0.24.0': 976 + optional: true 977 + 978 + '@esbuild/linux-x64@0.24.0': 979 + optional: true 980 + 981 + '@esbuild/netbsd-x64@0.24.0': 982 + optional: true 983 + 984 + '@esbuild/openbsd-arm64@0.24.0': 985 + optional: true 986 + 987 + '@esbuild/openbsd-x64@0.24.0': 988 + optional: true 989 + 990 + '@esbuild/sunos-x64@0.24.0': 991 + optional: true 992 + 993 + '@esbuild/win32-arm64@0.24.0': 994 + optional: true 995 + 996 + '@esbuild/win32-ia32@0.24.0': 997 + optional: true 998 + 999 + '@esbuild/win32-x64@0.24.0': 1000 + optional: true 1001 + 1002 + '@jridgewell/gen-mapping@0.3.5': 1003 + dependencies: 1004 + '@jridgewell/set-array': 1.2.1 1005 + '@jridgewell/sourcemap-codec': 1.5.0 1006 + '@jridgewell/trace-mapping': 0.3.25 1007 + 1008 + '@jridgewell/resolve-uri@3.1.2': {} 1009 + 1010 + '@jridgewell/set-array@1.2.1': {} 1011 + 1012 + '@jridgewell/sourcemap-codec@1.5.0': {} 1013 + 1014 + '@jridgewell/trace-mapping@0.3.25': 1015 + dependencies: 1016 + '@jridgewell/resolve-uri': 3.1.2 1017 + '@jridgewell/sourcemap-codec': 1.5.0 1018 + 1019 + '@microsoft/api-extractor-model@7.30.0(@types/node@22.10.1)': 1020 + dependencies: 1021 + '@microsoft/tsdoc': 0.15.1 1022 + '@microsoft/tsdoc-config': 0.17.1 1023 + '@rushstack/node-core-library': 5.10.0(@types/node@22.10.1) 1024 + transitivePeerDependencies: 1025 + - '@types/node' 1026 + 1027 + '@microsoft/api-extractor@7.48.0(@types/node@22.10.1)': 1028 + dependencies: 1029 + '@microsoft/api-extractor-model': 7.30.0(@types/node@22.10.1) 1030 + '@microsoft/tsdoc': 0.15.1 1031 + '@microsoft/tsdoc-config': 0.17.1 1032 + '@rushstack/node-core-library': 5.10.0(@types/node@22.10.1) 1033 + '@rushstack/rig-package': 0.5.3 1034 + '@rushstack/terminal': 0.14.3(@types/node@22.10.1) 1035 + '@rushstack/ts-command-line': 4.23.1(@types/node@22.10.1) 1036 + lodash: 4.17.21 1037 + minimatch: 3.0.8 1038 + resolve: 1.22.8 1039 + semver: 7.5.4 1040 + source-map: 0.6.1 1041 + typescript: 5.4.2 1042 + transitivePeerDependencies: 1043 + - '@types/node' 1044 + 1045 + '@microsoft/tsdoc-config@0.17.1': 1046 + dependencies: 1047 + '@microsoft/tsdoc': 0.15.1 1048 + ajv: 8.12.0 1049 + jju: 1.4.0 1050 + resolve: 1.22.8 1051 + 1052 + '@microsoft/tsdoc@0.15.1': {} 1053 + 1054 + '@rollup/pluginutils@5.1.3(rollup@4.28.0)': 1055 + dependencies: 1056 + '@types/estree': 1.0.6 1057 + estree-walker: 2.0.2 1058 + picomatch: 4.0.2 1059 + optionalDependencies: 1060 + rollup: 4.28.0 1061 + 1062 + '@rollup/rollup-android-arm-eabi@4.28.0': 1063 + optional: true 1064 + 1065 + '@rollup/rollup-android-arm64@4.28.0': 1066 + optional: true 1067 + 1068 + '@rollup/rollup-darwin-arm64@4.28.0': 1069 + optional: true 1070 + 1071 + '@rollup/rollup-darwin-x64@4.28.0': 1072 + optional: true 1073 + 1074 + '@rollup/rollup-freebsd-arm64@4.28.0': 1075 + optional: true 1076 + 1077 + '@rollup/rollup-freebsd-x64@4.28.0': 1078 + optional: true 1079 + 1080 + '@rollup/rollup-linux-arm-gnueabihf@4.28.0': 1081 + optional: true 1082 + 1083 + '@rollup/rollup-linux-arm-musleabihf@4.28.0': 1084 + optional: true 1085 + 1086 + '@rollup/rollup-linux-arm64-gnu@4.28.0': 1087 + optional: true 1088 + 1089 + '@rollup/rollup-linux-arm64-musl@4.28.0': 1090 + optional: true 1091 + 1092 + '@rollup/rollup-linux-powerpc64le-gnu@4.28.0': 1093 + optional: true 1094 + 1095 + '@rollup/rollup-linux-riscv64-gnu@4.28.0': 1096 + optional: true 1097 + 1098 + '@rollup/rollup-linux-s390x-gnu@4.28.0': 1099 + optional: true 1100 + 1101 + '@rollup/rollup-linux-x64-gnu@4.28.0': 1102 + optional: true 1103 + 1104 + '@rollup/rollup-linux-x64-musl@4.28.0': 1105 + optional: true 1106 + 1107 + '@rollup/rollup-win32-arm64-msvc@4.28.0': 1108 + optional: true 1109 + 1110 + '@rollup/rollup-win32-ia32-msvc@4.28.0': 1111 + optional: true 1112 + 1113 + '@rollup/rollup-win32-x64-msvc@4.28.0': 1114 + optional: true 1115 + 1116 + '@rushstack/node-core-library@5.10.0(@types/node@22.10.1)': 1117 + dependencies: 1118 + ajv: 8.13.0 1119 + ajv-draft-04: 1.0.0(ajv@8.13.0) 1120 + ajv-formats: 3.0.1(ajv@8.13.0) 1121 + fs-extra: 7.0.1 1122 + import-lazy: 4.0.0 1123 + jju: 1.4.0 1124 + resolve: 1.22.8 1125 + semver: 7.5.4 1126 + optionalDependencies: 1127 + '@types/node': 22.10.1 1128 + 1129 + '@rushstack/rig-package@0.5.3': 1130 + dependencies: 1131 + resolve: 1.22.8 1132 + strip-json-comments: 3.1.1 1133 + 1134 + '@rushstack/terminal@0.14.3(@types/node@22.10.1)': 1135 + dependencies: 1136 + '@rushstack/node-core-library': 5.10.0(@types/node@22.10.1) 1137 + supports-color: 8.1.1 1138 + optionalDependencies: 1139 + '@types/node': 22.10.1 1140 + 1141 + '@rushstack/ts-command-line@4.23.1(@types/node@22.10.1)': 1142 + dependencies: 1143 + '@rushstack/terminal': 0.14.3(@types/node@22.10.1) 1144 + '@types/argparse': 1.0.38 1145 + argparse: 1.0.10 1146 + string-argv: 0.3.2 1147 + transitivePeerDependencies: 1148 + - '@types/node' 1149 + 1150 + '@tsconfig/svelte@5.0.4': {} 1151 + 1152 + '@types/argparse@1.0.38': {} 1153 + 1154 + '@types/estree@1.0.6': {} 1155 + 1156 + '@types/node@22.10.1': 1157 + dependencies: 1158 + undici-types: 6.20.0 1159 + 1160 + '@volar/language-core@2.4.10': 1161 + dependencies: 1162 + '@volar/source-map': 2.4.10 1163 + 1164 + '@volar/source-map@2.4.10': {} 1165 + 1166 + '@volar/typescript@2.4.10': 1167 + dependencies: 1168 + '@volar/language-core': 2.4.10 1169 + path-browserify: 1.0.1 1170 + vscode-uri: 3.0.8 1171 + 1172 + '@vue/compiler-core@3.5.13': 1173 + dependencies: 1174 + '@babel/parser': 7.26.2 1175 + '@vue/shared': 3.5.13 1176 + entities: 4.5.0 1177 + estree-walker: 2.0.2 1178 + source-map-js: 1.2.1 1179 + 1180 + '@vue/compiler-dom@3.5.13': 1181 + dependencies: 1182 + '@vue/compiler-core': 3.5.13 1183 + '@vue/shared': 3.5.13 1184 + 1185 + '@vue/compiler-vue2@2.7.16': 1186 + dependencies: 1187 + de-indent: 1.0.2 1188 + he: 1.2.0 1189 + 1190 + '@vue/language-core@2.1.6(typescript@5.7.2)': 1191 + dependencies: 1192 + '@volar/language-core': 2.4.10 1193 + '@vue/compiler-dom': 3.5.13 1194 + '@vue/compiler-vue2': 2.7.16 1195 + '@vue/shared': 3.5.13 1196 + computeds: 0.0.1 1197 + minimatch: 9.0.5 1198 + muggle-string: 0.4.1 1199 + path-browserify: 1.0.1 1200 + optionalDependencies: 1201 + typescript: 5.7.2 1202 + 1203 + '@vue/shared@3.5.13': {} 1204 + 1205 + acorn-typescript@1.4.13(acorn@8.14.0): 1206 + dependencies: 1207 + acorn: 8.14.0 1208 + 1209 + acorn@8.14.0: {} 1210 + 1211 + ajv-draft-04@1.0.0(ajv@8.13.0): 1212 + optionalDependencies: 1213 + ajv: 8.13.0 1214 + 1215 + ajv-formats@3.0.1(ajv@8.13.0): 1216 + optionalDependencies: 1217 + ajv: 8.13.0 1218 + 1219 + ajv@8.12.0: 1220 + dependencies: 1221 + fast-deep-equal: 3.1.3 1222 + json-schema-traverse: 1.0.0 1223 + require-from-string: 2.0.2 1224 + uri-js: 4.4.1 1225 + 1226 + ajv@8.13.0: 1227 + dependencies: 1228 + fast-deep-equal: 3.1.3 1229 + json-schema-traverse: 1.0.0 1230 + require-from-string: 2.0.2 1231 + uri-js: 4.4.1 1232 + 1233 + argparse@1.0.10: 1234 + dependencies: 1235 + sprintf-js: 1.0.3 1236 + 1237 + aria-query@5.3.2: {} 1238 + 1239 + axobject-query@4.1.0: {} 1240 + 1241 + balanced-match@1.0.2: {} 1242 + 1243 + brace-expansion@1.1.11: 1244 + dependencies: 1245 + balanced-match: 1.0.2 1246 + concat-map: 0.0.1 1247 + 1248 + brace-expansion@2.0.1: 1249 + dependencies: 1250 + balanced-match: 1.0.2 1251 + 1252 + chokidar@4.0.1: 1253 + dependencies: 1254 + readdirp: 4.0.2 1255 + 1256 + compare-versions@6.1.1: {} 1257 + 1258 + computeds@0.0.1: {} 1259 + 1260 + concat-map@0.0.1: {} 1261 + 1262 + confbox@0.1.8: {} 1263 + 1264 + css-declaration-sorter@7.2.0(postcss@8.4.49): 1265 + dependencies: 1266 + postcss: 8.4.49 1267 + 1268 + de-indent@1.0.2: {} 1269 + 1270 + debug@4.3.7: 1271 + dependencies: 1272 + ms: 2.1.3 1273 + 1274 + entities@4.5.0: {} 1275 + 1276 + esbuild@0.24.0: 1277 + optionalDependencies: 1278 + '@esbuild/aix-ppc64': 0.24.0 1279 + '@esbuild/android-arm': 0.24.0 1280 + '@esbuild/android-arm64': 0.24.0 1281 + '@esbuild/android-x64': 0.24.0 1282 + '@esbuild/darwin-arm64': 0.24.0 1283 + '@esbuild/darwin-x64': 0.24.0 1284 + '@esbuild/freebsd-arm64': 0.24.0 1285 + '@esbuild/freebsd-x64': 0.24.0 1286 + '@esbuild/linux-arm': 0.24.0 1287 + '@esbuild/linux-arm64': 0.24.0 1288 + '@esbuild/linux-ia32': 0.24.0 1289 + '@esbuild/linux-loong64': 0.24.0 1290 + '@esbuild/linux-mips64el': 0.24.0 1291 + '@esbuild/linux-ppc64': 0.24.0 1292 + '@esbuild/linux-riscv64': 0.24.0 1293 + '@esbuild/linux-s390x': 0.24.0 1294 + '@esbuild/linux-x64': 0.24.0 1295 + '@esbuild/netbsd-x64': 0.24.0 1296 + '@esbuild/openbsd-arm64': 0.24.0 1297 + '@esbuild/openbsd-x64': 0.24.0 1298 + '@esbuild/sunos-x64': 0.24.0 1299 + '@esbuild/win32-arm64': 0.24.0 1300 + '@esbuild/win32-ia32': 0.24.0 1301 + '@esbuild/win32-x64': 0.24.0 1302 + 1303 + esm-env@1.2.1: {} 1304 + 1305 + esrap@1.2.3: 1306 + dependencies: 1307 + '@jridgewell/sourcemap-codec': 1.5.0 1308 + '@types/estree': 1.0.6 1309 + 1310 + estree-walker@2.0.2: {} 1311 + 1312 + fast-deep-equal@3.1.3: {} 1313 + 1314 + fdir@6.4.2(picomatch@4.0.2): 1315 + optionalDependencies: 1316 + picomatch: 4.0.2 1317 + 1318 + fs-extra@7.0.1: 1319 + dependencies: 1320 + graceful-fs: 4.2.11 1321 + jsonfile: 4.0.0 1322 + universalify: 0.1.2 1323 + 1324 + fsevents@2.3.3: 1325 + optional: true 1326 + 1327 + function-bind@1.1.2: {} 1328 + 1329 + graceful-fs@4.2.11: {} 1330 + 1331 + has-flag@4.0.0: {} 1332 + 1333 + hasown@2.0.2: 1334 + dependencies: 1335 + function-bind: 1.1.2 1336 + 1337 + he@1.2.0: {} 1338 + 1339 + import-lazy@4.0.0: {} 1340 + 1341 + is-core-module@2.15.1: 1342 + dependencies: 1343 + hasown: 2.0.2 1344 + 1345 + is-reference@3.0.3: 1346 + dependencies: 1347 + '@types/estree': 1.0.6 1348 + 1349 + jju@1.4.0: {} 1350 + 1351 + json-schema-traverse@1.0.0: {} 1352 + 1353 + jsonfile@4.0.0: 1354 + optionalDependencies: 1355 + graceful-fs: 4.2.11 1356 + 1357 + kolorist@1.8.0: {} 1358 + 1359 + local-pkg@0.5.1: 1360 + dependencies: 1361 + mlly: 1.7.3 1362 + pkg-types: 1.2.1 1363 + 1364 + locate-character@3.0.0: {} 1365 + 1366 + lodash@4.17.21: {} 1367 + 1368 + lru-cache@6.0.0: 1369 + dependencies: 1370 + yallist: 4.0.0 1371 + 1372 + magic-string@0.30.14: 1373 + dependencies: 1374 + '@jridgewell/sourcemap-codec': 1.5.0 1375 + 1376 + minimatch@3.0.8: 1377 + dependencies: 1378 + brace-expansion: 1.1.11 1379 + 1380 + minimatch@9.0.5: 1381 + dependencies: 1382 + brace-expansion: 2.0.1 1383 + 1384 + mlly@1.7.3: 1385 + dependencies: 1386 + acorn: 8.14.0 1387 + pathe: 1.1.2 1388 + pkg-types: 1.2.1 1389 + ufo: 1.5.4 1390 + 1391 + mri@1.2.0: {} 1392 + 1393 + ms@2.1.3: {} 1394 + 1395 + muggle-string@0.4.1: {} 1396 + 1397 + nanoid@3.3.8: {} 1398 + 1399 + path-browserify@1.0.1: {} 1400 + 1401 + path-parse@1.0.7: {} 1402 + 1403 + pathe@1.1.2: {} 1404 + 1405 + picocolors@1.1.1: {} 1406 + 1407 + picomatch@4.0.2: {} 1408 + 1409 + pkg-types@1.2.1: 1410 + dependencies: 1411 + confbox: 0.1.8 1412 + mlly: 1.7.3 1413 + pathe: 1.1.2 1414 + 1415 + postcss-less@6.0.0(postcss@8.4.49): 1416 + dependencies: 1417 + postcss: 8.4.49 1418 + 1419 + postcss-scss@4.0.9(postcss@8.4.49): 1420 + dependencies: 1421 + postcss: 8.4.49 1422 + 1423 + postcss@8.4.49: 1424 + dependencies: 1425 + nanoid: 3.3.8 1426 + picocolors: 1.1.1 1427 + source-map-js: 1.2.1 1428 + 1429 + prettier-plugin-css-order@2.1.2(postcss@8.4.49)(prettier@3.4.1): 1430 + dependencies: 1431 + css-declaration-sorter: 7.2.0(postcss@8.4.49) 1432 + postcss-less: 6.0.0(postcss@8.4.49) 1433 + postcss-scss: 4.0.9(postcss@8.4.49) 1434 + prettier: 3.4.1 1435 + transitivePeerDependencies: 1436 + - postcss 1437 + 1438 + prettier-plugin-svelte@3.3.2(prettier@3.4.1)(svelte@5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu)): 1439 + dependencies: 1440 + prettier: 3.4.1 1441 + svelte: 5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu) 1442 + 1443 + prettier@3.4.1: {} 1444 + 1445 + punycode@2.3.1: {} 1446 + 1447 + readdirp@4.0.2: {} 1448 + 1449 + require-from-string@2.0.2: {} 1450 + 1451 + resolve@1.22.8: 1452 + dependencies: 1453 + is-core-module: 2.15.1 1454 + path-parse: 1.0.7 1455 + supports-preserve-symlinks-flag: 1.0.0 1456 + 1457 + rollup@4.28.0: 1458 + dependencies: 1459 + '@types/estree': 1.0.6 1460 + optionalDependencies: 1461 + '@rollup/rollup-android-arm-eabi': 4.28.0 1462 + '@rollup/rollup-android-arm64': 4.28.0 1463 + '@rollup/rollup-darwin-arm64': 4.28.0 1464 + '@rollup/rollup-darwin-x64': 4.28.0 1465 + '@rollup/rollup-freebsd-arm64': 4.28.0 1466 + '@rollup/rollup-freebsd-x64': 4.28.0 1467 + '@rollup/rollup-linux-arm-gnueabihf': 4.28.0 1468 + '@rollup/rollup-linux-arm-musleabihf': 4.28.0 1469 + '@rollup/rollup-linux-arm64-gnu': 4.28.0 1470 + '@rollup/rollup-linux-arm64-musl': 4.28.0 1471 + '@rollup/rollup-linux-powerpc64le-gnu': 4.28.0 1472 + '@rollup/rollup-linux-riscv64-gnu': 4.28.0 1473 + '@rollup/rollup-linux-s390x-gnu': 4.28.0 1474 + '@rollup/rollup-linux-x64-gnu': 4.28.0 1475 + '@rollup/rollup-linux-x64-musl': 4.28.0 1476 + '@rollup/rollup-win32-arm64-msvc': 4.28.0 1477 + '@rollup/rollup-win32-ia32-msvc': 4.28.0 1478 + '@rollup/rollup-win32-x64-msvc': 4.28.0 1479 + fsevents: 2.3.3 1480 + 1481 + sade@1.8.1: 1482 + dependencies: 1483 + mri: 1.2.0 1484 + 1485 + semver@7.5.4: 1486 + dependencies: 1487 + lru-cache: 6.0.0 1488 + 1489 + source-map-js@1.2.1: {} 1490 + 1491 + source-map@0.6.1: {} 1492 + 1493 + sprintf-js@1.0.3: {} 1494 + 1495 + string-argv@0.3.2: {} 1496 + 1497 + strip-json-comments@3.1.1: {} 1498 + 1499 + supports-color@8.1.1: 1500 + dependencies: 1501 + has-flag: 4.0.0 1502 + 1503 + supports-preserve-symlinks-flag@1.0.0: {} 1504 + 1505 + svelte-check@4.1.0(picomatch@4.0.2)(svelte@5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu))(typescript@5.7.2): 1506 + dependencies: 1507 + '@jridgewell/trace-mapping': 0.3.25 1508 + chokidar: 4.0.1 1509 + fdir: 6.4.2(picomatch@4.0.2) 1510 + picocolors: 1.1.1 1511 + sade: 1.8.1 1512 + svelte: 5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu) 1513 + typescript: 5.7.2 1514 + transitivePeerDependencies: 1515 + - picomatch 1516 + 1517 + svelte@5.3.1(patch_hash=6qynve6ufonlwufsl6x7wujmdu): 1518 + dependencies: 1519 + '@ampproject/remapping': 2.3.0 1520 + '@jridgewell/sourcemap-codec': 1.5.0 1521 + '@types/estree': 1.0.6 1522 + acorn: 8.14.0 1523 + acorn-typescript: 1.4.13(acorn@8.14.0) 1524 + aria-query: 5.3.2 1525 + axobject-query: 4.1.0 1526 + esm-env: 1.2.1 1527 + esrap: 1.2.3 1528 + is-reference: 3.0.3 1529 + locate-character: 3.0.0 1530 + magic-string: 0.30.14 1531 + zimmerframe: 1.1.2 1532 + 1533 + typescript@5.4.2: {} 1534 + 1535 + typescript@5.7.2: {} 1536 + 1537 + ufo@1.5.4: {} 1538 + 1539 + undici-types@6.20.0: {} 1540 + 1541 + universalify@0.1.2: {} 1542 + 1543 + uri-js@4.4.1: 1544 + dependencies: 1545 + punycode: 2.3.1 1546 + 1547 + vite-plugin-dts@4.3.0(@types/node@22.10.1)(rollup@4.28.0)(typescript@5.7.2)(vite@6.0.2(@types/node@22.10.1)): 1548 + dependencies: 1549 + '@microsoft/api-extractor': 7.48.0(@types/node@22.10.1) 1550 + '@rollup/pluginutils': 5.1.3(rollup@4.28.0) 1551 + '@volar/typescript': 2.4.10 1552 + '@vue/language-core': 2.1.6(typescript@5.7.2) 1553 + compare-versions: 6.1.1 1554 + debug: 4.3.7 1555 + kolorist: 1.8.0 1556 + local-pkg: 0.5.1 1557 + magic-string: 0.30.14 1558 + typescript: 5.7.2 1559 + optionalDependencies: 1560 + vite: 6.0.2(@types/node@22.10.1) 1561 + transitivePeerDependencies: 1562 + - '@types/node' 1563 + - rollup 1564 + - supports-color 1565 + 1566 + vite@6.0.2(@types/node@22.10.1): 1567 + dependencies: 1568 + esbuild: 0.24.0 1569 + postcss: 8.4.49 1570 + rollup: 4.28.0 1571 + optionalDependencies: 1572 + '@types/node': 22.10.1 1573 + fsevents: 2.3.3 1574 + 1575 + vscode-uri@3.0.8: {} 1576 + 1577 + yallist@4.0.0: {} 1578 + 1579 + zimmerframe@1.1.2: {}
+4
pnpm-workspace.yaml
··· 1 + packages: 2 + - packages/internal 3 + - packages/bluesky-post-embed 4 + - packages/bluesky-profile-feed-embed
+19
themes/dark.css
··· 1 + .bluesky-embed { 2 + --font-size: 16px; 3 + --font-family: system-ui, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif, 'Apple Color Emoji', 4 + '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: system-ui, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif, 'Apple Color Emoji', 4 + '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: system-ui, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif, 'Apple Color Emoji', 4 + '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 + }