+13
.gitignore
+13
.gitignore
+25
.prettierrc
+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
+
}
+4
.vscode/settings.json
+4
.vscode/settings.json
+17
LICENSE
+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
+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
+

+17
package.json
+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
packages/bluesky-post-embed/.gitignore
···
1
+
themes/
+86
packages/bluesky-post-embed/README.md
+86
packages/bluesky-post-embed/README.md
···
1
+
# <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
+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
+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
+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
+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
+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
+13
packages/bluesky-post-embed/tsconfig.node.json
+102
packages/bluesky-post-embed/vite.config.ts
+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
packages/bluesky-profile-feed-embed/.gitignore
···
1
+
themes/
+87
packages/bluesky-profile-feed-embed/README.md
+87
packages/bluesky-profile-feed-embed/README.md
···
1
+
# <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
+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
+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
+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
+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
+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
+13
packages/bluesky-profile-feed-embed/tsconfig.node.json
+102
packages/bluesky-profile-feed-embed/vite.config.ts
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+7
packages/internal/types/post.ts
+7
packages/internal/types/profile-feed.ts
+7
packages/internal/types/profile-feed.ts
+27
packages/internal/utils/bsky-url.ts
+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
+3
packages/internal/utils/constants.ts
+44
packages/internal/utils/date.ts
+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
+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
+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
+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
+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
+4
pnpm-workspace.yaml
+19
themes/dark.css
+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
+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
+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
+
}