@lapis-notes/workspace 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE.md +3 -3
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +3 -0
  5. package/dist/plugin-management/CommunityPluginsSettingsPage.svelte +152 -73
  6. package/dist/plugin-management/PluginBrowser.svelte +558 -0
  7. package/dist/plugin-management/PluginBrowser.svelte.d.ts +17 -0
  8. package/dist/plugin-management/PluginBrowserCard.svelte +230 -0
  9. package/dist/plugin-management/PluginBrowserCard.svelte.d.ts +9 -0
  10. package/dist/plugin-management/PluginBrowserDetail.svelte +1168 -0
  11. package/dist/plugin-management/PluginBrowserDetail.svelte.d.ts +20 -0
  12. package/dist/plugin-management/PluginBrowserDirectory.svelte +1069 -0
  13. package/dist/plugin-management/PluginBrowserDirectory.svelte.d.ts +21 -0
  14. package/dist/plugin-management/PluginBrowserLanding.svelte +341 -0
  15. package/dist/plugin-management/PluginBrowserLanding.svelte.d.ts +12 -0
  16. package/dist/plugin-management/PluginRegistryBadge.svelte +41 -0
  17. package/dist/plugin-management/PluginRegistryBadge.svelte.d.ts +8 -0
  18. package/dist/plugin-management/PluginRegistryContentState.svelte +80 -0
  19. package/dist/plugin-management/PluginRegistryContentState.svelte.d.ts +14 -0
  20. package/dist/plugin-management/PluginRegistryImage.svelte +64 -0
  21. package/dist/plugin-management/PluginRegistryImage.svelte.d.ts +9 -0
  22. package/dist/plugin-management/PluginRegistryMedia.svelte +223 -0
  23. package/dist/plugin-management/PluginRegistryMedia.svelte.d.ts +8 -0
  24. package/dist/plugin-management/PluginRegistryMediaViewer.svelte +155 -0
  25. package/dist/plugin-management/PluginRegistryMediaViewer.svelte.d.ts +13 -0
  26. package/dist/plugin-management/PluginRegistryRow.svelte +120 -0
  27. package/dist/plugin-management/PluginRegistryRow.svelte.d.ts +25 -0
  28. package/dist/plugin-management/PluginRegistrySettingsPage.svelte +1009 -512
  29. package/dist/plugin-management/PluginRegistryStatus.svelte +33 -0
  30. package/dist/plugin-management/PluginRegistryStatus.svelte.d.ts +8 -0
  31. package/dist/plugin-management/PluginRegistryToolbar.svelte +159 -0
  32. package/dist/plugin-management/PluginRegistryToolbar.svelte.d.ts +21 -0
  33. package/dist/plugin-management/community-presentation/CommunityChip.svelte +229 -0
  34. package/dist/plugin-management/community-presentation/CommunityChip.svelte.d.ts +4 -0
  35. package/dist/plugin-management/community-presentation/CommunityMemberAvatar.svelte +42 -0
  36. package/dist/plugin-management/community-presentation/CommunityMemberAvatar.svelte.d.ts +13 -0
  37. package/dist/plugin-management/community-presentation/RegistryMediaGallery.svelte +282 -0
  38. package/dist/plugin-management/community-presentation/RegistryMediaGallery.svelte.d.ts +8 -0
  39. package/dist/plugin-management/community-presentation/RegistryMediaViewer.css +179 -0
  40. package/dist/plugin-management/community-presentation/RegistryMediaViewer.svelte +241 -0
  41. package/dist/plugin-management/community-presentation/RegistryMediaViewer.svelte.d.ts +14 -0
  42. package/dist/plugin-management/community-presentation/RegistryPluginChangelog.svelte +216 -0
  43. package/dist/plugin-management/community-presentation/RegistryPluginChangelog.svelte.d.ts +8 -0
  44. package/dist/plugin-management/community-presentation/RegistryVerifiedImage.svelte +179 -0
  45. package/dist/plugin-management/community-presentation/RegistryVerifiedImage.svelte.d.ts +15 -0
  46. package/dist/plugin-management/community-presentation/avatar-model.d.ts +14 -0
  47. package/dist/plugin-management/community-presentation/avatar-model.js +21 -0
  48. package/dist/plugin-management/community-presentation/community-member-avatar.css +137 -0
  49. package/dist/plugin-management/community-presentation/community-typography.css +30 -0
  50. package/dist/plugin-management/community-presentation/model-adapter.d.ts +54 -0
  51. package/dist/plugin-management/community-presentation/model-adapter.js +90 -0
  52. package/dist/plugin-management/community-presentation/model.d.ts +9 -0
  53. package/dist/plugin-management/community-presentation/model.js +40 -0
  54. package/dist/plugin-management/community-presentation/props.d.ts +21 -0
  55. package/dist/plugin-management/community-presentation/props.js +1 -0
  56. package/dist/plugin-management/community-presentation/registry-filter-icons.d.ts +3 -0
  57. package/dist/plugin-management/community-presentation/registry-filter-icons.js +70 -0
  58. package/dist/plugin-management/community-presentation/registry-markdown.d.ts +1 -0
  59. package/dist/plugin-management/community-presentation/registry-markdown.js +57 -0
  60. package/dist/plugin-management/community-presentation/registry-plugin-icons.d.ts +2 -0
  61. package/dist/plugin-management/community-presentation/registry-plugin-icons.js +31 -0
  62. package/dist/plugin-management/plugin-browser-model.d.ts +44 -0
  63. package/dist/plugin-management/plugin-browser-model.js +104 -0
  64. package/dist/plugin-management/plugin-browser-navigation.d.ts +3 -0
  65. package/dist/plugin-management/plugin-browser-navigation.js +38 -0
  66. package/dist/plugin-management/plugin-download-stats.d.ts +1 -0
  67. package/dist/plugin-management/plugin-download-stats.js +15 -0
  68. package/dist/plugin-management/plugin-management.css +1088 -117
  69. package/dist/plugin-management/plugin-registry-controller.svelte.d.ts +29 -0
  70. package/dist/plugin-management/plugin-registry-controller.svelte.js +154 -0
  71. package/dist/plugin-management/plugin-registry-detail-session.svelte.d.ts +18 -0
  72. package/dist/plugin-management/plugin-registry-detail-session.svelte.js +106 -0
  73. package/dist/plugin-management/plugin-registry-detail.d.ts +17 -0
  74. package/dist/plugin-management/plugin-registry-detail.js +1 -0
  75. package/dist/plugin-management/plugins-view.svelte.d.ts +41 -0
  76. package/dist/plugin-management/plugins-view.svelte.js +109 -0
  77. package/package.json +5 -5
@@ -0,0 +1,14 @@
1
+ import type { RegistryMediaItem } from "./model-adapter";
2
+ import "./RegistryMediaViewer.css";
3
+ type MediaMode = "preview" | "full" | "light" | "dark";
4
+ type $$ComponentProps = {
5
+ items: readonly RegistryMediaItem[];
6
+ pluginName: string;
7
+ open?: boolean;
8
+ selectedIndex?: number;
9
+ initialMode?: MediaMode;
10
+ opener?: HTMLElement | null;
11
+ };
12
+ declare const RegistryMediaViewer: import("svelte").Component<$$ComponentProps, {}, "open" | "selectedIndex">;
13
+ type RegistryMediaViewer = ReturnType<typeof RegistryMediaViewer>;
14
+ export default RegistryMediaViewer;
@@ -0,0 +1,216 @@
1
+ <script lang="ts">
2
+ import {
3
+ type RegistryChangelogTag,
4
+ type RegistryRelease,
5
+ } from "./model-adapter";
6
+ import CommunityChip from "./CommunityChip.svelte";
7
+
8
+ import { renderRegistryMarkdown } from "./registry-markdown";
9
+ let {
10
+ latestVersion,
11
+ releases,
12
+ }: {
13
+ latestVersion: string;
14
+ releases: readonly RegistryRelease[];
15
+ } = $props();
16
+
17
+ const dateFormatter = new Intl.DateTimeFormat("en", {
18
+ dateStyle: "medium",
19
+ timeZone: "UTC",
20
+ });
21
+
22
+ const tagLabels: Record<RegistryChangelogTag, string> = {
23
+ feature: "Feature",
24
+ improvement: "Improvement",
25
+ fix: "Fix",
26
+ security: "Security",
27
+ breaking: "Breaking",
28
+ deprecation: "Deprecation",
29
+ removal: "Removal",
30
+ };
31
+
32
+ function versionLabel(version: string): string {
33
+ return version.startsWith("v") ? version : `v${version}`;
34
+ }
35
+
36
+ function releasedAt(release: RegistryRelease): string {
37
+ return dateFormatter.format(new Date(release.releasedAt));
38
+ }
39
+
40
+ function noteTitle(release: RegistryRelease): string {
41
+ return (
42
+ release.changelog?.title ?? `${versionLabel(release.version)} release`
43
+ );
44
+ }
45
+
46
+ function noteDescription(release: RegistryRelease): string | undefined {
47
+ return release.changelog?.description ?? release.releasePost;
48
+ }
49
+
50
+ function noteHtml(release: RegistryRelease): string | undefined {
51
+ const description = noteDescription(release);
52
+ return description === undefined
53
+ ? undefined
54
+ : renderRegistryMarkdown(description);
55
+ }
56
+ </script>
57
+
58
+ <div class="registry-plugin-changelog-list">
59
+ {#each releases as release (release.version)}
60
+ <article
61
+ class="registry-plugin-changelog"
62
+ aria-label={`${release.version} changelog`}
63
+ >
64
+ <header class="registry-plugin-changelog__header">
65
+ <div class="registry-plugin-changelog__chips">
66
+ <CommunityChip
67
+ class="registry-plugin-changelog__version"
68
+ variant="muted"
69
+ tone="success">{versionLabel(release.version)}</CommunityChip
70
+ >
71
+ {#if release.version === latestVersion}
72
+ <CommunityChip
73
+ class="registry-plugin-changelog__latest"
74
+ variant="outline"
75
+ tone="success">Latest</CommunityChip
76
+ >
77
+ {/if}
78
+ </div>
79
+ <time datetime={release.releasedAt}>{releasedAt(release)}</time>
80
+ </header>
81
+
82
+ <div class="registry-plugin-changelog__prose">
83
+ <h2 class="registry-plugin-changelog__title">{noteTitle(release)}</h2>
84
+ {#if release.changelog?.tags?.length}
85
+ <div
86
+ class="registry-plugin-changelog__tags"
87
+ aria-label="Changelog tags"
88
+ >
89
+ {#each release.changelog.tags as tag}
90
+ <CommunityChip variant="subtle" size="compact"
91
+ >{tagLabels[tag]}</CommunityChip
92
+ >
93
+ {/each}
94
+ </div>
95
+ {/if}
96
+ {#if noteHtml(release)}
97
+ {@html noteHtml(release)}
98
+ {:else}
99
+ <p>This release does not include verified changelog notes.</p>
100
+ {/if}
101
+ </div>
102
+ </article>
103
+ {:else}
104
+ <article class="registry-plugin-changelog" aria-label="No changelog">
105
+ <div class="registry-plugin-changelog__prose">
106
+ <h2 class="registry-plugin-changelog__title">
107
+ No structured changelog yet
108
+ </h2>
109
+ <p>This plugin does not include verified changelog notes.</p>
110
+ </div>
111
+ </article>
112
+ {/each}
113
+ </div>
114
+
115
+ <style>
116
+ .registry-plugin-changelog-list {
117
+ display: grid;
118
+ gap: 1rem;
119
+ }
120
+
121
+ .registry-plugin-changelog {
122
+ display: grid;
123
+ min-width: 0;
124
+ gap: 1.4rem;
125
+ padding: clamp(1.4rem, 4vw, 2.4rem);
126
+ border: 1px solid color-mix(in srgb, var(--border) 86%, transparent);
127
+ border-radius: calc(var(--radius) * 1.8);
128
+ background: var(--background);
129
+ box-shadow: 0 1.25rem 3rem
130
+ color-mix(in srgb, var(--foreground) 7%, transparent);
131
+ }
132
+
133
+ .registry-plugin-changelog__header {
134
+ display: flex;
135
+ min-width: 0;
136
+ align-items: center;
137
+ justify-content: space-between;
138
+ gap: 1rem;
139
+ }
140
+
141
+ .registry-plugin-changelog__chips {
142
+ display: flex;
143
+ min-width: 0;
144
+ align-items: center;
145
+ flex-wrap: wrap;
146
+ gap: 0.7rem;
147
+ }
148
+
149
+ .registry-plugin-changelog__header time {
150
+ color: color-mix(in srgb, var(--muted-foreground) 84%, var(--foreground));
151
+ font-size: var(--community-text-section);
152
+ font-weight: 500;
153
+ white-space: nowrap;
154
+ }
155
+
156
+ .registry-plugin-changelog__prose {
157
+ display: grid;
158
+ gap: 1rem;
159
+ color: color-mix(in srgb, var(--muted-foreground) 84%, var(--foreground));
160
+ font-size: var(--community-text-body);
161
+ line-height: 1.55;
162
+ }
163
+
164
+ .registry-plugin-changelog__prose :global(*) {
165
+ margin: 0;
166
+ }
167
+
168
+ .registry-plugin-changelog__title,
169
+ .registry-plugin-changelog__prose :global(h1) {
170
+ color: var(--foreground);
171
+ font-size: var(--community-text-page);
172
+ font-weight: 800;
173
+ line-height: 1.12;
174
+ }
175
+
176
+ .registry-plugin-changelog__prose :global(h2),
177
+ .registry-plugin-changelog__prose :global(h3) {
178
+ margin-top: 0.75rem;
179
+ color: var(--foreground);
180
+ font-size: var(--community-text-section);
181
+ font-weight: 760;
182
+ line-height: 1.2;
183
+ }
184
+
185
+ .registry-plugin-changelog__prose :global(ul),
186
+ .registry-plugin-changelog__prose :global(ol) {
187
+ display: grid;
188
+ gap: 0.45rem;
189
+ padding-inline-start: 1.65rem;
190
+ }
191
+
192
+ .registry-plugin-changelog__prose :global(li::marker) {
193
+ color: color-mix(in srgb, var(--muted-foreground) 76%, transparent);
194
+ }
195
+
196
+ .registry-plugin-changelog__prose :global(code) {
197
+ border-radius: calc(var(--radius) * 0.6);
198
+ background: color-mix(in srgb, var(--muted) 76%, transparent);
199
+ color: color-mix(in srgb, var(--foreground) 72%, var(--muted-foreground));
200
+ padding: 0.08em 0.28em;
201
+ }
202
+
203
+ .registry-plugin-changelog__tags {
204
+ display: flex;
205
+ align-items: center;
206
+ flex-wrap: wrap;
207
+ gap: 0.4rem;
208
+ }
209
+
210
+ @media (max-width: 34rem) {
211
+ .registry-plugin-changelog__header {
212
+ align-items: flex-start;
213
+ flex-direction: column;
214
+ }
215
+ }
216
+ </style>
@@ -0,0 +1,8 @@
1
+ import { type RegistryRelease } from "./model-adapter";
2
+ type $$ComponentProps = {
3
+ latestVersion: string;
4
+ releases: readonly RegistryRelease[];
5
+ };
6
+ declare const RegistryPluginChangelog: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type RegistryPluginChangelog = ReturnType<typeof RegistryPluginChangelog>;
8
+ export default RegistryPluginChangelog;
@@ -0,0 +1,179 @@
1
+ <script lang="ts">
2
+ import { Skeleton } from "@lapismd/design-core/shadcn/skeleton";
3
+ import * as Empty from "@lapismd/design-core/shadcn/empty";
4
+ import ImageOff from "@lucide/svelte/icons/image-off";
5
+ import type { Snippet } from "svelte";
6
+ import type { HTMLImgAttributes } from "svelte/elements";
7
+
8
+ import { fetchVerifiedPluginImage } from "@lapis-notes/api";
9
+ import type { RegistryImageReference } from "./model-adapter";
10
+
11
+ let {
12
+ reference,
13
+ alt,
14
+ fallback,
15
+ width,
16
+ height,
17
+ loading,
18
+ class: className,
19
+ }: {
20
+ reference: RegistryImageReference;
21
+ alt: string;
22
+ fallback?: Snippet;
23
+ width?: HTMLImgAttributes["width"];
24
+ height?: HTMLImgAttributes["height"];
25
+ loading?: HTMLImgAttributes["loading"];
26
+ class?: HTMLImgAttributes["class"];
27
+ } = $props();
28
+
29
+ const loader = (
30
+ reference: RegistryImageReference,
31
+ options: { signal: AbortSignal },
32
+ ) => fetchVerifiedPluginImage(reference, fetch, options.signal);
33
+ let resolvedUrl = $state("");
34
+ let failed = $state(false);
35
+ let decoded = $state(false);
36
+ let observedElement = $state<HTMLElement>();
37
+ let inView = $state(false);
38
+
39
+ $effect(() => {
40
+ if (loader === undefined || loading !== "lazy") {
41
+ inView = true;
42
+ return;
43
+ }
44
+ const element = observedElement;
45
+ if (element === undefined || typeof IntersectionObserver === "undefined") {
46
+ inView = true;
47
+ return;
48
+ }
49
+ const observer = new IntersectionObserver(
50
+ (entries) => {
51
+ if (entries.some((entry) => entry.isIntersecting)) inView = true;
52
+ },
53
+ { rootMargin: "240px" },
54
+ );
55
+ observer.observe(element);
56
+ return () => observer.disconnect();
57
+ });
58
+
59
+ $effect(() => {
60
+ const current = reference;
61
+ if (loader === undefined) {
62
+ resolvedUrl = current.url;
63
+ failed = false;
64
+ return;
65
+ }
66
+ if (!inView) return;
67
+
68
+ const controller = new AbortController();
69
+ let active = true;
70
+ let objectUrl = "";
71
+ resolvedUrl = "";
72
+ failed = false;
73
+ decoded = false;
74
+ void loader(current, { signal: controller.signal })
75
+ .then((blob) => {
76
+ if (!active) return;
77
+ objectUrl = URL.createObjectURL(blob);
78
+ resolvedUrl = objectUrl;
79
+ })
80
+ .catch((cause: unknown) => {
81
+ if (
82
+ active &&
83
+ (!(cause instanceof DOMException) || cause.name !== "AbortError")
84
+ ) {
85
+ failed = true;
86
+ }
87
+ });
88
+
89
+ return () => {
90
+ active = false;
91
+ controller.abort();
92
+ if (objectUrl !== "") URL.revokeObjectURL(objectUrl);
93
+ };
94
+ });
95
+ </script>
96
+
97
+ <div
98
+ bind:this={observedElement}
99
+ class={["registry-verified-image", className].filter(Boolean).join(" ")}
100
+ data-image-state={failed ? "failed" : decoded ? "loaded" : "loading"}
101
+ style:aspect-ratio={width && height ? `${width} / ${height}` : undefined}
102
+ role={!decoded && alt ? "img" : undefined}
103
+ aria-label={!decoded && alt
104
+ ? `${alt}${failed ? " unavailable" : " loading"}`
105
+ : undefined}
106
+ aria-busy={!decoded && !failed ? "true" : undefined}
107
+ >
108
+ {#if resolvedUrl && !failed}
109
+ <img
110
+ src={resolvedUrl}
111
+ {alt}
112
+ {width}
113
+ {height}
114
+ loading="eager"
115
+ class:decoded
116
+ onload={() => (decoded = true)}
117
+ onerror={() => {
118
+ failed = true;
119
+ decoded = false;
120
+ }}
121
+ />
122
+ {/if}
123
+ {#if failed}
124
+ {#if fallback}{@render fallback()}{:else}
125
+ <Empty.Root
126
+ class="registry-verified-image__empty"
127
+ aria-hidden={alt === "" ? "true" : undefined}
128
+ >
129
+ <Empty.Header>
130
+ <Empty.Media variant="icon"
131
+ ><ImageOff aria-hidden="true" /></Empty.Media
132
+ >
133
+ {#if alt}<Empty.Title>Image unavailable</Empty.Title>{/if}
134
+ </Empty.Header>
135
+ </Empty.Root>
136
+ {/if}
137
+ {:else if !decoded}
138
+ <Skeleton class="registry-verified-image__skeleton" aria-hidden="true" />
139
+ {/if}
140
+ </div>
141
+
142
+ <style>
143
+ .registry-verified-image {
144
+ display: grid;
145
+ place-items: center;
146
+ width: 100%;
147
+ height: 100%;
148
+ min-width: 0;
149
+ min-height: 2rem;
150
+ overflow: hidden;
151
+ border-radius: inherit;
152
+ }
153
+ .registry-verified-image > img {
154
+ grid-area: 1 / 1;
155
+ display: block;
156
+ width: 100%;
157
+ height: 100%;
158
+ max-width: 100%;
159
+ max-height: 100%;
160
+ object-fit: contain;
161
+ opacity: 0;
162
+ }
163
+ .registry-verified-image > img.decoded {
164
+ opacity: 1;
165
+ }
166
+ .registry-verified-image :global(.registry-verified-image__skeleton),
167
+ .registry-verified-image :global(.registry-verified-image__empty) {
168
+ grid-area: 1 / 1;
169
+ width: 100%;
170
+ height: 100%;
171
+ min-width: 0;
172
+ min-height: 0;
173
+ }
174
+ .registry-verified-image :global(.registry-verified-image__empty) {
175
+ padding: 0.5rem;
176
+ background: var(--muted);
177
+ color: var(--muted-foreground);
178
+ }
179
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLImgAttributes } from "svelte/elements";
3
+ import type { RegistryImageReference } from "./model-adapter";
4
+ type $$ComponentProps = {
5
+ reference: RegistryImageReference;
6
+ alt: string;
7
+ fallback?: Snippet;
8
+ width?: HTMLImgAttributes["width"];
9
+ height?: HTMLImgAttributes["height"];
10
+ loading?: HTMLImgAttributes["loading"];
11
+ class?: HTMLImgAttributes["class"];
12
+ };
13
+ declare const RegistryVerifiedImage: import("svelte").Component<$$ComponentProps, {}, "">;
14
+ type RegistryVerifiedImage = ReturnType<typeof RegistryVerifiedImage>;
15
+ export default RegistryVerifiedImage;
@@ -0,0 +1,14 @@
1
+ type CommunityMemberModel = {
2
+ kind?: string;
3
+ };
4
+ export type CommunityMemberAvatarShape = "circle" | "rounded";
5
+ export declare function communityMemberAvatarShape(member: Pick<CommunityMemberModel, "kind">): CommunityMemberAvatarShape;
6
+ export declare const communityMemberAvatarFallbackTones: readonly ["blue", "emerald", "amber", "rose", "cyan", "violet", "orange"];
7
+ export type CommunityMemberAvatarFallbackTone = (typeof communityMemberAvatarFallbackTones)[number];
8
+ /**
9
+ * Select the same deterministic fallback tone as Buzz's shared UserAvatar.
10
+ * Keep the display name as the seed so renamed profiles receive the same
11
+ * presentation in both clients.
12
+ */
13
+ export declare function communityMemberAvatarFallbackTone(displayName: string): CommunityMemberAvatarFallbackTone;
14
+ export {};
@@ -0,0 +1,21 @@
1
+ export function communityMemberAvatarShape(member) {
2
+ return member.kind === "agent" ? "rounded" : "circle";
3
+ }
4
+ export const communityMemberAvatarFallbackTones = [
5
+ "blue",
6
+ "emerald",
7
+ "amber",
8
+ "rose",
9
+ "cyan",
10
+ "violet",
11
+ "orange",
12
+ ];
13
+ /**
14
+ * Select the same deterministic fallback tone as Buzz's shared UserAvatar.
15
+ * Keep the display name as the seed so renamed profiles receive the same
16
+ * presentation in both clients.
17
+ */
18
+ export function communityMemberAvatarFallbackTone(displayName) {
19
+ const hash = Array.from(displayName.trim().toLowerCase()).reduce((value, character) => (value * 31 + (character.codePointAt(0) ?? 0)) >>> 0, 0);
20
+ return communityMemberAvatarFallbackTones[hash % communityMemberAvatarFallbackTones.length];
21
+ }
@@ -0,0 +1,137 @@
1
+ .community-member-avatar {
2
+ --community-member-avatar-size: 2.25rem;
3
+ --community-member-avatar-presence-size: 0.625rem;
4
+
5
+ position: relative;
6
+ display: block;
7
+ width: var(--community-member-avatar-size);
8
+ height: var(--community-member-avatar-size);
9
+ flex: none;
10
+ }
11
+
12
+ .community-member-avatar[data-size="inline"] {
13
+ --community-member-avatar-size: 1.25em;
14
+
15
+ display: inline-grid;
16
+ align-self: center;
17
+ vertical-align: middle;
18
+ }
19
+
20
+ .community-member-avatar[data-size="inline"] .community-member-avatar__image {
21
+ font-size: var(--community-text-avatar-initials);
22
+ line-height: 1;
23
+ }
24
+
25
+ .community-member-avatar[data-size="small"] {
26
+ --community-member-avatar-size: 2rem;
27
+ --community-member-avatar-presence-size: 0.55rem;
28
+ }
29
+
30
+ .community-member-avatar[data-size="xsmall"] {
31
+ --community-member-avatar-size: 1.5rem;
32
+ --community-member-avatar-presence-size: 0.45rem;
33
+ }
34
+
35
+ .community-member-avatar[data-size="xsmall"] .community-member-avatar__image {
36
+ font-size: var(--community-text-avatar-initials);
37
+ line-height: 1;
38
+ }
39
+
40
+ .community-member-avatar[data-size="large"] {
41
+ --community-member-avatar-size: 4rem;
42
+ --community-member-avatar-presence-size: 0.85rem;
43
+ }
44
+
45
+ .community-member-avatar[data-size="xlarge"] {
46
+ --community-member-avatar-size: 6rem;
47
+ --community-member-avatar-presence-size: 1rem;
48
+ }
49
+
50
+ .community-member-avatar[data-shape="rounded"] .community-member-avatar__image {
51
+ border-radius: 25%;
52
+ }
53
+
54
+ .community-member-avatar__image {
55
+ display: grid;
56
+ width: 100%;
57
+ height: 100%;
58
+ place-items: center;
59
+ overflow: hidden;
60
+ border-radius: 50%;
61
+ font-size: var(--community-text-body);
62
+ font-weight: 700;
63
+ }
64
+
65
+ .community-member-avatar__image[data-fallback-tone="blue"] {
66
+ color: #ffffff;
67
+ background: #2563eb;
68
+ }
69
+
70
+ .community-member-avatar__image[data-fallback-tone="emerald"] {
71
+ color: #ffffff;
72
+ background: #047857;
73
+ }
74
+
75
+ .community-member-avatar__image[data-fallback-tone="amber"] {
76
+ color: #451a03;
77
+ background: #fbbf24;
78
+ }
79
+
80
+ .community-member-avatar__image[data-fallback-tone="rose"] {
81
+ color: #ffffff;
82
+ background: #e11d48;
83
+ }
84
+
85
+ .community-member-avatar__image[data-fallback-tone="cyan"] {
86
+ color: #083344;
87
+ background: #22d3ee;
88
+ }
89
+
90
+ .community-member-avatar__image[data-fallback-tone="violet"] {
91
+ color: #ffffff;
92
+ background: #7c3aed;
93
+ }
94
+
95
+ .community-member-avatar__image[data-fallback-tone="orange"] {
96
+ color: #ffffff;
97
+ background: #c2410c;
98
+ }
99
+
100
+ .community-member-avatar__image img {
101
+ display: block;
102
+ width: 100%;
103
+ height: 100%;
104
+ border-radius: inherit;
105
+ object-fit: cover;
106
+ }
107
+
108
+ .community-member-avatar__image [data-ui-part="emoji"] {
109
+ font-size: inherit;
110
+ font-weight: 400;
111
+ line-height: 1;
112
+ }
113
+
114
+ .community-member-avatar__presence {
115
+ position: absolute;
116
+ right: -0.08rem;
117
+ bottom: -0.08rem;
118
+ width: var(--community-member-avatar-presence-size);
119
+ height: var(--community-member-avatar-presence-size);
120
+ border: 2px solid
121
+ var(--community-member-avatar-presence-ring, var(--background));
122
+ border-radius: 50%;
123
+ background: var(--muted-foreground);
124
+ }
125
+
126
+ .community-member-avatar__presence[data-presence="online"] {
127
+ background: var(--success, #08b94e);
128
+ }
129
+
130
+ .community-member-avatar__presence[data-presence="away"] {
131
+ background: var(--warning, #d99a00);
132
+ }
133
+
134
+ .community-member-avatar__presence[data-presence="unknown"] {
135
+ background: var(--background);
136
+ box-shadow: inset 0 0 0 1px var(--muted-foreground);
137
+ }
@@ -0,0 +1,30 @@
1
+ /* One Community scale. Public components and portalled recipes use the same tokens. */
2
+ :root {
3
+ --community-text-body: 0.875rem;
4
+ --community-text-secondary: 0.75rem;
5
+ --community-text-avatar-initials: 0.625rem;
6
+ --community-text-section: 1rem;
7
+ --community-text-dialog: 1.125rem;
8
+ --community-text-page: 1.5rem;
9
+ --ui-dialog-frame-font-size: var(--community-text-body);
10
+ --ui-dialog-frame-title-size: var(--community-text-dialog);
11
+ }
12
+ :where(
13
+ .community-ui,
14
+ .community-application,
15
+ .community-application-root,
16
+ .agents,
17
+ .agent-profile,
18
+ .agent-activity
19
+ ) {
20
+ font-family: var(--font-sans, sans-serif);
21
+ font-size: var(--community-text-body);
22
+ line-height: 1.5;
23
+ }
24
+ :where(.community-ui) h1 {
25
+ font-size: var(--community-text-page);
26
+ }
27
+ :where(.community-ui) h2,
28
+ :where(.community-ui) h3 {
29
+ font-size: var(--community-text-section);
30
+ }
@@ -0,0 +1,54 @@
1
+ import { resolveRegistryMedia, type PluginCatalogEntry, type PluginCatalogDetail, type PluginCatalogRelease, type RegistryMediaV2 } from "@lapis-notes/api";
2
+ export type RegistryCatalogEntry = PluginCatalogEntry;
3
+ export type RegistryImageReference = ReturnType<typeof resolveRegistryMedia>;
4
+ export type RegistryMediaItem = Omit<RegistryMediaV2, "images"> & {
5
+ images: Record<"preview" | "full" | "light" | "dark", RegistryImageReference>;
6
+ };
7
+ export type RegistryGalleryItem = RegistryMediaItem & {
8
+ id: string;
9
+ };
10
+ export type RegistryRelease = PluginCatalogRelease & {
11
+ releaseSequence?: string;
12
+ bundle: PluginCatalogRelease["bundle"] & {
13
+ size: number;
14
+ pending: boolean;
15
+ };
16
+ };
17
+ export type RegistryChangelogTag = NonNullable<NonNullable<PluginCatalogRelease["changelog"]>["tags"]>[number];
18
+ export type RegistryResolvedDetail = {
19
+ entry: PluginCatalogEntry;
20
+ detail: Omit<PluginCatalogDetail, "appearance" | "media" | "gallery" | "versions" | "status"> & {
21
+ status: NonNullable<PluginCatalogDetail["status"]>;
22
+ appearance?: {
23
+ icon: string;
24
+ accent: string;
25
+ logo?: RegistryImageReference & {
26
+ alt: string;
27
+ };
28
+ };
29
+ media?: {
30
+ banner?: RegistryMediaItem;
31
+ overview?: RegistryMediaItem;
32
+ };
33
+ gallery?: RegistryGalleryItem[];
34
+ versions: Record<string, RegistryRelease>;
35
+ };
36
+ };
37
+ export type RegistryInstallAction = {
38
+ state: "available" | "installed" | "installing" | "disabled" | "rejected";
39
+ label?: string;
40
+ reason?: string;
41
+ installedVersion?: string;
42
+ };
43
+ export type RegistryInstallRequest = {
44
+ pluginId: string;
45
+ version: string;
46
+ bundle: RegistryRelease["bundle"];
47
+ };
48
+ export declare function imageReference(media: RegistryMediaV2): RegistryImageReference & {
49
+ alt: string;
50
+ };
51
+ export declare function communityDetail(entry: PluginCatalogEntry, detail: PluginCatalogDetail): RegistryResolvedDetail;
52
+ export declare function registryStatusLabel(status?: string): string;
53
+ export declare function titleCaseRegistryValue(value: string): string;
54
+ export declare function formatRegistryBytes(size: number): string;