@happyvertical/smrt-messages 0.30.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 (153) hide show
  1. package/AGENTS.md +31 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +103 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/collections/AccountCollection.d.ts +42 -0
  8. package/dist/collections/AccountCollection.d.ts.map +1 -0
  9. package/dist/collections/AttachmentCollection.d.ts +67 -0
  10. package/dist/collections/AttachmentCollection.d.ts.map +1 -0
  11. package/dist/collections/BlacklistCollection.d.ts +14 -0
  12. package/dist/collections/BlacklistCollection.d.ts.map +1 -0
  13. package/dist/collections/EmailAccountCollection.d.ts +74 -0
  14. package/dist/collections/EmailAccountCollection.d.ts.map +1 -0
  15. package/dist/collections/EmailAttachmentCollection.d.ts +38 -0
  16. package/dist/collections/EmailAttachmentCollection.d.ts.map +1 -0
  17. package/dist/collections/EmailCollection.d.ts +81 -0
  18. package/dist/collections/EmailCollection.d.ts.map +1 -0
  19. package/dist/collections/EmailFolderCollection.d.ts +85 -0
  20. package/dist/collections/EmailFolderCollection.d.ts.map +1 -0
  21. package/dist/collections/MessageCollection.d.ts +74 -0
  22. package/dist/collections/MessageCollection.d.ts.map +1 -0
  23. package/dist/collections/WhitelistCollection.d.ts +18 -0
  24. package/dist/collections/WhitelistCollection.d.ts.map +1 -0
  25. package/dist/index.d.ts +27 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +3068 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/manifest.json +10576 -0
  30. package/dist/models/Account.d.ts +47 -0
  31. package/dist/models/Account.d.ts.map +1 -0
  32. package/dist/models/Attachment.d.ts +48 -0
  33. package/dist/models/Attachment.d.ts.map +1 -0
  34. package/dist/models/Blacklist.d.ts +21 -0
  35. package/dist/models/Blacklist.d.ts.map +1 -0
  36. package/dist/models/Email.d.ts +98 -0
  37. package/dist/models/Email.d.ts.map +1 -0
  38. package/dist/models/EmailAccount.d.ts +65 -0
  39. package/dist/models/EmailAccount.d.ts.map +1 -0
  40. package/dist/models/EmailAttachment.d.ts +19 -0
  41. package/dist/models/EmailAttachment.d.ts.map +1 -0
  42. package/dist/models/EmailFolder.d.ts +65 -0
  43. package/dist/models/EmailFolder.d.ts.map +1 -0
  44. package/dist/models/Message.d.ts +105 -0
  45. package/dist/models/Message.d.ts.map +1 -0
  46. package/dist/models/SlackAccount.d.ts +13 -0
  47. package/dist/models/SlackAccount.d.ts.map +1 -0
  48. package/dist/models/SlackMessage.d.ts +34 -0
  49. package/dist/models/SlackMessage.d.ts.map +1 -0
  50. package/dist/models/Tweet.d.ts +31 -0
  51. package/dist/models/Tweet.d.ts.map +1 -0
  52. package/dist/models/TwitterAccount.d.ts +12 -0
  53. package/dist/models/TwitterAccount.d.ts.map +1 -0
  54. package/dist/models/Whitelist.d.ts +21 -0
  55. package/dist/models/Whitelist.d.ts.map +1 -0
  56. package/dist/playground.d.ts +2 -0
  57. package/dist/playground.d.ts.map +1 -0
  58. package/dist/playground.js +176 -0
  59. package/dist/playground.js.map +1 -0
  60. package/dist/senders/EmailSender.d.ts +13 -0
  61. package/dist/senders/EmailSender.d.ts.map +1 -0
  62. package/dist/senders/SlackSender.d.ts +11 -0
  63. package/dist/senders/SlackSender.d.ts.map +1 -0
  64. package/dist/senders/TweetSender.d.ts +11 -0
  65. package/dist/senders/TweetSender.d.ts.map +1 -0
  66. package/dist/smrt-knowledge.json +4234 -0
  67. package/dist/svelte/components/AccountAvatar.svelte +107 -0
  68. package/dist/svelte/components/AccountAvatar.svelte.d.ts +12 -0
  69. package/dist/svelte/components/AccountAvatar.svelte.d.ts.map +1 -0
  70. package/dist/svelte/components/AccountCard.svelte +173 -0
  71. package/dist/svelte/components/AccountCard.svelte.d.ts +12 -0
  72. package/dist/svelte/components/AccountCard.svelte.d.ts.map +1 -0
  73. package/dist/svelte/components/AccountList.svelte +90 -0
  74. package/dist/svelte/components/AccountList.svelte.d.ts +12 -0
  75. package/dist/svelte/components/AccountList.svelte.d.ts.map +1 -0
  76. package/dist/svelte/components/AttachmentChip.svelte +99 -0
  77. package/dist/svelte/components/AttachmentChip.svelte.d.ts +12 -0
  78. package/dist/svelte/components/AttachmentChip.svelte.d.ts.map +1 -0
  79. package/dist/svelte/components/AttachmentUpload.svelte +160 -0
  80. package/dist/svelte/components/AttachmentUpload.svelte.d.ts +11 -0
  81. package/dist/svelte/components/AttachmentUpload.svelte.d.ts.map +1 -0
  82. package/dist/svelte/components/ComposeForm.svelte +387 -0
  83. package/dist/svelte/components/ComposeForm.svelte.d.ts +13 -0
  84. package/dist/svelte/components/ComposeForm.svelte.d.ts.map +1 -0
  85. package/dist/svelte/components/EmailAccountManager.svelte +690 -0
  86. package/dist/svelte/components/EmailAccountManager.svelte.d.ts +15 -0
  87. package/dist/svelte/components/EmailAccountManager.svelte.d.ts.map +1 -0
  88. package/dist/svelte/components/EmailFilterManager.svelte +687 -0
  89. package/dist/svelte/components/EmailFilterManager.svelte.d.ts +14 -0
  90. package/dist/svelte/components/EmailFilterManager.svelte.d.ts.map +1 -0
  91. package/dist/svelte/components/FolderNav.svelte +171 -0
  92. package/dist/svelte/components/FolderNav.svelte.d.ts +11 -0
  93. package/dist/svelte/components/FolderNav.svelte.d.ts.map +1 -0
  94. package/dist/svelte/components/ForwardForm.svelte +166 -0
  95. package/dist/svelte/components/ForwardForm.svelte.d.ts +10 -0
  96. package/dist/svelte/components/ForwardForm.svelte.d.ts.map +1 -0
  97. package/dist/svelte/components/MessageCard.svelte +336 -0
  98. package/dist/svelte/components/MessageCard.svelte.d.ts +20 -0
  99. package/dist/svelte/components/MessageCard.svelte.d.ts.map +1 -0
  100. package/dist/svelte/components/MessageDetail.svelte +309 -0
  101. package/dist/svelte/components/MessageDetail.svelte.d.ts +18 -0
  102. package/dist/svelte/components/MessageDetail.svelte.d.ts.map +1 -0
  103. package/dist/svelte/components/MessageFilters.svelte +228 -0
  104. package/dist/svelte/components/MessageFilters.svelte.d.ts +13 -0
  105. package/dist/svelte/components/MessageFilters.svelte.d.ts.map +1 -0
  106. package/dist/svelte/components/MessageList.svelte +101 -0
  107. package/dist/svelte/components/MessageList.svelte.d.ts +23 -0
  108. package/dist/svelte/components/MessageList.svelte.d.ts.map +1 -0
  109. package/dist/svelte/components/MessageStatusIndicator.svelte +82 -0
  110. package/dist/svelte/components/MessageStatusIndicator.svelte.d.ts +11 -0
  111. package/dist/svelte/components/MessageStatusIndicator.svelte.d.ts.map +1 -0
  112. package/dist/svelte/components/MessageToolbar.svelte +131 -0
  113. package/dist/svelte/components/MessageToolbar.svelte.d.ts +14 -0
  114. package/dist/svelte/components/MessageToolbar.svelte.d.ts.map +1 -0
  115. package/dist/svelte/components/MessageTypeBadge.svelte +59 -0
  116. package/dist/svelte/components/MessageTypeBadge.svelte.d.ts +9 -0
  117. package/dist/svelte/components/MessageTypeBadge.svelte.d.ts.map +1 -0
  118. package/dist/svelte/components/RecipientInput.svelte +150 -0
  119. package/dist/svelte/components/RecipientInput.svelte.d.ts +11 -0
  120. package/dist/svelte/components/RecipientInput.svelte.d.ts.map +1 -0
  121. package/dist/svelte/components/ReplyForm.svelte +159 -0
  122. package/dist/svelte/components/ReplyForm.svelte.d.ts +11 -0
  123. package/dist/svelte/components/ReplyForm.svelte.d.ts.map +1 -0
  124. package/dist/svelte/components/SendStatusBadge.svelte +64 -0
  125. package/dist/svelte/components/SendStatusBadge.svelte.d.ts +8 -0
  126. package/dist/svelte/components/SendStatusBadge.svelte.d.ts.map +1 -0
  127. package/dist/svelte/components/ThreadView.svelte +240 -0
  128. package/dist/svelte/components/ThreadView.svelte.d.ts +12 -0
  129. package/dist/svelte/components/ThreadView.svelte.d.ts.map +1 -0
  130. package/dist/svelte/i18n.d.ts +42 -0
  131. package/dist/svelte/i18n.d.ts.map +1 -0
  132. package/dist/svelte/i18n.js +60 -0
  133. package/dist/svelte/i18n.messages.d.ts +32 -0
  134. package/dist/svelte/i18n.messages.d.ts.map +1 -0
  135. package/dist/svelte/i18n.messages.js +46 -0
  136. package/dist/svelte/index.d.ts +54 -0
  137. package/dist/svelte/index.d.ts.map +1 -0
  138. package/dist/svelte/index.js +44 -0
  139. package/dist/svelte/playground.d.ts +341 -0
  140. package/dist/svelte/playground.d.ts.map +1 -0
  141. package/dist/svelte/playground.js +171 -0
  142. package/dist/svelte/types.d.ts +195 -0
  143. package/dist/svelte/types.d.ts.map +1 -0
  144. package/dist/svelte/types.js +6 -0
  145. package/dist/types.d.ts +316 -0
  146. package/dist/types.d.ts.map +1 -0
  147. package/dist/types.js +2 -0
  148. package/dist/types.js.map +1 -0
  149. package/dist/ui.d.ts +4 -0
  150. package/dist/ui.d.ts.map +1 -0
  151. package/dist/ui.js +103 -0
  152. package/dist/ui.js.map +1 -0
  153. package/package.json +104 -0
@@ -0,0 +1,107 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AccountAvatar - Provider icon with fallback initials
4
+ */
5
+
6
+ export interface Props {
7
+ providerType: string;
8
+ name: string;
9
+ size?: 'sm' | 'md' | 'lg';
10
+ }
11
+
12
+ const { providerType, name, size = 'md' }: Props = $props();
13
+
14
+ const _initials = $derived(
15
+ name
16
+ .split(/\s+/)
17
+ .slice(0, 2)
18
+ .map((w) => w[0]?.toUpperCase() || '')
19
+ .join(''),
20
+ );
21
+
22
+ const _icon = $derived.by(() => {
23
+ switch (providerType) {
24
+ case 'imap':
25
+ case 'smtp':
26
+ case 'pop3':
27
+ case 'gmail':
28
+ return '✉';
29
+ case 'slack':
30
+ return '#';
31
+ case 'twitter':
32
+ return '𝕏';
33
+ default:
34
+ return null;
35
+ }
36
+ });
37
+ </script>
38
+
39
+ <span
40
+ class="avatar avatar--{size}"
41
+ class:avatar--email={['imap', 'smtp', 'pop3', 'gmail'].includes(providerType)}
42
+ class:avatar--slack={providerType === 'slack'}
43
+ class:avatar--twitter={providerType === 'twitter'}
44
+ title={name}
45
+ aria-label={`${name} (${providerType})`}
46
+ >
47
+ {#if _icon}
48
+ <span class="icon">{_icon}</span>
49
+ {:else}
50
+ <span class="initials">{_initials}</span>
51
+ {/if}
52
+ </span>
53
+
54
+ <style>
55
+ .avatar {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ border-radius: var(--smrt-radius-full, 9999px);
60
+ background: var(--smrt-color-secondary-container, #d7e3f7);
61
+ color: var(--smrt-color-on-secondary-container, #101c2b);
62
+ font-weight: var(--smrt-typography-weight-medium, 500);
63
+ flex-shrink: 0;
64
+ }
65
+
66
+ .avatar--sm {
67
+ width: 1.5rem;
68
+ height: 1.5rem;
69
+ font-size: var(--smrt-typography-label-small-size, 0.625rem);
70
+ }
71
+
72
+ .avatar--md {
73
+ width: 2rem;
74
+ height: 2rem;
75
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
76
+ }
77
+
78
+ .avatar--lg {
79
+ width: 2.5rem;
80
+ height: 2.5rem;
81
+ font-size: var(--smrt-typography-label-large-size, 0.875rem);
82
+ }
83
+
84
+ .avatar--email {
85
+ background: var(--smrt-color-primary-container, #d6e2ff);
86
+ color: var(--smrt-color-on-primary-container, #001a41);
87
+ }
88
+
89
+ .avatar--slack {
90
+ background: #e8def8;
91
+ color: #4a1175;
92
+ }
93
+
94
+ .avatar--twitter {
95
+ background: #d3e8fd;
96
+ color: #0c4a6e;
97
+ }
98
+
99
+ .icon {
100
+ font-size: 1.1em;
101
+ }
102
+
103
+ .initials {
104
+ text-transform: uppercase;
105
+ letter-spacing: 0.02em;
106
+ }
107
+ </style>
@@ -0,0 +1,12 @@
1
+ /**
2
+ * AccountAvatar - Provider icon with fallback initials
3
+ */
4
+ export interface Props {
5
+ providerType: string;
6
+ name: string;
7
+ size?: 'sm' | 'md' | 'lg';
8
+ }
9
+ declare const AccountAvatar: import("svelte").Component<Props, {}, "">;
10
+ type AccountAvatar = ReturnType<typeof AccountAvatar>;
11
+ export default AccountAvatar;
12
+ //# sourceMappingURL=AccountAvatar.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountAvatar.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AccountAvatar.svelte.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,MAAM,WAAW,KAAK;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3B;AA2CD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,173 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AccountCard - Individual account card with status and actions
4
+ */
5
+ import { Card } from '@happyvertical/smrt-ui/ui';
6
+ import type { AccountData } from '../types.js';
7
+ import AccountAvatar from './AccountAvatar.svelte';
8
+
9
+ export interface Props {
10
+ account: AccountData;
11
+ onsync?: (account: AccountData) => void;
12
+ onactivate?: (account: AccountData) => void;
13
+ ondeactivate?: (account: AccountData) => void;
14
+ onremove?: (account: AccountData) => void;
15
+ }
16
+
17
+ const { account, onsync, onactivate, ondeactivate, onremove }: Props = $props();
18
+
19
+ const _lastSyncFormatted = $derived.by(() => {
20
+ if (!account.lastSyncAt) return 'Never synced';
21
+ const d =
22
+ typeof account.lastSyncAt === 'string'
23
+ ? new Date(account.lastSyncAt)
24
+ : account.lastSyncAt;
25
+ return `Last sync: ${d.toLocaleString()}`;
26
+ });
27
+ </script>
28
+
29
+ <Card>
30
+ <div class="account-card">
31
+ <div class="account-info">
32
+ <AccountAvatar providerType={account.providerType} name={account.name} size="lg" />
33
+ <div class="details">
34
+ <h3 class="name">{account.name}</h3>
35
+ {#if account.email}
36
+ <span class="email">{account.email}</span>
37
+ {/if}
38
+ <span class="provider">{account.providerType}</span>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="status-row">
43
+ <span class="status" class:status--active={account.isActive} class:status--inactive={!account.isActive}>
44
+ {account.isActive ? 'Active' : 'Inactive'}
45
+ </span>
46
+ <span class="sync-time">{_lastSyncFormatted}</span>
47
+ {#if account.unreadCount !== undefined}
48
+ <span class="unread-count">{account.unreadCount} unread</span>
49
+ {/if}
50
+ </div>
51
+
52
+ <div class="actions">
53
+ {#if onsync && account.isActive}
54
+ <button class="action-btn" type="button" onclick={() => onsync?.(account)}>
55
+ Sync
56
+ </button>
57
+ {/if}
58
+ {#if account.isActive && ondeactivate}
59
+ <button class="action-btn" type="button" onclick={() => ondeactivate?.(account)}>
60
+ Deactivate
61
+ </button>
62
+ {/if}
63
+ {#if !account.isActive && onactivate}
64
+ <button class="action-btn" type="button" onclick={() => onactivate?.(account)}>
65
+ Activate
66
+ </button>
67
+ {/if}
68
+ {#if onremove}
69
+ <button class="action-btn action-btn--danger" type="button" onclick={() => onremove?.(account)}>
70
+ Remove
71
+ </button>
72
+ {/if}
73
+ </div>
74
+ </div>
75
+ </Card>
76
+
77
+ <style>
78
+ .account-card {
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: 0.75rem;
82
+ }
83
+
84
+ .account-info {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 0.75rem;
88
+ }
89
+
90
+ .details {
91
+ display: flex;
92
+ flex-direction: column;
93
+ gap: 0.125rem;
94
+ min-width: 0;
95
+ }
96
+
97
+ .name {
98
+ margin: 0;
99
+ font: var(--smrt-typography-title-medium-font, 500 1rem / 1.25 sans-serif);
100
+ color: var(--smrt-color-on-surface, #1a1c1e);
101
+ }
102
+
103
+ .email {
104
+ font: var(--smrt-typography-body-small-font, 0.75rem / 1.33 sans-serif);
105
+ color: var(--smrt-color-on-surface-variant, #43474e);
106
+ overflow: hidden;
107
+ text-overflow: ellipsis;
108
+ white-space: nowrap;
109
+ }
110
+
111
+ .provider {
112
+ font: var(--smrt-typography-label-small-font, 500 0.6875rem / 1 sans-serif);
113
+ color: var(--smrt-color-on-surface-variant, #43474e);
114
+ text-transform: uppercase;
115
+ letter-spacing: 0.05em;
116
+ }
117
+
118
+ .status-row {
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 0.75rem;
122
+ font: var(--smrt-typography-body-small-font, 0.75rem / 1.33 sans-serif);
123
+ }
124
+
125
+ .status {
126
+ padding: 0.125rem 0.375rem;
127
+ border-radius: var(--smrt-radius-small, 0.25rem);
128
+ font-weight: var(--smrt-typography-weight-medium, 500);
129
+ }
130
+
131
+ .status--active {
132
+ background: var(--smrt-color-tertiary-container, #d7f8d7);
133
+ color: var(--smrt-color-on-tertiary-container, #0a3a0a);
134
+ }
135
+
136
+ .status--inactive {
137
+ background: var(--smrt-color-surface-variant, #e1e2ec);
138
+ color: var(--smrt-color-on-surface-variant, #43474e);
139
+ }
140
+
141
+ .sync-time {
142
+ color: var(--smrt-color-on-surface-variant, #43474e);
143
+ }
144
+
145
+ .unread-count {
146
+ font-weight: var(--smrt-typography-weight-medium, 500);
147
+ color: var(--smrt-color-primary, #005ac1);
148
+ }
149
+
150
+ .actions {
151
+ display: flex;
152
+ gap: 0.375rem;
153
+ }
154
+
155
+ .action-btn {
156
+ padding: 0.375rem 0.75rem;
157
+ border: 1px solid var(--smrt-color-outline, #72787e);
158
+ border-radius: var(--smrt-radius-small, 0.25rem);
159
+ background: var(--smrt-color-surface, #fefbff);
160
+ color: var(--smrt-color-on-surface, #1a1c1e);
161
+ font: var(--smrt-typography-label-medium-font, 500 0.75rem / 1.33 sans-serif);
162
+ cursor: pointer;
163
+ }
164
+
165
+ .action-btn:hover {
166
+ background: var(--smrt-color-surface-variant, #e1e2ec);
167
+ }
168
+
169
+ .action-btn--danger {
170
+ color: var(--smrt-color-error, #ba1a1a);
171
+ border-color: var(--smrt-color-error, #ba1a1a);
172
+ }
173
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { AccountData } from '../types.js';
2
+ export interface Props {
3
+ account: AccountData;
4
+ onsync?: (account: AccountData) => void;
5
+ onactivate?: (account: AccountData) => void;
6
+ ondeactivate?: (account: AccountData) => void;
7
+ onremove?: (account: AccountData) => void;
8
+ }
9
+ declare const AccountCard: import("svelte").Component<Props, {}, "">;
10
+ type AccountCard = ReturnType<typeof AccountCard>;
11
+ export default AccountCard;
12
+ //# sourceMappingURL=AccountCard.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountCard.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AccountCard.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC3C;AAwED,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,90 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AccountList - Account management list
4
+ */
5
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
6
+ import { Grid } from '@happyvertical/smrt-ui/layout';
7
+ import { M } from '../i18n.js';
8
+ import type { AccountData } from '../types.js';
9
+ import AccountCard from './AccountCard.svelte';
10
+
11
+ const { t } = useI18n();
12
+
13
+ export interface Props {
14
+ accounts: AccountData[];
15
+ loading?: boolean;
16
+ onaccountclick?: (account: AccountData) => void;
17
+ onsync?: (account: AccountData) => void;
18
+ onremove?: (account: AccountData) => void;
19
+ }
20
+
21
+ const {
22
+ accounts,
23
+ loading = false,
24
+ onaccountclick,
25
+ onsync,
26
+ onremove,
27
+ }: Props = $props();
28
+
29
+ function handleAccountClick(account: AccountData) {
30
+ onaccountclick?.(account);
31
+ }
32
+
33
+ function handleAccountKeydown(event: KeyboardEvent, account: AccountData) {
34
+ if (event.target !== event.currentTarget) {
35
+ return;
36
+ }
37
+
38
+ if (event.key === 'Enter' || event.key === ' ') {
39
+ event.preventDefault();
40
+ handleAccountClick(account);
41
+ }
42
+ }
43
+ </script>
44
+
45
+ <div class="account-list" role="list" aria-label={t(M['messages.account_list.accounts'])}>
46
+ {#if loading}
47
+ <div class="loading" role="status" aria-live="polite">
48
+ <p>{t(M['messages.account_list.loading'])}</p>
49
+ </div>
50
+ {:else if accounts.length === 0}
51
+ <div class="empty" role="status" aria-live="polite">
52
+ <p>{t(M['messages.account_list.empty'])}</p>
53
+ </div>
54
+ {:else}
55
+ <Grid columns="auto" role="list" aria-label={t(M['messages.account_list.accounts'])}>
56
+ {#each accounts as account (account.id)}
57
+ <div role="listitem">
58
+ {#if onaccountclick}
59
+ <div
60
+ role="button"
61
+ tabindex="0"
62
+ onclick={() => handleAccountClick(account)}
63
+ onkeydown={(event) => handleAccountKeydown(event, account)}
64
+ >
65
+ <AccountCard {account} {onsync} {onremove} />
66
+ </div>
67
+ {:else}
68
+ <div>
69
+ <AccountCard {account} {onsync} {onremove} />
70
+ </div>
71
+ {/if}
72
+ </div>
73
+ {/each}
74
+ </Grid>
75
+ {/if}
76
+ </div>
77
+
78
+ <style>
79
+ .loading,
80
+ .empty {
81
+ text-align: center;
82
+ padding: var(--smrt-spacing-3xl, 3rem);
83
+ color: var(--smrt-color-on-surface-variant, #43474e);
84
+ }
85
+
86
+ .loading p,
87
+ .empty p {
88
+ font: var(--smrt-typography-body-large-font, 1.125rem / 1.5 sans-serif);
89
+ }
90
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { AccountData } from '../types.js';
2
+ export interface Props {
3
+ accounts: AccountData[];
4
+ loading?: boolean;
5
+ onaccountclick?: (account: AccountData) => void;
6
+ onsync?: (account: AccountData) => void;
7
+ onremove?: (account: AccountData) => void;
8
+ }
9
+ declare const AccountList: import("svelte").Component<Props, {}, "">;
10
+ type AccountList = ReturnType<typeof AccountList>;
11
+ export default AccountList;
12
+ //# sourceMappingURL=AccountList.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountList.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AccountList.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAChD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC3C;AAoED,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,99 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AttachmentChip - File chip with icon, name, and size
4
+ */
5
+ import type { AttachmentData } from '../types.js';
6
+
7
+ export interface Props {
8
+ attachment: AttachmentData;
9
+ onclick?: (attachment: AttachmentData) => void;
10
+ }
11
+
12
+ const { attachment, onclick }: Props = $props();
13
+
14
+ const _icon = $derived.by(() => {
15
+ const ct = attachment.contentType;
16
+ if (ct.startsWith('image/')) return '🖼️';
17
+ if (ct === 'application/pdf') return '📄';
18
+ if (ct.startsWith('video/')) return '🎥';
19
+ if (ct.startsWith('audio/')) return '🎧';
20
+ return '📎';
21
+ });
22
+
23
+ const _formattedSize = $derived.by(() => {
24
+ const units = ['B', 'KB', 'MB', 'GB'];
25
+ let size = attachment.size;
26
+ let unitIndex = 0;
27
+ while (size >= 1024 && unitIndex < units.length - 1) {
28
+ size /= 1024;
29
+ unitIndex++;
30
+ }
31
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
32
+ });
33
+ </script>
34
+
35
+ {#if onclick}
36
+ <button
37
+ class="chip"
38
+ type="button"
39
+ onclick={() => onclick?.(attachment)}
40
+ title={`${attachment.filename} (${_formattedSize})`}
41
+ >
42
+ <span class="icon">{_icon}</span>
43
+ <span class="name">{attachment.filename}</span>
44
+ <span class="size">{_formattedSize}</span>
45
+ </button>
46
+ {:else}
47
+ <span class="chip" title={`${attachment.filename} (${_formattedSize})`}>
48
+ <span class="icon">{_icon}</span>
49
+ <span class="name">{attachment.filename}</span>
50
+ <span class="size">{_formattedSize}</span>
51
+ </span>
52
+ {/if}
53
+
54
+ <style>
55
+ .chip {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ gap: 0.375rem;
59
+ padding: 0.25rem 0.625rem;
60
+ border-radius: var(--smrt-radius-small, 0.25rem);
61
+ background: var(--smrt-color-surface-variant, #e1e2ec);
62
+ color: var(--smrt-color-on-surface-variant, #43474e);
63
+ font: var(--smrt-typography-label-medium-font, 500 0.75rem / 1.33 sans-serif);
64
+ border: none;
65
+ cursor: default;
66
+ max-width: 200px;
67
+ }
68
+
69
+ button.chip {
70
+ cursor: pointer;
71
+ transition: background var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
72
+ }
73
+
74
+ button.chip:hover {
75
+ background: color-mix(in srgb, var(--smrt-color-surface-variant, #e1e2ec) 92%, var(--smrt-color-shadow, #000));
76
+ }
77
+
78
+ button.chip:focus-visible {
79
+ outline: 2px solid var(--smrt-color-primary, #005ac1);
80
+ outline-offset: 1px;
81
+ }
82
+
83
+ .icon {
84
+ flex-shrink: 0;
85
+ font-size: var(--smrt-typography-label-large-size, 0.875rem);
86
+ }
87
+
88
+ .name {
89
+ overflow: hidden;
90
+ text-overflow: ellipsis;
91
+ white-space: nowrap;
92
+ }
93
+
94
+ .size {
95
+ flex-shrink: 0;
96
+ opacity: 0.7;
97
+ font-size: var(--smrt-typography-label-small-size, 0.6875rem);
98
+ }
99
+ </style>
@@ -0,0 +1,12 @@
1
+ /**
2
+ * AttachmentChip - File chip with icon, name, and size
3
+ */
4
+ import type { AttachmentData } from '../types.js';
5
+ export interface Props {
6
+ attachment: AttachmentData;
7
+ onclick?: (attachment: AttachmentData) => void;
8
+ }
9
+ declare const AttachmentChip: import("svelte").Component<Props, {}, "">;
10
+ type AttachmentChip = ReturnType<typeof AttachmentChip>;
11
+ export default AttachmentChip;
12
+ //# sourceMappingURL=AttachmentChip.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttachmentChip.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/AttachmentChip.svelte.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,WAAW,KAAK;IACpB,UAAU,EAAE,cAAc,CAAC;IAC3B,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,cAAc,KAAK,IAAI,CAAC;CAChD;AA+CD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,160 @@
1
+ <script lang="ts" module>
2
+ import type { AttachmentData } from '../types.js';
3
+
4
+ export interface Props {
5
+ attachments: AttachmentData[];
6
+ onattach?: (files: File[]) => void;
7
+ onremove?: (index: number) => void;
8
+ maxSize?: number;
9
+ }
10
+ </script>
11
+
12
+ <script lang="ts">
13
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
14
+ import { M } from '../i18n.js';
15
+
16
+ const { t } = useI18n();
17
+
18
+ let {
19
+ attachments = [],
20
+ onattach,
21
+ onremove,
22
+ maxSize = 25 * 1024 * 1024,
23
+ }: Props = $props();
24
+
25
+ let isDragOver = $state(false);
26
+
27
+ function handleFiles(files: FileList | null) {
28
+ if (!files) return;
29
+ const fileArray = Array.from(files).filter((f) => f.size <= maxSize);
30
+ onattach?.(fileArray);
31
+ }
32
+
33
+ function handleDrop(event: DragEvent) {
34
+ event.preventDefault();
35
+ isDragOver = false;
36
+ handleFiles(event.dataTransfer?.files ?? null);
37
+ }
38
+
39
+ function handleDragOver(event: DragEvent) {
40
+ event.preventDefault();
41
+ isDragOver = true;
42
+ }
43
+
44
+ function handleDragLeave() {
45
+ isDragOver = false;
46
+ }
47
+
48
+ function formatSize(bytes: number): string {
49
+ if (bytes < 1024) return `${bytes} B`;
50
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
51
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
52
+ }
53
+ </script>
54
+
55
+ <div class="attachment-upload">
56
+ {#if attachments.length > 0}
57
+ <div class="attachment-list">
58
+ {#each attachments as attachment, i}
59
+ <div class="attachment-item">
60
+ <span class="filename">{attachment.filename}</span>
61
+ <span class="size">{formatSize(attachment.size)}</span>
62
+ <button
63
+ class="remove"
64
+ onclick={() => onremove?.(i)}
65
+ type="button"
66
+ >×</button>
67
+ </div>
68
+ {/each}
69
+ </div>
70
+ {/if}
71
+
72
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
73
+ <div
74
+ class="drop-zone"
75
+ class:drag-over={isDragOver}
76
+ ondrop={handleDrop}
77
+ ondragover={handleDragOver}
78
+ ondragleave={handleDragLeave}
79
+ >
80
+ <label class="upload-label">
81
+ <input
82
+ type="file"
83
+ multiple
84
+ class="file-input"
85
+ onchange={(e) => handleFiles((e.target as HTMLInputElement).files)}
86
+ />
87
+ <span class="upload-text">{t(M['messages.attachment_upload.drop_files'])}</span>
88
+ </label>
89
+ </div>
90
+ </div>
91
+
92
+ <style>
93
+ .attachment-upload {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: var(--smrt-spacing-2, 8px);
97
+ }
98
+
99
+ .attachment-list {
100
+ display: flex;
101
+ flex-wrap: wrap;
102
+ gap: var(--smrt-spacing-1, 4px);
103
+ }
104
+
105
+ .attachment-item {
106
+ display: inline-flex;
107
+ align-items: center;
108
+ gap: var(--smrt-spacing-2, 8px);
109
+ padding: var(--smrt-spacing-1, 4px) var(--smrt-spacing-2, 8px);
110
+ border-radius: var(--smrt-radius-sm, 8px);
111
+ background: var(--smrt-color-surface-variant, #e7e0ec);
112
+ font-size: var(--smrt-typography-body-medium-size, 13px);
113
+ font-family: var(--smrt-font-family, system-ui);
114
+ }
115
+
116
+ .filename {
117
+ color: var(--smrt-color-on-surface-variant, #49454f);
118
+ }
119
+
120
+ .size {
121
+ color: var(--smrt-color-outline, #79747e);
122
+ font-size: var(--smrt-typography-label-small-size, 11px);
123
+ }
124
+
125
+ .remove {
126
+ background: none;
127
+ border: none;
128
+ cursor: pointer;
129
+ padding: 0 var(--smrt-spacing-1, 4px);
130
+ color: var(--smrt-color-error, #ba1a1a);
131
+ font-size: var(--smrt-typography-label-large-size, 14px);
132
+ }
133
+
134
+ .drop-zone {
135
+ border: 2px dashed var(--smrt-color-outline-variant, #cac4d0);
136
+ border-radius: var(--smrt-radius-md, 12px);
137
+ padding: var(--smrt-spacing-3, 12px);
138
+ text-align: center;
139
+ transition: border-color var(--smrt-duration-fast, 150ms) var(--smrt-easing-standard, ease);
140
+ }
141
+
142
+ .drop-zone.drag-over {
143
+ border-color: var(--smrt-color-primary, #6750a4);
144
+ background: var(--smrt-color-primary-container, #eaddff);
145
+ }
146
+
147
+ .upload-label {
148
+ cursor: pointer;
149
+ }
150
+
151
+ .file-input {
152
+ display: none;
153
+ }
154
+
155
+ .upload-text {
156
+ color: var(--smrt-color-outline, #79747e);
157
+ font-size: var(--smrt-typography-body-medium-size, 13px);
158
+ font-family: var(--smrt-font-family, system-ui);
159
+ }
160
+ </style>