# @atmo-dev/kit 中文文档

规范地址：https://npmcn.com/package/%40atmo-dev/kit/

版本：0\.9\.0
npm 元数据更新时间：2026-11-08T03:53:40.438Z
中文译文更新时间：2026-11-09T20:12:44.205Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40atmo-dev/kit
原文对照：https://npmcn.com/package/%40atmo-dev/kit/?lang=original
项目主页：https://github.com/flo-bit/atmo-kit#readme
源码仓库：https://github.com/flo-bit/atmo-kit

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：634 kB，330 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：28 个
- peer 依赖：plyr ^3\.8\.5，svelte \>=5\.48\.0 \<6，maplibre-gl ^6\.16\.0，tailwindcss ^4\.0\.0，svelte-maplibre-gl ^2\.2\.1
- 首次发布：2026-10-27T19:04:34.616Z
- 最新版本发布：2026-11-08T03:53:40.133Z
- 近 30 天下载量：1761（2026-10-07 至 2026-11-05，npm 官方下载统计）
- 已发布版本数：15（近 12 个月 15 个）

## 中文简介

Atmo apps的共享UI基金会.

## 安装

```sh
npm install @atmo-dev/kit
```

也可以使用：`pnpm add @atmo-dev/kit`、`yarn add @atmo-dev/kit`、`bun add @atmo-dev/kit`

## README

# 阿特莫・克特

Svelte 5和Tailwind CSS 4组件用于Atmo应用.

-   安装 : `pnpm add @atmo-dev/kit`。 。 。 .
-   样式: 导入 `@atmo-dev/kit/theme.css` 之后 `tailwindcss`; 将您的应用程序包入 `class="atmo zinc pink light"` (或 `dark`) (中文(简体) ).
-   开发: `pnpm install && pnpm dev`组件 `/components`应用程序模型 `/preview`。 。 。 .
-   验证 : `pnpm lint && pnpm check && pnpm test:ci && pnpm build`。 。 。 .
-   释放: `pnpm changeset`,然后合并生成的释放 PR. [细节](https://npmcn.com/package/%40atmo-dev/kit/.changeset/README.md)。 。 。 .
-   许可证 : [麻省理工学院](https://npmcn.com/package/%40atmo-dev/kit/LICENSE)。 。 。 .

## 导航

`IconRail` 接受项目 a `variant` 从 `Button` (单位:千美元)`primary`, (中文(简体) ), `secondary`, (中文(简体) ), `ghost`, (中文(简体) ), `link`; 省略的变体保留 `ghost`启用 `primary: true, variant: 'primary'` 用于在铁路、移动栏或抽屉中进行分离、填充的创建动作。 这个 现有 `primary` 仅旗帜保留了它的微妙口音/相距行为.

### 边栏账户菜单

使用 `AccountMenu` 在一个签名的边栏页脚。 它的外形按钮刚刚打开 **转到配置文件** 财务报告和审定财务报表 **签字**; 没有账户切换. `expanded` 为更宽的侧边栏和移动抽屉添加名称和可选手柄.

```svelte
<IconRail label="Navigation" {items}>
    {#snippet footer({ expanded, close })}
        {#if viewer}
            <AccountMenu
                name={viewer.displayName || viewer.handle}
                handle={viewer.handle}
                avatar={viewer.avatar}
                profileHref={`/p/${encodeURIComponent(viewer.did)}`}
                {expanded}
                onprofile={close}
                onsignout={() => {
                    close();
                    signOut();
                }}
            />
        {/if}
    {/snippet}
</IconRail>
```

两种部件均从 `@atmo-dev/kit`启用 `profileHref` 当地人 链接,或省略它和供应 `onprofile` 为应用自主导航. Apps拥有 profile 路由,会话,签名请求,以及签名的UI. 菜单控件 键盘导航,解职,焦点恢复和门户主题继承.

## 安全连接目的地

使用无依赖性 `@atmo-dev/kit/urls` 在服务器加载器和组件中输入:

```ts
import { safeHttpUrl, safeLinkHref } from '@atmo-dev/kit/urls';

const website = safeHttpUrl(record.url);
const navigation = safeLinkHref(card.href);
```

-   `safeHttpUrl(unknown)` 接受绝对 HTTP( S) URL 不使用用户名/ 密码字 并返回其已规范的 URL。 用于未信任的外部记录 URL .
-   `safeLinkHref(unknown)` 允许根相通的应用路径,碎片, `mailto:` 财务报告和审定财务报表 `tel:` 行动。 协议相对路径和反斜/控制特征 本地/接触链接被拒绝 。 它没有解决 `./` 或者说 `../` 相对路径.
-   无效的输入返回 `undefined`: 将结果传递给 `href`,或者省略链接. 永远不要回到最初的未信任值. `target="_blank"` 不是 URL 验证.

帮助者不执行 IO 并导入任何 Svelte, DOM 或 Markdown 解析器 。 他们也是 从主 Kit 条目导出。 现有 `safeWebUrl` 原产地为富特出 别名 `safeHttpUrl`丰富文本提及回调,法律链接保留 更严格的目的地政策。 普通的 UI 原始语, 如按钮仍然接受 应用拥有的路由; 在通过之前验证未信任的路由 .

这些是 **导航** 政策,而不是 HTML 消毒器或 SSRF 保护。 图像, iframe和媒体来源可能需要更严格的政策。 服务器端获取需求 单独的主机/IP、DNS、重定向和响应内容检查.

## AT 记录元数据( AT Tags)

使用 `AtTags` 让其他客户发现 AT 协议记录和身份 一个 HTML 页面代表。 它使服务器侧 `<meta name="at:…" content="at://…">` 标记在文档头,当其道具改变时更新/删除。 这需要 没有主题、样式、网络请求或应用程序状态.

这个 [AT 标记建议](https://tangled.org/chrisshank.com/at-tags) 是一个草稿。 4个 标准关系为:

| 财产 | 含义 | 内容 |
| --- | --- | --- |
| `canonical` | 本页所代表的记录;删除该记录将删除该页. | 在 URI 记录 |
| `alternate` | 显示或引用的辅助记录;该页面在删除后幸存. | 在 URI 记录 |
| `author` | 作者页面内容的账户 . | `at://<DID>` |
| `me` | 此站点或区域所属的账户 . | `at://<DID>` |

每个属性接受一个数组;多个值产生单独的标记,精确 在每个财产中重复删除。 使用元a **名称**图形 `property` 属性或 `<link href="at://…">`。这些标签补充-不是 替换 - 您的标题、 描述、 HTTPS 词汇 URL 和 Open Graph 元数据.

对于由存储器所有者撰写的记录支持的页面,请使用 无框架 `recordAtTags` 服务器加载器中的助手 :

```ts
import { recordAtTags } from '@atmo-dev/kit/at-tags';

// After app-owned loading, visibility/moderation checks and handle resolution:
return {
    atTags: recordAtTags(record, relatedRecords)
};
```

`record` 选择 `relatedRecords` 只需输入 `{ uri: string }`。 。 。 . 帮助者设置 `canonical`,生成 `author` 从记录的存储器做了, 并列出相关记录如下: `alternate`,不包括犬科URI. 它会扔 `SyntaxError` 用于非记录或基于手柄的 URI : 通过已解决的基于 DDE 的记录 URIs,不是URL路径或查看器参数. 它不解决或获取任何东西.

在 SvelteKit 根布局中将元数据复制一次( 也覆盖嵌入) :

```svelte
<script lang="ts">
    import { page } from '$app/state';
    import { AtTags } from '@atmo-dev/kit';

    let { children } = $props();
</script>

<AtTags tags={page.error ? undefined : page.data.atTags} />
{@render children()}
```

添加 `atTags?: import('@atmo-dev/kit/at-tags').AtTagMetadata` 改为 `App.PageData`。 。 。 . 在其它 Svelte 应用程序中, 直接通过当前页面的元数据; 设置 `tags` 改为 `undefined` 没有关系的页面。 不要挂载竞争的标签所有者 .

你可以提供 `AtTagMetadata` 直接用于多个犬科记录, 不同 或账户部分:

```ts
import type { AtTagMetadata } from '@atmo-dev/kit/at-tags';

const atTags: AtTagMetadata = {
    me: [`at://${profile.did}`],
    alternate: profile.uri ? [profile.uri] : []
};
```

一个账户的事件页面 可能会幸免于删除其可选的 Bluesky 简介, 因此,剖面记录是一个替代的, 不一定是夸张的。 同样, 嵌入式评论是替代记录,而不是页面的作者. `me` 这不是 已签名的查看器或每个记录作者; 除非 网站实际上属于该账户。 普通发现和创建页面 通常没有犬科记录.

Apps拥有这些语义选择,记录加载,身份分辨率,能见度, 和校正页。 组件摆脱属性但不能验证 原始元数据或验证记录的存在。 使用有收藏和 Rkey 的记录 URI, 赤裸裸的 `at://<DID>` 身份URI没有后遗症 只有四个标准 属性得到支持;解析/搜索页面和自定义名称空间草稿为 在这个API之外.

## 活动

事件UI以前在 `@atmo-dev/events-ui` 生活在此套件中 :

```svelte
<script lang="ts">
    import { EventEditor, EventView, EventCard } from '@atmo-dev/kit/events';
    import type { EditorAdapter, EditorViewer } from '@atmo-dev/kit/events-data';

    let { adapter, viewer, event, data, pageUrl } = $props();
</script>

<EventCard {event} />
<EventEditor {adapter} {viewer} eventData={event} actorDid={event.did} rkey={event.rkey} />
<EventView {adapter} {viewer} {data} {pageUrl} />
```

-   `@atmo-dev/kit/events`:事件卡,视图,编辑器,RSVP/出勤/评论UI,起步器-包动作,会议时间表,VOD播放器/transcript和日期/时间/图像控制. 活动背景和编辑部分保持内部.
-   `@atmo-dev/kit/events-data`:结构事件类型,适配器合同,会话/日期帮助者,URL帮助者,主题值,链接-记录类型和缩略图选择. 本条目在平原节点和服务器路线中安全:没有斯维尔特,CSS,地图或玩家被加载. 浏览器帮助器, 如 `compressImage` 仅在调用时访问浏览器 API .
-   `@atmo-dev/kit/events/editor`数字 : `buildEventRecord` 财务报告和审定财务报表 `buildThumbnailMedia` 用于需要明确记录构建的主机。 这个单独的条目包括面向浏览器的缩略图代码;它不是纯数据API的一部分.

事件描述是带有AT 协议层面的Markdown. 在服务器上分析并传递结果为 `data.description`,所以Markdown 解析器 留在客户端捆绑:

```ts
import { parseRichText } from '@atmo-dev/kit/richtext';

const description = event.description
    ? parseRichText(event.description, { facets: event.facets, markdown: true, detectLinks: true })
    : null;
```

无 `data.description`, (中文(简体) ), `EventView` 将描述显示为带有平面链接的纯文本。 描述标题在事件标题下显示一个级别, Bluesky 共享卡使用 `richTextToPlainText(description)`。会议时间表显示谈话描述为短预览:通过 `conferenceTalkDescriptions`,由 talk URI 键入的纯文本,所以Markdown语法并不出现在那里. 见 [丰富文本](https://npmcn.com/package/%40atmo-dev/kit/#readme-rich-text) 对于什么得到支持.

事件组件默认读取了Bluesky的公用AppView和CDN. 使用其他服务呼叫的 Apps `configureAtproto({ appview, cdn })` 从 `events-data` 一次,在渲染前,在服务器和客户端上(例如,在根版式上方).

活动卡和被子更喜欢本土 `media` 图像,然后是 HTTPS `poster.url`,然后一个URI命名 `Event Image` (笑. town相容). `getEventExternalImage(event)` 从 `events-data` 返回时 `{ url, alt, aspectRatio? }` 或者说 `null`,验证 URL 和可选的海报尺寸。 它不执行 IO; 应用程序在获取外部图像服务器侧面之前必须应用自己的允许列表/方向策略 .

应用软件已执行 `EditorAdapter` 用于认证提示、寄存器IO、上传、保存/删除导航以及可选的链接记录传输/索引通知。 保留会话, Cloudflare 绑定, app 路由, 服务器端点, 缓存和节制 app 中. 编辑/视图从不导入消费者的应用状态. 现有 `EventView` 页面数据形状和RSVP行为在此迁移中被保存.

全事件 UI 需要可选的同位素(普通 Kit 消费者不) :

```sh
pnpm add maplibre-gl@^6.16.0 svelte-maplibre-gl@^2.2.1 plyr@^3.8.5
```

导入 `theme.css` 如上; 它扫描被包的事件组件, 因此不需要单独的 Tailwind 源路径 。 Kit主条目故意不出口活动.

最初的动作保存了遗留的默认: `/p/:actor/e/:rkey` 事件和 `/p/:actor` 配置链接, `/api/geocoding`Bluesky的公开评论 API `?created` 共享流量和RSVP的 `createdWith` 标识符。 主机目前需要兼容的路线/端点; `shareUrlOverride` 更改出行共享链接。 这些违约并非新的通用线路合约. 进一步的主机政策提取可以独立于软件包移动.

事件浏览器测试在 `src/tests/events`; 运行 `pnpm test:unit --run --project events`。 。 。 . `pnpm test:package` 检查实际构建的服务器条目及其没有Vite别名的运行时间导入图.

## 相片、谷物相册和流派

`PhotoGrid` 财务报告和审定财务报表 `PhotoLightbox` 与中性提供者合作 `Photo[]` 数据(`src`选项 `thumbnail`, (中文(简体) ), `alt`、尺寸和 `contentWarning`) (中文(简体) ). 网格在应答的泥瓦柱中保留了自然图像比例,细微的2%悬浮缩放被禁用来降低运动. 它在挂起图像前会发出警告,并打开一个无边界的全屏取景器。 取景者的背景暗淡,上/下箭头相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相 鼠标/ pen 拖动也会直接移动音轨 。 斯威普斯环形如箭头;垂直的平板不导航,控件与拖放表面相分离. 原生剪接缩放,箭键,出逃,焦点陷阱和还原被保留;放出动画尊重还原运动. `PhotoLightbox` 同时独立工作 `bind:open` 财务报告和审定财务报表 `bind:index`;直接使用它的呼叫者必须首先过滤或披露任何被警告的照片. Apps拥有URL验证和温和政策.

`GrainAlbumPicker`, (中文(简体) ), `GrainAlbumView` 财务报告和审定财务报表 `StreamplacePicker` 使用明确的召回。 他们拥有陈述、待发/误发状态和经久读的保护——而不是会议、事件记录、持久性、路线或缓存。 Picker 选择回调可以返回一个面向用户的错误字符串来保持对话框的打开. 参见离线示例 `/components`。 。 。 .

```svelte
<GrainAlbumPicker
    bind:open
    actor={viewer.did}
    listAlbums={photos.listAlbums}
    loadAlbum={photos.getGallery}
    onselect={(album) => attachAlbum(album)}
/>
<GrainAlbumView reference={albumUri} loadAlbum={photos.getGallery} />
<StreamplacePicker
    bind:open={streamOpen}
    actor={viewer.handle}
    {resolveHandle}
    onselect={(stream) => attachStream(stream.url)}
/>
```

`photos` 执行无框架 `GrainClient` 合同来源 `@atmo-dev/kit/grain`数字 : `getGallery(reference)` 财务报告和审定财务报表 `listAlbums(actor, cursor?)`,返回时已恢复正常 `GrainGallery`编号 :`GrainAlbumPage` 数据。 内线有 **没有** 选择后端或获取默认的应用路由。 Atmo.pics/Contrail的实现可以晚些时取代提供商而无需改变取取取器,道廊或灯盒.

现在, `createGrainXrpcClient(fetchImpl?)` \* 提供粮仓的社会执行:其固定公开 `social.grain.unspecced.getGallery` 财务报告和审定财务报表 `dev.hatk.getFeed` 结束点,反应正常化,CDN验证,内容-警告旗,5秒超时而无方向. 它不执行IO直到一个方法被调用。 Apps选择它运行的地方并拥有任何代理端点,缓存,认证或额外的节制. 没有显示任意源代理选项 .

`parseGrainLink` 从 `@atmo-dev/kit/grain`, (中文(简体) ), `parseStreamplaceLink` 从 `@atmo-dev/kit/streamplace`,以及 `Photo` 从 `@atmo-dev/kit/media`主要入境点也出口这些产品。 谷物 URL 在装入后正常化为基于 DD 的链接; 接受手柄和 %- 编码的 DD 标识符 。 这些帮助者只验证语法,而不是账户的存在.

## 只读配置文件

`UserProfile` 显示 Bluesky 配置头条: 横幅、 外观、 名称、 手柄、 代名词、 可选的计数、 生物、 现代配置链接, 以及基于 DDE 的“ Bluesky 上的视频” 链接 。 它没有编辑或跟踪行为。 可选 `actions` 片段控制着应用软件. `metrics={[{ label: 'events created', value: 12 }]}` 替换默认的 Bluesky 计数; `metrics={[]}` 藏一切数.

```svelte
<script lang="ts">
    import { UserProfile, blueskyProfileToProfileData } from '@atmo-dev/kit';

    // Fetch and apply moderation in the app, not in the component.
    let { profileView } = $props();
    let profile = $derived(blueskyProfileToProfileData(profileView));
</script>

{#if profile}<UserProfile {profile} />{/if}
```

`ProfileData` 也可以直接提供。 Bios 仍然隐藏有检测到的 HTTP( S) 链接、 引用和标签; 保留了新行 。 没有HTML/Markdown解释器,DOM消毒器,只使用客户端渲染,或提及解析请求. 链接目的地和图像拒绝可执行的方案和具有证书的URL. 外部链接使用 `noopener noreferrer`; 用户链接也使用 `nofollow ugc`。 。 。 .

`blueskyProfileToProfileData` 接受一个 `app.bsky.actor.getProfile` 查看和验证可选字段。 内容 `links` 必须是水分 `app.bsky.actor.link` 意见`url`选项 `title` 财务报告和审定财务报表 `icon` 图像 URL),而非配置记录的订购强Refs. 它以保守的方式省略了未解决的参考文献和标签链接,简化了目的地,最多显示10个。 遗产 `website` 字段被故意忽略。 Apps拥有水合,账户节制以及任何更具体的链接-调制政策. 被应用的图标渲染为小型装饰图像;不安全或失败的图标会倒回只文本的链接. 组件没有获取链接元数据或目的地 .

## Bluesky 评论和嵌入

`NestedComments` 调制 `PostData[]` 带有递归式回复和紧凑式回复,重出并类似图标/计数链接。 将默认数计为0;每个链接都有完整的可访问标签,并在取景者的首选社交客户端中打开帖子. 这些是外出链接,不是认证的接触按钮.

使用 `blueskyPostToPostData(postView)` 转换公共 AppView 帖子,包括 `repostCount`。 。 。 . `PostContent` 支持图像,画廊,外部链接卡,HLS视频,被引用的帖子(包括其嵌入板),记录-与媒体组合,feed,列表,起动包,以及适度服务卡. 缺少、被屏蔽和被分解的引文具有可读取的占位符;未知的嵌入式变体显示不存在倒数. 出引入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入出入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入出入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入出入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入出出出出出出出出出出出出 非员额记录卡使用Bluesky路线,因为其他客户可能不会执行. Apps仍然拥有取货和节制.

### 丰富文本

`RichText` 使文本由帖子、事件描述和其他用户内容共享。 纯文本可以直接通过 :

```svelte
<RichText
    text={post.text}
    facets={post.facets}
    {socialClient}
    mentionUrl={(actor) => `/p/${encodeURIComponent(actor)}`}
/>
```

对于Markdown, 解析为 `@atmo-dev/kit/richtext` (没有Svelte或DOM;在工人中工作)和通过 `content`数字 :

```ts
// +page.server.ts
import { parseRichText } from '@atmo-dev/kit/richtext';

const content = parseRichText(text, { facets, markdown: true, detectLinks: true });
```

```svelte
<RichText {content} {socialClient} headingOffset={1} />
```

每个功能可分别打开:

-   `markdown`: 普通马克加 `~~strikethrough~~`。单行新线是换行。 Raw HTML 以书面显示。 图像从不被加载;它们成为被其备选案文所标注的链接. 表格和其他扩展保留文字。 文字超过一万个字符, 超过一千个 `*`, (中文(简体) ), `_`, (中文(简体) ), `~` 或者说 `[` 字符,或者在行上有超过8个嵌入式引号或列表标记,都是作为纯文本提供的,因为解析器在一些输入上以四倍的速度慢了下来。 无 `markdown`,文本为平白并保留了空白.
-   `facets`: 记录元件在运行时被验证 。 不规则的、重叠的或中间的字符范围会被忽略,而不丢弃文字。 在Markdown中,方方面面适用于格式化文本,例如 `**@alice**`;代码中的方块,在Markdown链接内部,跨Markdown语法或被逃脱或实体所更改的文本上的方块被忽略.
-   `detectLinks`:在没有侧面、外部代码和现有链接时检测URL、引用和标签。 检测使用引用、标签和 URL 规则 `@atcute/bluesky-richtext-parser` 在一个线性通道。 内容 `tokenize` 并且将重点和反向路线描述为 `*` (一百分钟),所以不要运行在不可信的文本上; `detectTokens` 从 `@atmo-dev/kit/richtext` 返回相容符号.

链接只接受没有证书的HTTP(S) URL. `socialClient` 选择提及和标签目的地; `mentionUrl` 使用 HTTP( S) URL 或 root- relative 路径覆盖路由 。 本地链接保留在同一标签; 外部链接使用 `noopener noreferrer nofollow ugc`。邮政文本默认保留 Bluesky 目的地.

解析 `RichTextDocument` 普通的JSON有一个 `version`,独立于取景器,因此可以在页面数据中发送并公开缓存. `RichText` 仍视其为不可信:它只使已知元素,再次检查每个链接,并将未知节点显示为文本. 优先分析每个请求(几毫秒)而不是存储文件. `richTextToPlainText(content)` 返回没有 Markdown 语法的文字,用于元描述和预览.

要在浏览器中解析, 例如在编辑器预览中, 按要求加载解析器 `await import('@atmo-dev/kit/richtext')` 因此它不是最初捆绑的一部分.

### 特殊链接嵌入

外部链接使用订购的提供者登记处。 YouTube 手表,短片,直播,嵌入,以及 `youtu.be` 链接获得一个点击到加载,隐私增强的播放器。 激活前没有装入YouTube iframe或SDK;预览只使用提供的AppView缩略图. 克丽皮的 `static.klipy.com/ii/...` 媒体链接使用 Bluesky 的媒体代理来渲染 GIF 或 循环, 调音视频, 保留 GIF 描述为 alt 文本 。 Klipy 媒体自动播放和循环,没有本地视频控制或标题行. 一个小的角上覆盖暂停/播放按钮控制动画; papersing video保留了它的当前帧. 启用了减少的动作后,它会保留在预览上,直到明确播放. 没有可用媒体 URL 的普通 Klipy 页面链接仍然是链接卡;不需要元数据 API 或 API 密钥 。 未知的URL和回放失败回落到原始的链接卡上.

过 `embedProviders` 改为 `PostContent` 或者说 `NestedComments` (它传播到所有的答复和引文)。 给YouTube + Klipy, 或者通过 `[]` 以禁用特殊玩家。 不修改渲染器而添加提供者 :

```ts
import { defaultExternalEmbedProviders, type ExternalEmbedProvider } from '@atmo-dev/kit/social';

const exampleProvider: ExternalEmbedProvider = {
    id: 'example',
    label: 'Example',
    resolve(url) {
        if (url.hostname !== 'example.com') return null;
        const id = /^\/watch\/([a-zA-Z0-9_-]+)$/.exec(url.pathname)?.[1];
        return id ? { type: 'iframe', src: `https://player.example.com/embed/${id}` } : null;
    }
};
const embedProviders = [exampleProvider, ...defaultExternalEmbedProviders];
```

供应商被信任为应用程序所有,同步的 URL 解析器返回 `iframe`, (中文(简体) ), `video` (类型来源),或 `image` 描述符 - 不是 HTML 。 匹配确切的主机,验证ID,并构建已知的玩家URL. 媒体描述符可以设置 `autoplay: true` 和视频可以设置 `controls: false` 用于类似 GIF 的回放; iframe 播放器总是需要点击 。 共享 `ExternalEmbed` 渲染器拥有回放策略,减动处理,沙盒化的iframes,安全的 URL 方案,暂停控制,源变化重置,和倒回卡. 它还出口,供单独使用.

## 首选社会客户端

`SocialClientPicker` 是一个Bluesky, mu, Blacksky, Witchsky, 或自定义网络客户端的本地形式字段组. 传出观众的救赎 `value` 联合国 `PreferredSocialClient`,或 `null` 就Bluesky而言)和可选 `disabled`它提交 `client` 和,对于定制客户, `customUrl`。应用程序拥有窗体,保存按钮、认证和反馈.

专门的公开记录是: `social.atmo.actor.preferredClient/self`数字 :

```json
{ "$type": "social.atmo.actor.preferredClient", "client": "mu" }
```

对于自定义客户端, 使用 `"client": "custom"` 财务报告和审定财务报表 `"customUrl": "https://example.com"`。这个词汇库 **仅选择社会客户**而非一般偏好。 其计划可在 `@atmo-dev/kit/lexicons/social.atmo.actor.preferredClient.json`。 。 。 .

无框架帮助导出自 `@atmo-dev/kit/social` (以及主要入口):

-   `PREFERRED_CLIENT_COLLECTION`, (中文(简体) ), `PREFERRED_CLIENT_RKEY`, (中文(简体) ), `PREFERRED_CLIENT_SCOPE` 用于存储器IO和OAuth元数据/递增许可请求。 范围授权只在此收藏上创建/更新;当用户选择授权此设置时请求,而不是在正常登录或注册时请求.
-   `parsePreferredSocialClient(record)` 验证寄存器记录或窗体输入; 无效、 未知或缺少选择返回 `null`。 。 。 .
-   `preferredSocialClientRecord(selection)` 建立有效记录。 绝不只因为读取失败而写; 区分 `RecordNotFound` 来自PDS/网络故障,以及使用 `swapRecord` 以守护最新消息.
-   `socialClientInfo(selection)`, (中文(简体) ), `socialProfileUrl(actor, selection)`, (中文(简体) ), `socialPostUrl(atUri, selection)`,以及 `socialHashtagUrl(tag, selection)` 默认为 Bluesky. `socialPostUrl` 接受 feed- post AT URI 并返回 `null` 无效者。 自定义 URL 必须是绝对的 HTTP( S) , 没有证书、 查询或片段, 并支持 `/profile/:actor` 路线。 基本路径被保留.

从他们的PDS中读取已签名的查看器记录, 在查看器数据中包含解析选择,并将其传递给 `<UserProfile {profile} socialClient={viewer.preferredSocialClient} />`。配置文件的外部按钮和处理链接使用该客户端。 传出同样的 `socialClient` 向 `NestedComments` 用于作者、回复、重新张贴、和引用链接,包括嵌入式回复。 更改选中内容更新这些链接而不更改存储的后置数据 。 生物标签也使用所选客户端. 提供应用程序 `mentionUrl` 回调到 `UserProfile` (例如, `(actor) => '/p/' + encodeURIComponent(actor)`)在同一个分页中路由 Bio 本地引用;没有它,它们打开了选定的社会客户端. 内容后方面保留其现有的 Bluesky 链接 。 Apps仍然负责服务器侧面的授权,IO,访问缺失时的递增授权,保存后将查看器数据无效.

## 登录标识符

`LoginForm` 财务报告和审定财务报表 `LoginDialog` 默认情况下接受控件和完成 。 手被剪了, 剥了铅 `@` 字符,并小写。 Dids只被修剪: 方法专用的大小写和百分比编码被保存下来 . `initialHandle` 也接受 DID;其现有名称为相容性.

这个 `login(identifier, actor?, options?)` 财务报告和审定财务报表 `onSuccess(identifier, actor?)` 回调器在正常化后收到该标识符后保持不变。 手动输入的 DOD 不编造剖面数据或最近账户记录 。 可选权限审查对两个标识符都相同.

验证是综合的,而不是证明存在账户。 Apps 拥有 DD 解析度, 支持 DD 方法, OAuth, 和会话 。 输入是否绕过可选的处理搜索提供者并取消等待的建议 .

其他登录入口,导入 `normalizeIdentifier` 财务报告和审定财务报表 `isValidIdentifier`现有 `normalizeHandle` 财务报告和审定财务报表 `isValidHandle` 仅限手柄的助手,不要用在 DCD 上.

## 法律页和页脚

`LegalDocument` 使应用程序拥有的文档数据具有可读的打字功能和可选的应用程序审查修订日期。 出版状态为元数据,不是可见的页面标签. `LegalFooter` 只制作已配置的链接。 预览实际页面 `/legal`; 作者配置和诊断有自己的观点.

```svelte
<script lang="ts">
    import { LegalDocument, LegalFooter } from '@atmo-dev/kit';
    import { privacy } from './reviewed-policies';
</script>

<LegalDocument document={privacy} />
<LegalFooter
    links={{
        terms: '/terms',
        privacy: '/privacy',
        imprint: '/imprint',
        moderation: false,
        report: '/report',
        source: { label: 'Source code', href: 'https://github.com/example/project' }
    }}
/>
```

每个目的地都是可选的:省略或设定为 `false` 藏起来 字符串使用默认标签; `{ label, href }` 定制它。 平面 `mailto:` 接受报告目的地。 不安全的 URL 无法点击 。 无链接/ 动作表示没有空页脚. 脚指向原生属性和 `class`。 。 。 .

一个应用公司 `reportAction` 片段替换 `links.report` 带有上下文按钮/对话框触发器。 设置 `links.report: false` 也禁用动作 。 页脚没有会话、报告主题、对话框或网络操作.

### 作者和审查文件

导入免费框架助手 `@atmo-dev/kit/legal` 脚本中:

```ts
import { createLegalDrafts, getLegalDraftIssues } from '@atmo-dev/kit/legal';

const config = {
    app: { name: 'Example', url: 'https://example.com', description: 'an event app' },
    operator: { name: 'Operator', email: 'hello@example.com', address: [] },
    privacy: [
        {
            id: 'storage',
            title: 'Data storage',
            paragraphs: [
                'Describe actual data, purposes, recipients, retention, legal bases, and safeguards.'
            ]
        }
    ],
    moderation: [
        {
            id: 'reports',
            title: 'Reporting',
            paragraphs: ['Describe the working report channel and review procedure.']
        }
    ]
};
console.error(getLegalDraftIssues(config));
console.log(JSON.stringify(createLegalDrafts(config), null, 2));
```

在耗用应用程序中保存生成的文件,审查/编辑其措辞,并制作快照-不是运行时间呼叫 `createLegalDrafts`。 。 。 . **Kit 升级不能默地重写已公布的政策 .** 模板是草稿,不是法律咨询,也不是遵守的保证。 它们推断没有管辖权、年龄门槛、法律依据、保留期、处理器协议、接受流量或通知承诺。 操作者的事实可以被跨应用程序再用;每个应用程序仍然需要其实际处理和术语审查.

`LegalDocumentData` 包含一个 `title`, (中文(简体) ), `status: 'draft' | 'published'`,可选应用程序 `updated` 日期,以及 `sections`。每个部分有一个独特的 `id`, (中文(简体) ), `title`选择 `paragraphs`, (中文(简体) ), `items`, (中文(简体) ), `address`,以及 `links`。文本被逃脱,而不是作为 HTML 提供。 模板提供术语、隐私、印记和节制草稿,但应用软件可以提供自己的文本/文件以及外部政策链接.

设定 `status: 'published'` 和一个ISO `updated` 只有在审查之后才确定日期. `getLegalDocumentIssues` 检查出版字段和部分标识; `getLegalDraftIssues` 检查作者配置 。 也没有决定遵守法律。 路线、页面标题、网页草稿索引编制、通知/接受机制以及强制执行仍然由应用公司负责.

## 报告

`ReportDialog` 拥有原因/细节表,待决状态和成功状态,且已签名的电子邮件回落. Apps拥有触发器,会话,主题引用,词汇链接,邮箱,以及服务器提交.

```svelte
<ReportDialog
    bind:open
    isLoggedIn={viewer.isLoggedIn}
    email="reports@example.com"
    subjectUrl={canonicalUrl}
    onsignin={showLogin}
    submit={async (input) => {
        try {
            await sendReport({ subject, ...input });
        } catch {
            return { message: 'Reporting is unavailable. Please report by email.' };
        }
    }}
/>
```

`submit` 接收 `{ reasonType, reason? }`,使用标准的 ATProto 垃圾邮件,违反,以及其他原因识别符. 空白细节被省略;所输入的细节被修剪并限于2000个字符. 成功保存或返回后无值解决 `{ message, needsSignIn? }` 安全、用户化的失败. `needsSignIn` 提供再次签名的行动。 未预料到的拒绝显示普遍失败,而不披露例外细节.

电子邮件回落预填主题链接和任何输入的细节 。 打开新对话框或更改 `subjectUrl` 重新设置状态; stale async 结果被忽略 。 对话框不会导入一个应用程序的 Auth singleton, 联系一个温和的服务, 或者决定谁可以报告一个主题 。 服务器的授权仍然是强制性的.

## 常见问题

### @atmo-dev/kit 是什么？

Atmo apps的共享UI基金会\.

### 如何安装 @atmo-dev/kit？

在项目目录运行 npm install @atmo-dev/kit，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：svelte（\>=5\.48\.0 \<6）、tailwindcss（^4\.0\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @atmo-dev/kit 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @atmo-dev/kit 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @atmo-dev/kit 可以商用吗？

@atmo-dev/kit 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @atmo-dev/kit 的下载量有多少？

根据 npm 官方下载统计，@atmo-dev/kit 在 2026/10/07 – 2026/11/05 这 30 天内被下载 1,761 次，日均约 59 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @atmo-dev/kit 还在维护吗？

最新版本 0\.9\.0 发布于 2026/11/08（1 天前）；至今共发布 15 个版本，其中过去 12 个月发布了 15 个；首次发布于 2026/10/28。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

