nostr-components 0.2.0

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 (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +334 -0
  3. package/dist/assets/base-styles-CBypR3FR.js +145 -0
  4. package/dist/assets/base-styles-CBypR3FR.js.map +1 -0
  5. package/dist/assets/copy-delegation-C4uvRTVM.js +15 -0
  6. package/dist/assets/copy-delegation-C4uvRTVM.js.map +1 -0
  7. package/dist/assets/dialog-component-Dqg0QU9I.js +66 -0
  8. package/dist/assets/dialog-component-Dqg0QU9I.js.map +1 -0
  9. package/dist/assets/dialog-likers-BzTesCZa.js +238 -0
  10. package/dist/assets/dialog-likers-BzTesCZa.js.map +1 -0
  11. package/dist/assets/icons-Dr_d9MII.js +105 -0
  12. package/dist/assets/icons-Dr_d9MII.js.map +1 -0
  13. package/dist/assets/nip05-utils-BNBHUmkr.js +2 -0
  14. package/dist/assets/nip05-utils-BNBHUmkr.js.map +1 -0
  15. package/dist/assets/nostr-service-pr_crY62.js +78 -0
  16. package/dist/assets/nostr-service-pr_crY62.js.map +1 -0
  17. package/dist/assets/nostr-user-component-Q7GeeFyu.js +2 -0
  18. package/dist/assets/nostr-user-component-Q7GeeFyu.js.map +1 -0
  19. package/dist/assets/preload-helper-D7HrI6pR.js +2 -0
  20. package/dist/assets/preload-helper-D7HrI6pR.js.map +1 -0
  21. package/dist/assets/pure-jrVhRVpB.js +2 -0
  22. package/dist/assets/pure-jrVhRVpB.js.map +1 -0
  23. package/dist/assets/theme-C1r1Zw8r.js +2 -0
  24. package/dist/assets/theme-C1r1Zw8r.js.map +1 -0
  25. package/dist/assets/user-resolver-C-E6KdwY.js +2 -0
  26. package/dist/assets/user-resolver-C-E6KdwY.js.map +1 -0
  27. package/dist/assets/utils--bxLbhGF.js +2 -0
  28. package/dist/assets/utils--bxLbhGF.js.map +1 -0
  29. package/dist/assets/zap-utils-B1sz0Abx.js +2 -0
  30. package/dist/assets/zap-utils-B1sz0Abx.js.map +1 -0
  31. package/dist/components/nostr-comment.es.js +924 -0
  32. package/dist/components/nostr-comment.es.js.map +1 -0
  33. package/dist/components/nostr-dm.es.js +217 -0
  34. package/dist/components/nostr-dm.es.js.map +1 -0
  35. package/dist/components/nostr-follow-button.es.js +103 -0
  36. package/dist/components/nostr-follow-button.es.js.map +1 -0
  37. package/dist/components/nostr-like.es.js +296 -0
  38. package/dist/components/nostr-like.es.js.map +1 -0
  39. package/dist/components/nostr-live-chat.es.js +523 -0
  40. package/dist/components/nostr-live-chat.es.js.map +1 -0
  41. package/dist/components/nostr-post.es.js +441 -0
  42. package/dist/components/nostr-post.es.js.map +1 -0
  43. package/dist/components/nostr-profile-badge.es.js +100 -0
  44. package/dist/components/nostr-profile-badge.es.js.map +1 -0
  45. package/dist/components/nostr-profile.es.js +287 -0
  46. package/dist/components/nostr-profile.es.js.map +1 -0
  47. package/dist/components/nostr-zap.es.js +694 -0
  48. package/dist/components/nostr-zap.es.js.map +1 -0
  49. package/dist/index.d.ts +2 -0
  50. package/dist/nostr-comment.d.ts +4 -0
  51. package/dist/nostr-components.es.js +2 -0
  52. package/dist/nostr-components.es.js.map +1 -0
  53. package/dist/nostr-components.umd.js +4200 -0
  54. package/dist/nostr-components.umd.js.map +1 -0
  55. package/dist/nostr-dm.d.ts +4 -0
  56. package/dist/nostr-follow-button.d.ts +4 -0
  57. package/dist/nostr-like.d.ts +4 -0
  58. package/dist/nostr-live-chat.d.ts +4 -0
  59. package/dist/nostr-post.d.ts +4 -0
  60. package/dist/nostr-profile-badge.d.ts +4 -0
  61. package/dist/nostr-profile.d.ts +4 -0
  62. package/dist/nostr-zap.d.ts +4 -0
  63. package/dist/src/base/base-component/nostr-base-component.d.ts +116 -0
  64. package/dist/src/base/copy-delegation.d.ts +5 -0
  65. package/dist/src/base/dialog-component/dialog-component.d.ts +67 -0
  66. package/dist/src/base/dialog-component/style.d.ts +5 -0
  67. package/dist/src/base/event-component/nostr-event-component.d.ts +53 -0
  68. package/dist/src/base/render-options.d.ts +5 -0
  69. package/dist/src/base/resolvers/event-resolver.d.ts +20 -0
  70. package/dist/src/base/resolvers/user-resolver.d.ts +19 -0
  71. package/dist/src/base/text-row/render-name.d.ts +7 -0
  72. package/dist/src/base/text-row/render-nip05.d.ts +1 -0
  73. package/dist/src/base/text-row/render-npub.d.ts +1 -0
  74. package/dist/src/base/text-row/render-text-row.d.ts +9 -0
  75. package/dist/src/base/user-component/nostr-user-component.d.ts +43 -0
  76. package/dist/src/common/base-styles.d.ts +44 -0
  77. package/dist/src/common/constants.d.ts +4 -0
  78. package/dist/src/common/date-utils.d.ts +9 -0
  79. package/dist/src/common/icons.d.ts +7 -0
  80. package/dist/src/common/nip05-utils.d.ts +13 -0
  81. package/dist/src/common/nostr-service.d.ts +40 -0
  82. package/dist/src/common/theme.d.ts +4 -0
  83. package/dist/src/common/types.d.ts +1 -0
  84. package/dist/src/common/utils.d.ts +34 -0
  85. package/dist/src/index.d.ts +10 -0
  86. package/dist/src/nostr-comment/nostr-comment.d.ts +60 -0
  87. package/dist/src/nostr-comment/render.d.ts +15 -0
  88. package/dist/src/nostr-comment/utils.d.ts +81 -0
  89. package/dist/src/nostr-dm/nostr-dm.d.ts +34 -0
  90. package/dist/src/nostr-dm/render.d.ts +15 -0
  91. package/dist/src/nostr-follow-button/nostr-follow-button.d.ts +24 -0
  92. package/dist/src/nostr-follow-button/render.d.ts +11 -0
  93. package/dist/src/nostr-follow-button/style.d.ts +1 -0
  94. package/dist/src/nostr-like/dialog-help-style.d.ts +1 -0
  95. package/dist/src/nostr-like/dialog-help.d.ts +2 -0
  96. package/dist/src/nostr-like/dialog-likers-style.d.ts +1 -0
  97. package/dist/src/nostr-like/dialog-likers.d.ts +24 -0
  98. package/dist/src/nostr-like/like-utils.d.ts +52 -0
  99. package/dist/src/nostr-like/nostr-like.d.ts +49 -0
  100. package/dist/src/nostr-like/render.d.ts +10 -0
  101. package/dist/src/nostr-like/style.d.ts +1 -0
  102. package/dist/src/nostr-live-chat/nostr-live-chat.d.ts +65 -0
  103. package/dist/src/nostr-live-chat/render.d.ts +31 -0
  104. package/dist/src/nostr-post/nostr-post.d.ts +25 -0
  105. package/dist/src/nostr-post/parse-text.d.ts +8 -0
  106. package/dist/src/nostr-post/render-content.d.ts +5 -0
  107. package/dist/src/nostr-post/render.d.ts +19 -0
  108. package/dist/src/nostr-post/style.d.ts +1 -0
  109. package/dist/src/nostr-profile/nostr-profile.d.ts +24 -0
  110. package/dist/src/nostr-profile/render-stats.d.ts +1 -0
  111. package/dist/src/nostr-profile/render.d.ts +22 -0
  112. package/dist/src/nostr-profile/style.d.ts +1 -0
  113. package/dist/src/nostr-profile-badge/nostr-profile-badge.d.ts +34 -0
  114. package/dist/src/nostr-profile-badge/render.d.ts +11 -0
  115. package/dist/src/nostr-profile-badge/style.d.ts +1 -0
  116. package/dist/src/nostr-zap/dialog-help-style.d.ts +5 -0
  117. package/dist/src/nostr-zap/dialog-help.d.ts +2 -0
  118. package/dist/src/nostr-zap/dialog-zap-style.d.ts +6 -0
  119. package/dist/src/nostr-zap/dialog-zap.d.ts +31 -0
  120. package/dist/src/nostr-zap/dialog-zappers-style.d.ts +1 -0
  121. package/dist/src/nostr-zap/dialog-zappers.d.ts +25 -0
  122. package/dist/src/nostr-zap/nostr-zap.d.ts +45 -0
  123. package/dist/src/nostr-zap/render.d.ts +9 -0
  124. package/dist/src/nostr-zap/style.d.ts +1 -0
  125. package/dist/src/nostr-zap/zap-utils.d.ts +53 -0
  126. package/dist/themes/dark.css +10 -0
  127. package/dist/themes/light.css +10 -0
  128. package/dist/themes.css +52 -0
  129. package/dist/vite.config.d.ts +2 -0
  130. package/dist/vite.config.esm.d.ts +2 -0
  131. package/dist/vite.config.umd.d.ts +2 -0
  132. package/package.json +95 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 saiy2k
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,334 @@
1
+ # Nostr Components
2
+
3
+ **Embed Nostr anywhere on the internet, a Zap Button for every webpage.**
4
+
5
+ ## 🚀 About the Project
6
+
7
+ Nostr Components makes it easy to embed Nostr profiles, posts, and follow buttons in any website. Inspired by <a href="https://unpkg.com/nostr-web-components@0.0.15/demo.html">fiatjaf's Nostr Web Components</a>, this project adds a beautiful UI, a Storybook component generator (for webmasters), and allows embedding Nostr content anywhere on the Internet.
8
+
9
+ 🔹 **[Nostr Zap](#5-nostr-zap-)** - Lightning Network zap button for Nostr
10
+ 🔹 **[Nostr Profile Badge](#1-nostr-profile-badge-)** - Compact badge-style profile display
11
+ 🔹 **[Nostr Profile](#2-nostr-profile-)** - Full Nostr profile with more details
12
+ 🔹 **[Nostr Post](#3-nostr-post-)** - Embed a specific Nostr post
13
+ 🔹 **[Nostr Follow](#4-nostr-follow-)** - Follow button for Nostr
14
+ 🔹 **[Nostr DM](#6-nostr-dm-)** - Send a direct message on Nostr
15
+ 🔹 **[Nostr Live Chat](#7-nostr-live-chat-)** - Real-time chat with message history
16
+ 🔹 **[Nostr Comment](#8-nostr-comment-)** - Decentralized comment system for any website
17
+ 🔹 **[Wordpress Integration](#9-wordpress-integration)** - Wordpress Integration
18
+
19
+ ## 🛠️ Usage
20
+
21
+ 1. **Include the Script(s):** Add the compiled component script to your HTML's `<head>`. If using the `nostr-post` component with multiple images/videos, also include Glide.js CSS for the carousel feature.
22
+
23
+ ```html
24
+ <head>
25
+ <!-- Required: Nostr Components Script (choose UMD or ES) -->
26
+ <script src="./dist/nostr-components.umd.js"></script>
27
+ <!-- Or nostr-components.es.js -->
28
+ <script>
29
+ // Initialize components (only needed for UMD build)
30
+ NostrComponents.default.init();
31
+ </script>
32
+
33
+ <!-- Optional: Glide.js CSS for Post Carousel -->
34
+ <!-- Needed only if displaying posts that might contain multiple images/videos -->
35
+ <link
36
+ rel="stylesheet"
37
+ href="https://cdn.jsdelivr.net/npm/@glidejs/glide/dist/css/glide.core.min.css"
38
+ />
39
+ <link
40
+ rel="stylesheet"
41
+ href="https://cdn.jsdelivr.net/npm/@glidejs/glide/dist/css/glide.theme.min.css"
42
+ />
43
+ </head>
44
+ ```
45
+
46
+ - **UMD (Universal Module Definition):** Use `<script src="./dist/nostr-components.umd.js"></script>` and the initialization script.
47
+ - **ES Module:** Use `<script type="module">import NostrComponents from './dist/nostr-components.es.js'; NostrComponents.init();</script>`.
48
+
49
+ _Note: Replace `./dist/nostr-components._.js`with the actual path to the file on your server or use a CDN link if available (e.g.,`https://nostr-components.web.app/dist/nostr-components.umd.js`).\*
50
+
51
+ 2. **Use the Components:** Place the component tags anywhere in your `<body>`.
52
+
53
+ ---
54
+
55
+ ## 1. Nostr Profile Badge 🔖
56
+
57
+ A small badge displaying a Nostr profile with a username and avatar.
58
+
59
+ **Usage:**
60
+
61
+ ```html
62
+ <head>
63
+ <script
64
+ type="module"
65
+ src="./dist/components/nostr-profile-badge.es.js"
66
+ ></script>
67
+ </head>
68
+ <body>
69
+ <nostr-profile-badge
70
+ pubkey="npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6"
71
+ ></nostr-profile-badge>
72
+ </body>
73
+ ```
74
+
75
+ **Preview:**
76
+
77
+ ![Preview of profile badge](images/profile-badge-preview.png)
78
+
79
+ ---
80
+
81
+ ## 2. Nostr Profile 👤
82
+
83
+ A detailed profile card showing avatar, name, bio, notes count, followers, etc,.
84
+
85
+ **Usage:**
86
+
87
+ ```html
88
+ <head>
89
+ <script type="module" src="./dist/components/nostr-profile.es.js"></script>
90
+ </head>
91
+ <body>
92
+ <nostr-profile
93
+ pubkey="npub1a2cww4kn9wqte4ry70vyfwqyqvpswksna27rtxd8vty6c74era8sdcw83a"
94
+ ></nostr-profile>
95
+ </body>
96
+ ```
97
+
98
+ **Preview:**
99
+
100
+ ![Preview of profile](images/profile-preview.png)
101
+
102
+ ---
103
+
104
+ ## 3. Nostr Post 📝
105
+
106
+ Embed any Nostr post by providing the event ID.
107
+
108
+ **Usage:**
109
+
110
+ ```html
111
+ <head>
112
+ <script type="module" src="./dist/components/nostr-post.es.js"></script>
113
+ </head>
114
+ <body>
115
+ <nostr-post
116
+ eventId="note1t2jvt5vpusrwrxkfu8x8r7q65zzvm32xuur6y7am4zn475r8ucjqmwwhd2"
117
+ ></nostr-post>
118
+ <!-- Note: The previous example incorrectly used a pubkey, use eventId for posts -->
119
+ </body>
120
+ ```
121
+
122
+ **Preview:**
123
+
124
+ ![Preview of post](images/post-preview.png)
125
+
126
+ ---
127
+
128
+ ## 4. Nostr Follow ➕
129
+
130
+ A simple button that allows users to follow a Nostr profile.
131
+
132
+ **Usage:**
133
+
134
+ ```html
135
+ <head>
136
+ <script
137
+ type="module"
138
+ src="./dist/components/nostr-follow-button.es.js"
139
+ ></script>
140
+ </head>
141
+ <body>
142
+ <nostr-follow-button
143
+ pubkey="npub1qsvv5ttv6mrlh38q8ydmw3gzwq360mdu8re2vr7rk68sqmhmsh4svhsft3"
144
+ ></nostr-follow-button>
145
+ </body>
146
+ ```
147
+
148
+ **Preview:**
149
+
150
+ ![Preview of follow button](images/follow-button-preview.png)
151
+
152
+ ## 5. Nostr Zap ⚡
153
+
154
+ A Lightning Network zap button that allows users to send sats to any Nostr user with a lightning address or LNURL.
155
+
156
+ **Usage:**
157
+
158
+ ```html
159
+ <head>
160
+ <script type="module" src="./dist/components/nostr-zap.es.js"></script>
161
+ </head>
162
+ <body>
163
+ <nostr-zap
164
+ npub="npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6"
165
+ theme="dark"
166
+ button-text="⚡ Zap Me"
167
+ button-color="#8a63d2"
168
+ amount="1000"
169
+ ></nostr-zap>
170
+ </body>
171
+ ```
172
+
173
+ **Preview:**
174
+
175
+ ![Preview of zap button](images/zap-preview.png)
176
+
177
+ ---
178
+
179
+ ## 6. Nostr DM 💬
180
+
181
+ A simple direct message composer for sending one-time messages to any Nostr user.
182
+
183
+ **Usage:**
184
+
185
+ ```html
186
+ <head>
187
+ <script type="module" src="./dist/components/nostr-dm.es.js"></script>
188
+ </head>
189
+ <body>
190
+ <!-- Basic DM, user will be prompted to enter a recipient -->
191
+ <nostr-dm theme="light"></nostr-dm>
192
+
193
+ <!-- Pre-configured recipient with npub -->
194
+ <nostr-dm
195
+ recipient-npub="npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk"
196
+ theme="light"
197
+ relays="wss://relay.damus.io,wss://relay.primal.net"
198
+ ></nostr-dm>
199
+
200
+ <!-- Using NIP-05 identifier -->
201
+ <nostr-dm
202
+ nip05="user@domain.com"
203
+ theme="dark"
204
+ ></nostr-dm>
205
+ </body>
206
+ ```
207
+
208
+
209
+ **Preview:**
210
+
211
+ ![Preview of DM component](images/dm-preview.png)
212
+
213
+ ---
214
+
215
+ ## 7. Nostr Live Chat 💬
216
+
217
+ Real-time chat component with persistent message history and live updates.
218
+
219
+ **Usage:**
220
+
221
+ ```html
222
+ <head>
223
+ <script type="module" src="./dist/components/nostr-live-chat.es.js"></script>
224
+ </head>
225
+ <body>
226
+ <!-- Basic live chat, user will be prompted to enter a recipient -->
227
+ <nostr-live-chat theme="light"></nostr-live-chat>
228
+
229
+ <!-- Pre-configured recipient with npub -->
230
+ <nostr-live-chat
231
+ recipient-npub="npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk"
232
+ theme="light"
233
+ relays="wss://relay.damus.io,wss://relay.primal.net,wss://relay.snort.social"
234
+ ></nostr-live-chat>
235
+
236
+ <!-- Using NIP-05 identifier -->
237
+ <nostr-live-chat
238
+ nip05="user@domain.com"
239
+ theme="dark"
240
+ ></nostr-live-chat>
241
+ </body>
242
+ ```
243
+
244
+
245
+
246
+ **Preview:**
247
+
248
+ ![Preview of live chat component](images/live-chat-preview.png)
249
+
250
+ ---
251
+
252
+ ## 8. Nostr Comment 💬
253
+
254
+ A complete decentralized comment system that stores comments on the Nostr network instead of a traditional database.
255
+
256
+ **Usage:**
257
+
258
+ ```html
259
+ <head>
260
+ <script type="importmap">
261
+ {
262
+ "imports": {
263
+ "lit": "https://unpkg.com/lit@3.1.0/index.js?module",
264
+ "dayjs": "https://unpkg.com/dayjs@1.11.10/dayjs.min.js?module"
265
+ }
266
+ }
267
+ </script>
268
+ <script type="module" src="./dist/components/nostr-comment.es.js"></script>
269
+ </head>
270
+ <body>
271
+ <nostr-comment
272
+ theme="light"
273
+ placeholder="Write a comment..."
274
+ relays="wss://relay.damus.io,wss://nostr.wine,wss://relay.nostr.net"
275
+ ></nostr-comment>
276
+ </body>
277
+ ```
278
+
279
+ **Preview:**
280
+
281
+ ![Preview of comment system](images/comment-preview.png)
282
+
283
+ ---
284
+
285
+ ## 9. WordPress Integration
286
+
287
+ Install the Nostr Components plugin from the WordPress plugin directory to easily embed Nostr content in your posts and pages.
288
+
289
+ ---
290
+
291
+ ## 📖 Documentation, Examples and Demo
292
+
293
+ Check out our full documentation [here](https://nostr-components.web.app).
294
+
295
+ ---
296
+
297
+ ## 🛠️ Development
298
+
299
+ ### Storybook Setup
300
+
301
+ This project uses Storybook for component development and testing. The setup includes both public showcase stories and private testing stories.
302
+
303
+ **Development Commands:**
304
+
305
+ ```bash
306
+ # Start Storybook in development mode (includes testing stories)
307
+ npm run storybook
308
+
309
+ # Build Storybook for production (excludes testing stories)
310
+ STORYBOOK_ENV=production npm run build-storybook
311
+ ```
312
+
313
+ **Story Organization:**
314
+ - **Public Stories**: Showcase stories for component demos and documentation
315
+ - **Testing Stories**: Private stories for development testing (excluded from production builds)
316
+
317
+ The production build automatically excludes all testing stories to keep the public Storybook clean and focused on showcasing components.
318
+
319
+ ---
320
+
321
+ ## 🤝 Contributing
322
+
323
+ We welcome contributions!
324
+ Feel free to submit issues, feature requests, or PRs on [GitHub](https://github.com/saiy2k/nostr-components/issues).
325
+
326
+ ---
327
+
328
+ ## 📝 License
329
+
330
+ This project is licensed under the MIT License.
331
+
332
+ ---
333
+
334
+ 💙 **Spread Nostr Everywhere!** 🚀
@@ -0,0 +1,145 @@
1
+ var w=Object.defineProperty;var k=(t,r,e)=>r in t?w(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e;var c=(t,r,e)=>k(t,typeof r!="symbol"?r+"":r,e);import{D as v,c as p,g as m,M as E,N as T}from"./nostr-service-pr_crY62.js";const I=t=>{if(typeof t!="string"||!t.startsWith("npub1"))return"";try{const r=p.decode(t);if(r&&typeof r.data=="string")return r.data}catch(r){console.error("Failed to decode npub:",r)}return""};function M(t){if(!t||!N(t))return"";try{return p.npubEncode(t.toLowerCase())}catch(r){return console.error("Failed to encode hex to npub:",r),""}}function O(t="",r=3){const e=t.length;if(!t.startsWith("npub1"))return"Invalid nPub: expected npub1...";if(!S(t))return"Invalid nPub";let s="npub1";for(let n=5;n<r+5;n++)s+=t[n];s+="...";let o="";for(let n=e-1;n>=e-r;n--)o=t[n]+o;return s+=o,s}async function z(t,r){const e=await t.fetchEvents({kinds:[m.Repost],"#e":[r||""]}),s=d=>d.tags.filter(g=>g[0]==="p").length===1,o=d=>d.tags.filter(g=>g[0]==="e").length===1,n=Array.from(e).filter(s).length,a=await t.fetchEvents({kinds:[m.Reaction],"#e":[r||""]}),i=0,u=await t.fetchEvents({kinds:[m.Text],"#e":[r||""]}),l=Array.from(u).filter(o).length;return{likes:a.size,reposts:n,zaps:i/E,replies:l}}function C(t){if(t){const r=t.split(",").map(e=>e.trim()).filter(Boolean).filter(y);return r.length?Array.from(new Set(r)):[...v]}return[...v]}function L(t){const r=t==null?void 0:t.trim().toLowerCase();return r==="light"||r==="dark"?r:"light"}function P(t){return t===null?!1:t===""||t.toLowerCase()==="true"}function B(t){const r=document.createElement("div");return r.textContent=t,r.innerHTML}function F(t){try{const r=new URL(t);return["http:","https:"].includes(r.protocol)}catch{return!1}}function y(t){try{const r=new URL(t);return r.protocol==="wss:"||r.protocol==="ws:"}catch{return!1}}function N(t){return/^[0-9a-fA-F]+$/.test(t)&&t.length===64}function S(t){try{const{type:r}=p.decode(t);return r==="npub"}catch{return!1}}function j(t){return/^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+\.[a-zA-Z]{2,}$/.test(t)}function b(t,r){try{const{type:e}=p.decode(t);return e===r}catch{return!1}}function U(t){return b(t,"note")}function D(t){return b(t,"nevent")}function H(t){return navigator.clipboard.writeText(t)}function q(t){try{const r=Date.now(),e=t*1e3,s=r-e,o=Math.floor(s/1e3);if(o<60)return"just now";if(o<3600){const a=Math.floor(o/60);return`${a} ${a===1?"min":"mins"} ago`}if(o<86400){const a=Math.floor(o/3600);return`${a} ${a===1?"hour":"hours"} ago`}if(o<2592e3){const a=Math.floor(o/86400);return`${a} ${a===1?"day":"days"} ago`}if(o<31536e3){const a=Math.floor(o/2592e3);return`${a} ${a===1?"month":"months"} ago`}const n=Math.floor(o/31536e3);return`${n} ${n===1?"year":"years"} ago`}catch(r){return console.error("Error formatting relative time:",r),"unknown"}}var f=(t=>(t[t.Idle=0]="Idle",t[t.Loading=1]="Loading",t[t.Ready=2]="Ready",t[t.Error=3]="Error",t))(f||{});const $="nc:status";class K extends HTMLElement{constructor(e=!0){super();c(this,"nostrService",T.getInstance());c(this,"theme","light");c(this,"errorMessage","");c(this,"nostrReady");c(this,"nostrReadyResolve");c(this,"nostrReadyReject");c(this,"conn",this.channel("connection"));c(this,"_statuses",new Map);c(this,"_overall",0);c(this,"connectSeq",0);e&&this.attachShadow({mode:"open"}),this.resetNostrReadyBarrier()}static get observedAttributes(){return["data-theme","relays"]}connectedCallback(){this.validateInputs()&&(this.getTheme(),this.conn.get()===0&&this.connectToNostr())}disconnectedCallback(){this.shadowRoot&&this._delegated&&this._delegated.clear()}attributeChangedCallback(e,s,o){s!==o&&(e==="data-theme"||e==="relays")&&this.validateInputs()&&(e==="relays"&&(this.resetNostrReadyBarrier(),this.connectToNostr()),e==="data-theme"&&(this.getTheme(),this.render()))}setStatusFor(e,s,o){const n=this._statuses.get(e);if(!(n!==s||s===3&&!!o))return;this._statuses.set(e,s),s===3&&o?this.errorMessage=o:n===3&&s!==3&&(this.errorMessage="");const i=`${e}-status`,u=f[s].toLowerCase();this.getAttribute(i)!==u&&this.setAttribute(i,u);const l=this.computeOverall(),d=f[l].toLowerCase();this._overall!==l?(this._overall=l,this.setAttribute("status",d),this.onStatusChange(l)):l===3&&o&&this.onStatusChange(l),this.dispatchEvent(new CustomEvent($,{detail:{key:e,status:s,all:this.snapshotStatuses(),overall:this._overall,errorMessage:this.errorMessage||void 0},bubbles:!0,composed:!0}))}getStatusFor(e){return this._statuses.get(e)??0}snapshotStatuses(){return Object.fromEntries(this._statuses.entries())}onStatusChange(e){}onNostrRelaysConnected(){}computeOverall(){const e=[...this._statuses.values()];return e.includes(3)?3:e.includes(1)?1:e.includes(2)?2:0}initChannelStatus(e,s,o={reflectOverall:!1}){if(this._statuses.set(e,s),this.setAttribute(`${e}-status`,f[s].toLowerCase()),o.reflectOverall){const n=this.computeOverall();this._overall=n,this.setAttribute("status",f[n].toLowerCase())}}channel(e){return{set:(s,o)=>this.setStatusFor(e,s,o),get:()=>this.getStatusFor(e)}}validateInputs(){const e=this.getAttribute("data-theme")||"light",s=this.getAttribute("relays"),o=this.tagName.toLowerCase();if(e==="light"||e==="dark"){if(s&&typeof s!="string")return this.conn.set(3,"Invalid relays list"),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1;if(s){const a=s.split(",").map(i=>i.trim()).filter(Boolean).filter(i=>!y(i));if(a.length>0){const i=a.join(", ");return this.conn.set(3,`Invalid relay URLs: ${i}. Relay URLs must start with 'wss://' or 'ws://'`),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1}}}else return this.conn.set(3,`Invalid theme '${e}'. Accepted values are 'light', 'dark'`),console.error(`Nostr-Components: ${o}: ${this.errorMessage}`),!1;return this.errorMessage="",!0}async connectToNostr(){var s,o;const e=++this.connectSeq;this.conn.set(1);try{if(await this.nostrService.connectToNostr(this.getRelays()),e!==this.connectSeq)return;this.conn.set(2),(s=this.nostrReadyResolve)==null||s.call(this);try{this.onNostrRelaysConnected()}catch(n){console.error("Error in onNostrRelaysConnected hook:",n)}}catch(n){if(e!==this.connectSeq)return;console.error("Failed to connect to Nostr relays:",n),this.conn.set(3,"Failed to connect to relays"),(o=this.nostrReadyReject)==null||o.call(this,n)}}ensureNostrConnected(){return this.nostrReady}getRelays(){return C(this.getAttribute("relays"))}getTheme(){this.theme=L(this.getAttribute("data-theme"))}delegateEvent(e,s,o){var i;const n=this.shadowRoot;if(!n)return;const a=`${e}:${s}`;(i=this._delegated)!=null&&i.has(a)||(this._delegated||(this._delegated=new Set),this._delegated.add(a),n.addEventListener(e,u=>{u.target.closest(s)&&o(u)}))}addDelegatedListener(e,s,o){this.delegateEvent(e,s,o)}renderError(e){return`Error: ${e}`}updateHostClasses(){const e=this.computeOverall()===1,s=this.computeOverall()===3,o=this.computeOverall()===2;this.classList.remove("is-clickable","is-disabled","is-error"),e?this.classList.add("is-disabled"):s?this.classList.add("is-error"):o&&this.classList.add("is-clickable")}render(){this.updateHostClasses(),this.renderContent()}handleNjumpClick(e,s,o){if(this.computeOverall()!==2)return;const n=new CustomEvent(e,{detail:s,bubbles:!0,composed:!0,cancelable:!0});this.dispatchEvent(n)&&window.open(`https://njump.me/${o}`,"_blank","noopener,noreferrer")}resetNostrReadyBarrier(){this.connectSeq++,this.nostrReady=new Promise((e,s)=>{this.nostrReadyResolve=e,this.nostrReadyReject=s})}}function x(){return`
2
+ :host {
3
+ /* === GENERIC DESIGN TOKENS === */
4
+ --nostrc-color-background: #ffffff;
5
+ --nostrc-color-hover-background: rgba(0, 0, 0, 0.05);
6
+ --nostrc-color-border: #e0e0e0;
7
+ --nostrc-color-error-background: #ffebee;
8
+ --nostrc-color-error-text: #d32f2f;
9
+ --nostrc-color-error-icon: #d32f2f;
10
+
11
+ /* === TYPOGRAPHY === */
12
+ --nostrc-font-family-primary: ui-sans-serif, system-ui, sans-serif;
13
+ --nostrc-font-family-mono: monospace;
14
+ --nostrc-font-size-base: 1em;
15
+ --nostrc-font-size-small: 0.8em;
16
+ --nostrc-font-size-large: 1.2em;
17
+ --nostrc-font-weight-normal: 400;
18
+ --nostrc-font-weight-medium: 500;
19
+ --nostrc-font-weight-bold: 700;
20
+
21
+ /* === SPACING === */
22
+ --nostrc-spacing-xs: 4px;
23
+ --nostrc-spacing-sm: 8px;
24
+ --nostrc-spacing-md: 12px;
25
+ --nostrc-spacing-lg: 16px;
26
+ --nostrc-spacing-xl: 20px;
27
+
28
+ /* === BORDERS === */
29
+ --nostrc-border-radius-sm: 4px;
30
+ --nostrc-border-radius-md: 8px;
31
+ --nostrc-border-radius-lg: 12px;
32
+ --nostrc-border-radius-full: 50%;
33
+ --nostrc-border-width: 1px;
34
+
35
+ /* === SKELETON === */
36
+ --nostrc-skeleton-color-min: #f0f0f0;
37
+ --nostrc-skeleton-color-max: #e0e0e0;
38
+ --nostrc-skeleton-duration: 1.5s;
39
+ --nostrc-skeleton-timing-function: linear;
40
+ --nostrc-skeleton-iteration-count: infinite;
41
+
42
+ /* === TRANSITIONS === */
43
+ --nostrc-transition-duration: 0.2s;
44
+ --nostrc-transition-timing: ease;
45
+ }
46
+
47
+ :host(.is-disabled) {
48
+ opacity: 0.7;
49
+ cursor: not-allowed;
50
+ }
51
+
52
+ /* === ESSENTIAL UTILITY STYLES === */
53
+ ${h.skeleton()}
54
+ ${h.copyButton()}
55
+ ${h.textRow()}
56
+ ${h.profileName()}
57
+ ${h.errorIcon()}
58
+ `}function V(t){return`
59
+ <style>
60
+ ${x()}
61
+ /* === COMPONENT-SPECIFIC STYLES === */
62
+ ${t}
63
+ </style>
64
+ `}const h={error:()=>`
65
+ :host(.is-error) {
66
+ color: var(--nostrc-color-error-text);
67
+ }
68
+ `,skeleton:()=>`
69
+ .skeleton {
70
+ background: linear-gradient(
71
+ 90deg,
72
+ var(--nostrc-skeleton-color-min) 0%,
73
+ var(--nostrc-skeleton-color-max) 50%,
74
+ var(--nostrc-skeleton-color-min) 100%
75
+ );
76
+ background-size: 200% 100%;
77
+ animation: skeleton-loading var(--nostrc-skeleton-duration) var(--nostrc-skeleton-timing-function) var(--nostrc-skeleton-iteration-count);
78
+ border-radius: var(--nostrc-border-radius-sm);
79
+ height: 16px;
80
+ margin-bottom: var(--nostrc-spacing-xs);
81
+ }
82
+
83
+ .skeleton:last-child {
84
+ margin-bottom: 0;
85
+ }
86
+
87
+ @keyframes skeleton-loading {
88
+ 0% { background-position: 200% 0; }
89
+ 100% { background-position: -200% 0; }
90
+ }
91
+
92
+ @media (prefers-reduced-motion: reduce) {
93
+ .skeleton { animation: none; }
94
+ }
95
+ `,copyButton:()=>`
96
+ .nc-copy-btn {
97
+ cursor: pointer;
98
+ opacity: 0.7;
99
+ transition: opacity var(--nostrc-transition-duration) var(--nostrc-transition-timing);
100
+ font-size: 1.5em;
101
+ border: none;
102
+ background: transparent;
103
+ color: var(--nostrc-color-text-muted);
104
+ }
105
+
106
+ .nc-copy-btn:hover {
107
+ opacity: 1;
108
+ }
109
+
110
+ .nc-copy-btn.copied {
111
+ color: var(--nostrc-color-accent);
112
+ }
113
+ `,profileName:()=>`
114
+ .nostr-profile-name {
115
+ color: var(--nostrc-theme-text-primary, #333333);
116
+ font-weight: var(--nostrc-font-weight-bold);
117
+ padding-bottom: var(--nostrc-spacing-xs);
118
+ }
119
+ `,textRow:()=>`
120
+ .text-row {
121
+ display: flex;
122
+ align-items: center;
123
+ gap: var(--nostrc-spacing-sm);
124
+ font-size: var(--nostrc-font-size-base);
125
+ }
126
+
127
+ .text-row.mono {
128
+ font-family: var(--nostrc-font-family-mono);
129
+ font-size: var(--nostrc-font-size-small);
130
+ }
131
+ `,errorIcon:()=>`
132
+ .error-icon {
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ width: 48px;
137
+ height: 48px;
138
+ border-radius: var(--nostrc-border-radius-full);
139
+ background-color: var(--nostrc-color-error-background);
140
+ color: var(--nostrc-color-error-icon);
141
+ font-size: 2em;
142
+ margin: auto;
143
+ }
144
+ `};export{K as N,f as a,S as b,F as c,H as d,B as e,q as f,V as g,M as h,N as i,U as j,D as k,z as l,O as m,I as n,P as p,j as v};
145
+ //# sourceMappingURL=base-styles-CBypR3FR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-styles-CBypR3FR.js","sources":["../../src/common/utils.ts","../../src/base/base-component/nostr-base-component.ts","../../src/common/base-styles.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport NDK, { NDKKind, NDKEvent } from '@nostr-dev-kit/ndk';\nimport { nip19 } from \"nostr-tools\";\n\n\nimport { Theme } from './types';\nimport { DEFAULT_RELAYS, MILLISATS_PER_SAT } from './constants';\n\nexport const decodeNpub = (npub: string): string => {\n if (typeof npub !== 'string' || !npub.startsWith('npub1')) {\n return '';\n }\n\n try {\n const decoded = nip19.decode(npub);\n if (decoded && typeof decoded.data === 'string') {\n return decoded.data;\n }\n } catch (error) {\n console.error('Failed to decode npub:', error);\n }\n \n return '';\n};\n\n/**\n * Convert hex pubkey to npub\n */\nexport function hexToNpub(hex: string): string {\n if (!hex || !isValidHex(hex)) return '';\n try {\n return nip19.npubEncode(hex.toLowerCase());\n } catch (error) {\n console.error('Failed to encode hex to npub:', error);\n return '';\n }\n}\n\n// Could be npub, note1, naddr, nsec, etc.,\nexport const decodeNip19Entity = (entity: string): any => {\n if (typeof entity !== 'string' || !/^[a-z0-9]+1[ac-hj-np-z02-9]+/.test(entity)) {\n return null;\n }\n\n try {\n const decoded = nip19.decode(entity);\n return decoded?.data ?? null;\n } catch (error) {\n console.error('Failed to decode NIP-19 entity:', error);\n return null;\n }\n};\n\nexport function maskNPub(npubString: string = '', length = 3) {\n const npubLength = npubString.length;\n\n if (!npubString.startsWith('npub1')) {\n return 'Invalid nPub: expected npub1...';\n }\n\n if (!validateNpub(npubString)) {\n return 'Invalid nPub';\n }\n\n let result = 'npub1';\n\n for (let i = 5; i < length + 5; i++) {\n result += npubString[i];\n }\n\n result += '...';\n\n let suffix = '';\n for (let i = npubLength - 1; i >= npubLength - length; i--) {\n suffix = npubString[i] + suffix;\n }\n\n result += suffix;\n\n return result;\n}\n\nexport type Stats = {\n likes: number;\n reposts: number;\n zaps: number;\n replies: number;\n};\n\nexport async function getPostStats(ndk: NDK, postId: string): Promise<Stats> {\n const reposts = await ndk.fetchEvents({\n kinds: [NDKKind.Repost],\n '#e': [postId || ''],\n });\n\n const isDirectRepost = (repost: NDKEvent): boolean => {\n const pTagCounts = repost.tags.filter(tag => tag[0] === 'p').length;\n return pTagCounts === 1;\n };\n\n const isDirectReply = (reply: NDKEvent): boolean => {\n const eTagsCount = reply.tags.filter(tag => tag[0] === 'e').length;\n return eTagsCount === 1;\n };\n\n // Only take the count of direct reposts\n const repostsCount = Array.from(reposts).filter(isDirectRepost).length;\n\n const likes = await ndk.fetchEvents({\n kinds: [NDKKind.Reaction],\n '#e': [postId || ''],\n });\n\n // TODO: Add zap receipt validation - https://github.com/nostr-protocol/nips/blob/master/57.md#appendix-f-validating-zap-receipts\n // const zaps = await ndk.fetchEvents({\n // kinds: [NDKKind.Zap],\n // '#e': [postId || '']\n // });\n\n // const zapAmount = Array.from(zaps).reduce((prev, curr) => {\n // const bolt11Tag = curr.getMatchingTags('bolt11');\n\n // if(\n // !bolt11Tag ||\n // !Array.isArray(bolt11Tag) ||\n // bolt11Tag.length === 0 ||\n // !bolt11Tag[0] ||\n // !Array.isArray(bolt11Tag[0]) ||\n // (bolt11Tag[0] as string[]).length === 0\n // ) {\n // return prev;\n // }\n\n // const bolt11 = bolt11Tag[0][1];\n\n // const decodedbol11 = decode(bolt11);\n\n // const amountSection = decodedbol11.sections.find(section => section.name === 'amount');\n\n // if(amountSection) {\n // const millisats = Number(amountSection.value);\n\n // return prev + millisats;\n // }\n\n // return prev;\n // }, 0);\n\n const zapAmount = 0;\n\n const replies = await ndk.fetchEvents({\n kinds: [NDKKind.Text],\n '#e': [postId || ''],\n });\n\n // Only take the direct replies\n // https://github.com/nostr-protocol/nips/blob/master/10.md#positional-e-tags-deprecated\n const replyCount = Array.from(replies).filter(isDirectReply).length;\n\n return {\n likes: likes.size,\n reposts: repostsCount,\n zaps: zapAmount / MILLISATS_PER_SAT,\n replies: replyCount,\n };\n}\n\nexport function parseRelays(relaysAttr: string | null): string[] {\n if (relaysAttr) {\n const list = relaysAttr\n .split(',')\n .map(r => r.trim())\n .filter(Boolean)\n .filter(isValidRelayUrl);\n // fall back to defaults if user provided no valid entries\n return list.length ? Array.from(new Set(list)) : [...DEFAULT_RELAYS];\n }\n return [...DEFAULT_RELAYS];\n}\n\nexport function parseTheme(themeAttr: string | null): Theme {\n\n const theme = themeAttr?.trim().toLowerCase();\n\n if (theme === 'light' || theme === 'dark') {\n return theme;\n }\n\n return 'light';\n}\n\nexport function parseBooleanAttribute(attr: string | null): boolean {\n // Handles: \"true\", \"\", null, \"false\"\n if (attr === null) return false;\n if (attr === '' || attr.toLowerCase() === 'true') return true;\n return false;\n}\n\nexport function escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\nexport function isValidUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return ['http:', 'https:'].includes(parsed.protocol);\n } catch {\n return false;\n }\n}\n\nexport function isValidRelayUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return u.protocol === 'wss:' || u.protocol === 'ws:';\n } catch {\n return false;\n }\n}\n\nexport function isValidHex(hex: string): boolean {\n return /^[0-9a-fA-F]+$/.test(hex) && hex.length === 64;\n}\n\nexport function validateNpub(npub: string): boolean {\n try {\n const { type } = nip19.decode(npub);\n return type === 'npub';\n } catch (e) {\n return false;\n }\n}\n\nexport function validateNip05(nip05: string): boolean {\n const nip05Regex = /^[a-zA-Z0-9_\\-\\.]+@[a-zA-Z0-9_\\-\\.]+\\.[a-zA-Z]{2,}$/;\n return nip05Regex.test(nip05);\n}\n\nfunction validateBech32OfType(input: string, expected: 'note' | 'nevent'): boolean {\n try {\n const { type } = nip19.decode(input);\n return type === expected;\n } catch {\n return false;\n }\n}\n\nexport function validateNoteId(noteId: string): boolean {\n return validateBech32OfType(noteId, 'note');\n}\n\nexport function validateEventId(eventId: string): boolean {\n return validateBech32OfType(eventId, 'nevent');\n}\n\nexport function copyToClipboard(text: string): Promise<void> {\n return navigator.clipboard.writeText(text)\n}\n\n/**\n * Format timestamp as relative time (e.g., \"2 mins ago\", \"1 month ago\")\n * @param ts Timestamp in seconds\n * @returns Formatted relative time string\n */\nexport function formatRelativeTime(ts: number): string {\n try {\n const now = Date.now();\n const messageTime = ts * 1000;\n const diffMs = now - messageTime;\n\n // Convert to seconds\n const diffSec = Math.floor(diffMs / 1000);\n\n if (diffSec < 60) {\n return 'just now';\n }\n\n // Minutes\n if (diffSec < 3600) {\n const mins = Math.floor(diffSec / 60);\n return `${mins} ${mins === 1 ? 'min' : 'mins'} ago`;\n }\n\n // Hours\n if (diffSec < 86400) {\n const hours = Math.floor(diffSec / 3600);\n return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`;\n }\n\n // Days\n if (diffSec < 2592000) { // ~30 days\n const days = Math.floor(diffSec / 86400);\n return `${days} ${days === 1 ? 'day' : 'days'} ago`;\n }\n\n // Months\n if (diffSec < 31536000) { // ~365 days\n const months = Math.floor(diffSec / 2592000);\n return `${months} ${months === 1 ? 'month' : 'months'} ago`;\n }\n\n // Years\n const years = Math.floor(diffSec / 31536000);\n return `${years} ${years === 1 ? 'year' : 'years'} ago`;\n } catch (error) {\n console.error('Error formatting relative time:', error);\n return 'unknown';\n }\n}\n","// SPDX-License-Identifier: MIT\n\nimport { NostrService } from '../../common/nostr-service';\nimport { Theme } from '../../common/types';\nimport { parseRelays, parseTheme, isValidRelayUrl } from '../../common/utils';\n\nexport enum NCStatus {\n Idle, // 0\n Loading, // 1\n Ready, // 2\n Error, // 3\n}\n\nconst EVT_STATUS = 'nc:status';\n\n/**\n * NostrBaseComponent\n * ==================\n * Foundation for all Nostr web components in this library.\n *\n * Overview\n * - Manages relay connectivity via a shared `NostrService`.\n * - Parses common attributes (`theme`, `relays`) and applies theme.\n * - Provides a generic status management logic, that is extensible by derived classes.\n * - Offers small utilities useful to subclasses (event delegation, error snippet).\n *\n * Observed attributes\n * - `data-theme` — \"light\" | \"dark\"\n * - `relays` — CSV of relay URLs\n * \n * Events\n * - `nc:status` — from base, reflects connection and user/profile loading status\n * \n * TODO: Is this class doing too much work? Time to split into smaller components?\n */\nexport abstract class NostrBaseComponent extends HTMLElement {\n\n protected nostrService: NostrService = NostrService.getInstance();\n\n protected theme: Theme = 'light';\n protected errorMessage: string = '';\n\n protected nostrReady!: Promise<void>;\n protected nostrReadyResolve?: () => void;\n protected nostrReadyReject?: (e: unknown) => void;\n\n protected conn = this.channel('connection');\n\n private _statuses = new Map<string, NCStatus>();\n private _overall: NCStatus = NCStatus.Idle;\n\n // guard to ignore stale connects\n private connectSeq = 0;\n\n constructor(shadow: boolean = true) {\n super();\n if (shadow) this.attachShadow({ mode: 'open' });\n this.resetNostrReadyBarrier();\n }\n\n /** Lifecycle methods */\n static get observedAttributes() {\n return ['data-theme', 'relays'];\n }\n\n connectedCallback() {\n if (this.validateInputs()) {\n this.getTheme();\n // Avoid duplicate connects if a subclass handles it\n if (this.conn.get() === NCStatus.Idle) {\n void this.connectToNostr();\n }\n }\n }\n\n disconnectedCallback() {\n // Clean up delegated event listeners if shadow root exists\n if (this.shadowRoot && (this as any)._delegated) {\n (this as any)._delegated.clear();\n }\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null\n ) {\n if (oldValue === newValue) return;\n\n if (name === 'data-theme' || name === 'relays') {\n if (this.validateInputs()) {\n if (name === 'relays') {\n this.resetNostrReadyBarrier();\n void this.connectToNostr();\n }\n\n if (name === 'data-theme') {\n this.getTheme();\n this.render();\n }\n }\n }\n }\n\n /** Status map API */\n\n protected setStatusFor(key: string, next: NCStatus, error?: string) {\n const prev = this._statuses.get(key);\n const changed = prev !== next || (next === NCStatus.Error && !!error);\n\n if (!changed) return;\n\n this._statuses.set(key, next);\n\n if (next === NCStatus.Error && error) {\n this.errorMessage = error;\n } else if (prev === NCStatus.Error && next !== NCStatus.Error) {\n this.errorMessage = '';\n }\n\n // Reflect per-key attribute, e.g. user-status=\"loading\"\n const perKeyAttr = `${key}-status`;\n const perKeyVal = NCStatus[next].toLowerCase();\n if (this.getAttribute(perKeyAttr) !== perKeyVal) {\n this.setAttribute(perKeyAttr, perKeyVal);\n }\n\n // Compute & reflect overall for backward-compat CSS\n const overall = this.computeOverall();\n const overallVal = NCStatus[overall].toLowerCase();\n if (this._overall !== overall) {\n this._overall = overall;\n this.setAttribute('status', overallVal);\n this.onStatusChange(overall);\n } else if (overall === NCStatus.Error && error) {\n // propagate error updates even if overall state didn't flip\n this.onStatusChange(overall);\n }\n\n // Emit a single event with structured detail\n this.dispatchEvent(new CustomEvent(EVT_STATUS, {\n detail: {\n key,\n status: next,\n all: this.snapshotStatuses(),\n overall: this._overall,\n errorMessage: this.errorMessage || undefined,\n },\n bubbles: true,\n composed: true,\n }));\n }\n\n protected getStatusFor(key: string): NCStatus {\n return this._statuses.get(key) ?? NCStatus.Idle;\n }\n\n protected snapshotStatuses(): Record<string, NCStatus> {\n return Object.fromEntries(this._statuses.entries());\n }\n\n /** Overall status change hook */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onStatusChange(_overall: NCStatus) { }\n\n /** Hook for subclasses to react when relays are connected */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected onNostrRelaysConnected() { }\n\n protected computeOverall(): NCStatus {\n const vals = [...this._statuses.values()];\n if (vals.includes(NCStatus.Error)) return NCStatus.Error;\n if (vals.includes(NCStatus.Loading)) return NCStatus.Loading;\n if (vals.includes(NCStatus.Ready)) return NCStatus.Ready;\n return NCStatus.Idle;\n }\n\n /**\n * Before first channel.set(), the map won't be set and hence get() will return `idle`.\n * In some cases (or maybe in all cases), this is not ideal.\n * In UserComponent, 'user' not set and hence returning `idle`, while\n * 'connect' switched between idle -> loading -> ready\n * and then\n * 'user' switches to loading -> ready,\n * results in loading -> ready -> loading -> ready in profile-badge onStatusChange.\n * \n * To avoid this, this function is called from UserComponent constructor,\n * to set default value as 'loading' without emitting onStatusChange event.\n * \n * Makes sense?\n * Try commenting this function call in UserComponent()\n * and add a log in ProfileBadge :: onStatusChange. You will get it.\n */\n protected initChannelStatus(key: string, status: NCStatus, opts = { reflectOverall: false }) {\n this._statuses.set(key, status);\n this.setAttribute(`${key}-status`, NCStatus[status].toLowerCase());\n if (opts.reflectOverall) {\n const overall = this.computeOverall();\n this._overall = overall;\n this.setAttribute('status', NCStatus[overall].toLowerCase());\n }\n }\n\n protected channel(key: string) {\n return {\n set: (s: NCStatus, e?: string) => this.setStatusFor(key, s, e),\n get: () => this.getStatusFor(key)\n };\n }\n\n /** Protected methods */\n protected validateInputs(): boolean {\n const theme = this.getAttribute('data-theme') || 'light';\n const relays = this.getAttribute('relays');\n const tagName = this.tagName.toLowerCase();\n\n if (!(theme === 'light' || theme === 'dark')) {\n this.conn.set(NCStatus.Error, `Invalid theme '${theme}'. Accepted values are 'light', 'dark'`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n } else if (relays && typeof relays != 'string') {\n this.conn.set(NCStatus.Error, `Invalid relays list`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n } else if (relays) {\n const relayList = relays.split(',').map(r => r.trim()).filter(Boolean);\n const invalidRelays = relayList.filter(relay => !isValidRelayUrl(relay));\n \n if (invalidRelays.length > 0) {\n const invalidRelaysList = invalidRelays.join(', ');\n this.conn.set(NCStatus.Error, `Invalid relay URLs: ${invalidRelaysList}. Relay URLs must start with 'wss://' or 'ws://'`);\n console.error(`Nostr-Components: ${tagName}: ${this.errorMessage}`);\n return false;\n }\n }\n\n this.errorMessage = \"\";\n return true;\n }\n\n protected async connectToNostr() {\n const seq = ++this.connectSeq;\n this.conn.set(NCStatus.Loading);\n try {\n await this.nostrService.connectToNostr(this.getRelays());\n if (seq !== this.connectSeq) return; // stale attempt\n this.conn.set(NCStatus.Ready);\n this.nostrReadyResolve?.();\n try {\n this.onNostrRelaysConnected();\n } catch (hookError) {\n console.error('Error in onNostrRelaysConnected hook:', hookError);\n }\n } catch (error) {\n if (seq !== this.connectSeq) return; // stale attempt\n console.error('Failed to connect to Nostr relays:', error);\n this.conn.set(NCStatus.Error, 'Failed to connect to relays');\n this.nostrReadyReject?.(error);\n }\n }\n\n protected ensureNostrConnected(): Promise<void> {\n return this.nostrReady;\n }\n\n protected getRelays() {\n return parseRelays(this.getAttribute('relays'));\n }\n\n protected getTheme() {\n this.theme = parseTheme(this.getAttribute('data-theme'));\n }\n\n /**\n * Delegate events within shadow DOM.\n * Example: this.delegateEvent('click', '#npub-copy', (e) => this.copyNpub(e));\n */\n protected delegateEvent<K extends keyof HTMLElementEventMap>(\n type: K,\n selector: string,\n handler: (event: HTMLElementEventMap[K]) => void\n ) {\n const root = this.shadowRoot;\n if (!root) return;\n\n // Attach once per (event type, selector)\n const key = `${type}:${selector}`;\n if ((this as any)._delegated?.has(key)) return;\n\n if (!(this as any)._delegated) (this as any)._delegated = new Set<string>();\n (this as any)._delegated.add(key);\n\n root.addEventListener(type, (e) => {\n const t = e.target as HTMLElement;\n if (t.closest(selector)) {\n handler(e as any);\n }\n });\n }\n\n protected addDelegatedListener<K extends keyof HTMLElementEventMap>(\n type: K,\n selector: string,\n handler: (event: HTMLElementEventMap[K]) => void\n ) {\n this.delegateEvent(type, selector, handler);\n }\n\n protected renderError(errorMessage: string): string {\n return `Error: ${errorMessage}`;\n }\n\n /**\n * Updates host element classes based on component status\n * This is a common pattern used by all components\n */\n protected updateHostClasses() {\n const isLoading = this.computeOverall() === NCStatus.Loading;\n const isError = this.computeOverall() === NCStatus.Error;\n const isReady = this.computeOverall() === NCStatus.Ready;\n \n // Remove all state classes\n this.classList.remove('is-clickable', 'is-disabled', 'is-error');\n \n // Add appropriate state class\n if (isLoading) {\n this.classList.add('is-disabled');\n } else if (isError) {\n this.classList.add('is-error');\n } else if (isReady) {\n this.classList.add('is-clickable');\n }\n }\n\n /**\n * Base render method that handles common render logic\n * Subclasses should override renderContent() instead of render()\n */\n protected render() {\n this.updateHostClasses();\n this.renderContent();\n }\n\n /**\n * Handles click events with njump.me default action\n * Creates custom event, dispatches it, and opens njump.me if not prevented\n */\n protected handleNjumpClick(\n eventType: string,\n detail: any,\n njumpPath: string\n ): void {\n if (this.computeOverall() !== NCStatus.Ready) return;\n \n const event = new CustomEvent(eventType, {\n detail,\n bubbles: true,\n composed: true,\n cancelable: true,\n });\n \n const notPrevented = this.dispatchEvent(event);\n if (notPrevented) {\n window.open(`https://njump.me/${njumpPath}`, '_blank', 'noopener,noreferrer');\n }\n }\n\n /**\n * Abstract method for component-specific rendering\n * Must be implemented by subclasses\n */\n protected abstract renderContent(): void;\n\n /** Private methods */\n private resetNostrReadyBarrier() {\n this.connectSeq++;\n this.nostrReady = new Promise<void>((resolve, reject) => {\n this.nostrReadyResolve = resolve;\n this.nostrReadyReject = reject;\n });\n }\n}\n","// SPDX-License-Identifier: MIT\n\n/**\n * Base Styles Utility for Nostr Components\n * =========================================\n * \n * This utility provides common base styles that can be shared across components.\n * It includes design tokens, common patterns, and utility functions.\n */\n\n/**\n * Generates minimal base styles for any Nostr component\n * Includes only essential design tokens and base component styles\n * Uses generic CSS variables that can be overridden by data-theme\n */\nexport function getBaseStyles(): string {\n return `\n :host {\n /* === GENERIC DESIGN TOKENS === */\n --nostrc-color-background: #ffffff;\n --nostrc-color-hover-background: rgba(0, 0, 0, 0.05);\n --nostrc-color-border: #e0e0e0;\n --nostrc-color-error-background: #ffebee;\n --nostrc-color-error-text: #d32f2f;\n --nostrc-color-error-icon: #d32f2f;\n \n /* === TYPOGRAPHY === */\n --nostrc-font-family-primary: ui-sans-serif, system-ui, sans-serif;\n --nostrc-font-family-mono: monospace;\n --nostrc-font-size-base: 1em;\n --nostrc-font-size-small: 0.8em;\n --nostrc-font-size-large: 1.2em;\n --nostrc-font-weight-normal: 400;\n --nostrc-font-weight-medium: 500;\n --nostrc-font-weight-bold: 700;\n \n /* === SPACING === */\n --nostrc-spacing-xs: 4px;\n --nostrc-spacing-sm: 8px;\n --nostrc-spacing-md: 12px;\n --nostrc-spacing-lg: 16px;\n --nostrc-spacing-xl: 20px;\n \n /* === BORDERS === */\n --nostrc-border-radius-sm: 4px;\n --nostrc-border-radius-md: 8px;\n --nostrc-border-radius-lg: 12px;\n --nostrc-border-radius-full: 50%;\n --nostrc-border-width: 1px;\n \n /* === SKELETON === */\n --nostrc-skeleton-color-min: #f0f0f0;\n --nostrc-skeleton-color-max: #e0e0e0;\n --nostrc-skeleton-duration: 1.5s;\n --nostrc-skeleton-timing-function: linear;\n --nostrc-skeleton-iteration-count: infinite;\n \n /* === TRANSITIONS === */\n --nostrc-transition-duration: 0.2s;\n --nostrc-transition-timing: ease;\n }\n \n :host(.is-disabled) {\n opacity: 0.7;\n cursor: not-allowed;\n }\n \n /* === ESSENTIAL UTILITY STYLES === */\n ${styleUtils.skeleton()}\n ${styleUtils.copyButton()}\n ${styleUtils.textRow()}\n ${styleUtils.profileName()}\n ${styleUtils.errorIcon()}\n `;\n}\n\n/**\n * Generates component-specific styles by combining base styles with custom styles\n * Uses CSS theme variables instead of theme prop\n */\nexport function getComponentStyles(customStyles: string): string {\n return `\n <style>\n ${getBaseStyles()}\n /* === COMPONENT-SPECIFIC STYLES === */\n ${customStyles}\n </style>\n `;\n}\n\nexport const styleUtils = {\n /**\n * Generates error state styles\n */\n error: () => `\n :host(.is-error) {\n color: var(--nostrc-color-error-text);\n }\n `,\n \n /**\n * Generates skeleton loading styles\n */\n skeleton: () => `\n .skeleton {\n background: linear-gradient(\n 90deg,\n var(--nostrc-skeleton-color-min) 0%,\n var(--nostrc-skeleton-color-max) 50%,\n var(--nostrc-skeleton-color-min) 100%\n );\n background-size: 200% 100%;\n animation: skeleton-loading var(--nostrc-skeleton-duration) var(--nostrc-skeleton-timing-function) var(--nostrc-skeleton-iteration-count);\n border-radius: var(--nostrc-border-radius-sm);\n height: 16px;\n margin-bottom: var(--nostrc-spacing-xs);\n }\n \n .skeleton:last-child {\n margin-bottom: 0;\n }\n \n @keyframes skeleton-loading {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .skeleton { animation: none; }\n }\n `,\n \n /**\n * Generates copy button styles\n */\n copyButton: () => `\n .nc-copy-btn {\n cursor: pointer;\n opacity: 0.7;\n transition: opacity var(--nostrc-transition-duration) var(--nostrc-transition-timing);\n font-size: 1.5em;\n border: none;\n background: transparent;\n color: var(--nostrc-color-text-muted);\n }\n \n .nc-copy-btn:hover {\n opacity: 1;\n }\n \n .nc-copy-btn.copied {\n color: var(--nostrc-color-accent);\n }\n `,\n \n /**\n * Generates profile name styles\n */\n profileName: () => `\n .nostr-profile-name {\n color: var(--nostrc-theme-text-primary, #333333);\n font-weight: var(--nostrc-font-weight-bold);\n padding-bottom: var(--nostrc-spacing-xs);\n }\n `,\n \n /**\n * Generates text row styles\n */\n textRow: () => `\n .text-row {\n display: flex;\n align-items: center;\n gap: var(--nostrc-spacing-sm);\n font-size: var(--nostrc-font-size-base);\n }\n \n .text-row.mono {\n font-family: var(--nostrc-font-family-mono);\n font-size: var(--nostrc-font-size-small);\n }\n `,\n \n /**\n * Generates error icon styles\n */\n errorIcon: () => `\n .error-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border-radius: var(--nostrc-border-radius-full);\n background-color: var(--nostrc-color-error-background);\n color: var(--nostrc-color-error-icon);\n font-size: 2em;\n margin: auto;\n }\n `,\n \n};\n"],"names":["decodeNpub","npub","decoded","nip19","error","hexToNpub","hex","isValidHex","maskNPub","npubString","length","npubLength","validateNpub","result","i","suffix","getPostStats","ndk","postId","reposts","NDKKind","isDirectRepost","repost","tag","isDirectReply","reply","repostsCount","likes","zapAmount","replies","replyCount","MILLISATS_PER_SAT","parseRelays","relaysAttr","list","r","isValidRelayUrl","DEFAULT_RELAYS","parseTheme","themeAttr","theme","parseBooleanAttribute","attr","escapeHtml","text","div","isValidUrl","url","parsed","u","type","validateNip05","nip05","validateBech32OfType","input","expected","validateNoteId","noteId","validateEventId","eventId","copyToClipboard","formatRelativeTime","ts","now","messageTime","diffMs","diffSec","mins","hours","days","months","years","NCStatus","NCStatus2","EVT_STATUS","NostrBaseComponent","shadow","__publicField","NostrService","name","oldValue","newValue","key","next","prev","perKeyAttr","perKeyVal","overall","overallVal","_overall","vals","status","opts","e","relays","tagName","invalidRelays","relay","invalidRelaysList","seq","_a","hookError","_b","selector","handler","root","errorMessage","isLoading","isError","isReady","eventType","detail","njumpPath","event","resolve","reject","getBaseStyles","styleUtils","getComponentStyles","customStyles"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import{e as r,m,d as f}from"./base-styles-CBypR3FR.js";function l(e){const{value:a,display:t=a,className:s="",monospace:o=!1,title:n=t,showCopyButton:c=!1}=e,i=s.replace(/[^\w\- ]/g,""),p=r(t),u=r(n),d="&#x2398;";if(c){const y=r(a);return`
2
+ <div class="${`text-row nc-copy ${o?"mono":""} ${i}`.trim()}" data-copy="${y}" title="${u}">
3
+ <span class="nc-copy-text">${p}</span>
4
+ <button type="button"
5
+ class="nc-copy-btn"
6
+ aria-label="Copy"
7
+ title="Copy"
8
+ >${d}</button>
9
+ </div>
10
+ `}return`
11
+ <div class="${`text-row ${o?"mono":""} ${i}`.trim()}" title="${u}">
12
+ ${p}
13
+ </div>
14
+ `}function w(e){return l({value:e,display:m(e),monospace:!0,showCopyButton:!0,title:e})}function v(e){return l({value:e,display:e,monospace:!0,showCopyButton:!0})}function N(e){const{name:a,className:t="",title:s,showCopyButton:o=!1}=e;return l({value:a,display:a,className:`nostr-profile-name ${t}`.trim(),title:s??a,showCopyButton:o})}function g(e){e.addDelegatedListener("click",".nc-copy-btn",async a=>{var n;a.stopPropagation();const t=(n=a.target)==null?void 0:n.closest(".nc-copy-btn");if(!t)return;const s=t==null?void 0:t.closest(".nc-copy"),o=(s==null?void 0:s.dataset.copy)??"";if(o)try{await f(o),t.classList.add("copied");const c=t.getAttribute("aria-label")||"Copy";t.setAttribute("aria-label","Copied!"),setTimeout(()=>{t.classList.remove("copied"),t.setAttribute("aria-label",c)},1200)}catch{}})}export{v as a,w as b,g as c,l as d,N as r};
15
+ //# sourceMappingURL=copy-delegation-C4uvRTVM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-delegation-C4uvRTVM.js","sources":["../../src/base/text-row/render-text-row.ts","../../src/base/text-row/render-npub.ts","../../src/base/text-row/render-nip05.ts","../../src/base/text-row/render-name.ts","../../src/base/copy-delegation.ts"],"sourcesContent":["// SPDX-License-Identifier: MIT\n\nimport { escapeHtml } from '../../common/utils';\n\nexport interface TextRowOptions {\n value: string; // the raw text to copy\n display?: string; // what to show (defaults to value)\n className?: string; // extra classes to add to the row\n monospace?: boolean; // show in mono font\n title?: string; // title/tooltip for the text\n showCopyButton?: boolean; // show copy button (default false)\n}\n\nexport function renderTextRow(opts: TextRowOptions): string {\n const {\n value,\n display = value,\n className = '',\n monospace = false,\n title = display,\n showCopyButton = false,\n } = opts;\n\n // Allow only class token chars to avoid breaking out of class attr\n const safeClassName = className.replace(/[^\\w\\- ]/g, '');\n const safeDisplay = escapeHtml(display);\n const safeTitle = escapeHtml(title);\n const iconHtml = '&#x2398;'; // ⎘\n\n if (showCopyButton) {\n const safeValue = escapeHtml(value);\n const rowClass = `text-row nc-copy ${monospace ? 'mono' : ''} ${safeClassName}`.trim();\n return `\n <div class=\"${rowClass}\" data-copy=\"${safeValue}\" title=\"${safeTitle}\">\n <span class=\"nc-copy-text\">${safeDisplay}</span>\n <button type=\"button\" \n class=\"nc-copy-btn\"\n aria-label=\"Copy\"\n title=\"Copy\"\n >${iconHtml}</button>\n </div>\n `;\n }\n\n const rowClass = `text-row ${monospace ? 'mono' : ''} ${safeClassName}`.trim();\n return `\n <div class=\"${rowClass}\" title=\"${safeTitle}\">\n ${safeDisplay}\n </div>\n `;\n}\n","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from \"./render-text-row\";\nimport { maskNPub } from \"../../common/utils\";\n\nexport function renderNpub(\n npub: string,\n): string {\n\n return renderTextRow({\n value: npub,\n display: maskNPub(npub),\n monospace: true,\n showCopyButton: true,\n title: npub,\n });\n}","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from \"./render-text-row\";\n\nexport function renderNip05(\n nip05: string,\n): string {\n return renderTextRow({\n value: nip05,\n display: nip05,\n monospace: true,\n showCopyButton: true,\n });\n}","// SPDX-License-Identifier: MIT\n\nimport { renderTextRow } from './render-text-row';\n\nexport interface RenderNameOptions {\n name: string;\n className?: string;\n title?: string;\n showCopyButton?: boolean;\n}\n\nexport function renderName(options: RenderNameOptions): string {\n const { name, className = '', title, showCopyButton = false } = options;\n \n return renderTextRow({\n value: name,\n display: name,\n className: `nostr-profile-name ${className}`.trim(),\n title: title ?? name,\n showCopyButton: showCopyButton,\n });\n}\n","// SPDX-License-Identifier: MIT\n\nimport { copyToClipboard } from '../common/utils';\n\ninterface DelegatedHost {\n addDelegatedListener(\n type: string,\n selector: string,\n handler: (e: Event) => void\n ): void;\n }\n\nexport function attachCopyDelegation(host: DelegatedHost) {\n host.addDelegatedListener('click', '.nc-copy-btn', async (e: Event) => {\n e.stopPropagation();\n const btn = (e.target as HTMLElement)?.closest('.nc-copy-btn') as HTMLElement;\n if (!btn) return;\n const row = btn?.closest('.nc-copy') as HTMLElement | null;\n const value = row?.dataset.copy ?? '';\n if (!value) return;\n\n try {\n await copyToClipboard(value);\n btn.classList.add('copied');\n const prev = btn.getAttribute('aria-label') || 'Copy';\n btn.setAttribute('aria-label', 'Copied!');\n setTimeout(() => {\n btn.classList.remove('copied');\n btn.setAttribute('aria-label', prev);\n }, 1200);\n } catch {\n // optional: surface error // todo\n }\n });\n}\n"],"names":["renderTextRow","opts","value","display","className","monospace","title","showCopyButton","safeClassName","safeDisplay","escapeHtml","safeTitle","iconHtml","safeValue","renderNpub","npub","maskNPub","renderNip05","nip05","renderName","options","name","attachCopyDelegation","host","e","btn","_a","row","copyToClipboard","prev"],"mappings":"uDAaO,SAASA,EAAcC,EAA8B,CACpD,KAAA,CACJ,MAAAC,EACA,QAAAC,EAAUD,EACV,UAAAE,EAAY,GACZ,UAAAC,EAAY,GACZ,MAAAC,EAAQH,EACR,eAAAI,EAAiB,EAAA,EACfN,EAGEO,EAAgBJ,EAAU,QAAQ,YAAa,EAAE,EACjDK,EAAcC,EAAWP,CAAO,EAChCQ,EAAcD,EAAWJ,CAAK,EAC9BM,EAAc,WAEpB,GAAIL,EAAgB,CACZ,MAAAM,EAAYH,EAAWR,CAAK,EAE3B,MAAA;AAAA,oBADU,oBAAoBG,EAAY,OAAS,EAAE,IAAIG,CAAa,GAAG,KAAK,CAE7D,gBAAgBK,CAAS,YAAYF,CAAS;AAAA,qCACrCF,CAAW;AAAA;AAAA;AAAA;AAAA;AAAA,iBAK/BG,CAAQ;AAAA;AAAA,KAAA,CAMhB,MAAA;AAAA,kBADU,YAAYP,EAAY,OAAS,EAAE,IAAIG,CAAa,GAAG,KAAK,CAErD,YAAYG,CAAS;AAAA,QACvCF,CAAW;AAAA;AAAA,GAGnB,CC7CO,SAASK,EACdC,EACQ,CAER,OAAOf,EAAc,CACnB,MAAOe,EACP,QAASC,EAASD,CAAI,EACtB,UAAW,GACX,eAAgB,GAChB,MAAOA,CAAA,CACR,CACH,CCZO,SAASE,EACdC,EACQ,CACR,OAAOlB,EAAc,CACnB,MAAOkB,EACP,QAASA,EACT,UAAW,GACX,eAAgB,EAAA,CACjB,CACH,CCFO,SAASC,EAAWC,EAAoC,CAC7D,KAAM,CAAE,KAAAC,EAAM,UAAAjB,EAAY,GAAI,MAAAE,EAAO,eAAAC,EAAiB,IAAUa,EAEhE,OAAOpB,EAAc,CACnB,MAAOqB,EACP,QAASA,EACT,UAAW,sBAAsBjB,CAAS,GAAG,KAAK,EAClD,MAAOE,GAASe,EAChB,eAAAd,CAAA,CACD,CACH,CCTO,SAASe,EAAqBC,EAAqB,CACxDA,EAAK,qBAAqB,QAAS,eAAgB,MAAOC,GAAa,OACrEA,EAAE,gBAAgB,EAClB,MAAMC,GAAOC,EAAAF,EAAE,SAAF,YAAAE,EAA0B,QAAQ,gBAC/C,GAAI,CAACD,EAAK,OACJ,MAAAE,EAAMF,GAAA,YAAAA,EAAK,QAAQ,YACnBvB,GAAQyB,GAAA,YAAAA,EAAK,QAAQ,OAAQ,GACnC,GAAKzB,EAED,GAAA,CACF,MAAM0B,EAAgB1B,CAAK,EACvBuB,EAAA,UAAU,IAAI,QAAQ,EAC1B,MAAMI,EAAOJ,EAAI,aAAa,YAAY,GAAK,OAC3CA,EAAA,aAAa,aAAc,SAAS,EACxC,WAAW,IAAM,CACXA,EAAA,UAAU,OAAO,QAAQ,EACzBA,EAAA,aAAa,aAAcI,CAAI,GAClC,IAAI,CAAA,MACD,CAAA,CAER,CACD,CACH"}