@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,171 @@
1
+ <script lang="ts">
2
+ /**
3
+ * FolderNav - Folder/label navigation sidebar
4
+ */
5
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
6
+ import { M } from '../i18n.messages.js';
7
+ import type { FolderData } from '../types.js';
8
+
9
+ const { t } = useI18n();
10
+
11
+ export interface Props {
12
+ folders: FolderData[];
13
+ activeFolderId?: string;
14
+ showCounts?: boolean;
15
+ onfolderclick?: (folder: FolderData) => void;
16
+ }
17
+
18
+ const {
19
+ folders,
20
+ activeFolderId,
21
+ showCounts = true,
22
+ onfolderclick,
23
+ }: Props = $props();
24
+
25
+ const _systemFolders = $derived(folders.filter((f) => f.specialUse));
26
+
27
+ const _userFolders = $derived(folders.filter((f) => !f.specialUse));
28
+
29
+ function getFolderIcon(specialUse?: string): string {
30
+ switch (specialUse) {
31
+ case '\\Inbox':
32
+ return '📥';
33
+ case '\\Sent':
34
+ return '📤';
35
+ case '\\Drafts':
36
+ return '✎';
37
+ case '\\Trash':
38
+ return '🗑';
39
+ case '\\Junk':
40
+ case '\\Spam':
41
+ return '⚠';
42
+ default:
43
+ return '📁';
44
+ }
45
+ }
46
+ </script>
47
+
48
+ <nav class="folder-nav" aria-label={t(M['messages.folder_nav.folders'])}>
49
+ {#if _systemFolders.length > 0}
50
+ <ul class="folder-list" role="list">
51
+ {#each _systemFolders as folder (folder.id)}
52
+ <li>
53
+ <button
54
+ class="folder-item"
55
+ class:folder-item--active={folder.id === activeFolderId}
56
+ type="button"
57
+ onclick={() => onfolderclick?.(folder)}
58
+ aria-current={folder.id === activeFolderId ? 'true' : undefined}
59
+ >
60
+ <span class="folder-icon">{getFolderIcon(folder.specialUse)}</span>
61
+ <span class="folder-name">{folder.name}</span>
62
+ {#if showCounts && folder.unreadCount > 0}
63
+ <span class="unread-badge">{folder.unreadCount}</span>
64
+ {/if}
65
+ </button>
66
+ </li>
67
+ {/each}
68
+ </ul>
69
+ {/if}
70
+
71
+ {#if _userFolders.length > 0}
72
+ {#if _systemFolders.length > 0}
73
+ <hr class="divider" />
74
+ {/if}
75
+ <ul class="folder-list" role="list">
76
+ {#each _userFolders as folder (folder.id)}
77
+ <li>
78
+ <button
79
+ class="folder-item"
80
+ class:folder-item--active={folder.id === activeFolderId}
81
+ type="button"
82
+ onclick={() => onfolderclick?.(folder)}
83
+ aria-current={folder.id === activeFolderId ? 'true' : undefined}
84
+ >
85
+ <span class="folder-icon">📁</span>
86
+ <span class="folder-name">{folder.name}</span>
87
+ {#if showCounts && folder.unreadCount > 0}
88
+ <span class="unread-badge">{folder.unreadCount}</span>
89
+ {/if}
90
+ </button>
91
+ </li>
92
+ {/each}
93
+ </ul>
94
+ {/if}
95
+ </nav>
96
+
97
+ <style>
98
+ .folder-nav {
99
+ display: flex;
100
+ flex-direction: column;
101
+ padding: 0.5rem 0;
102
+ }
103
+
104
+ .folder-list {
105
+ list-style: none;
106
+ margin: 0;
107
+ padding: 0;
108
+ }
109
+
110
+ .folder-item {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 0.5rem;
114
+ width: 100%;
115
+ padding: 0.5rem 0.75rem;
116
+ border: none;
117
+ background: none;
118
+ cursor: pointer;
119
+ font: var(--smrt-typography-body-medium-font, 0.875rem / 1.25 sans-serif);
120
+ color: var(--smrt-color-on-surface, #1a1c1e);
121
+ border-radius: var(--smrt-radius-small, 0.25rem);
122
+ transition: background var(--smrt-duration-short2, 150ms);
123
+ text-align: left;
124
+ }
125
+
126
+ .folder-item:hover {
127
+ background: var(--smrt-color-surface-variant, #e1e2ec);
128
+ }
129
+
130
+ .folder-item--active {
131
+ background: var(--smrt-color-secondary-container, #d7e3f7);
132
+ font-weight: var(--smrt-typography-weight-medium, 500);
133
+ }
134
+
135
+ .folder-item:focus-visible {
136
+ outline: 2px solid var(--smrt-color-primary, #005ac1);
137
+ outline-offset: -2px;
138
+ }
139
+
140
+ .folder-icon {
141
+ flex-shrink: 0;
142
+ font-size: var(--smrt-typography-body-large-size, 1rem);
143
+ }
144
+
145
+ .folder-name {
146
+ flex: 1;
147
+ overflow: hidden;
148
+ text-overflow: ellipsis;
149
+ white-space: nowrap;
150
+ }
151
+
152
+ .unread-badge {
153
+ flex-shrink: 0;
154
+ min-width: 1.25rem;
155
+ height: 1.25rem;
156
+ display: inline-flex;
157
+ align-items: center;
158
+ justify-content: center;
159
+ border-radius: var(--smrt-radius-full, 9999px);
160
+ background: var(--smrt-color-primary, #005ac1);
161
+ color: var(--smrt-color-on-primary, #fff);
162
+ font: var(--smrt-typography-label-small-font, 500 0.6875rem / 1 sans-serif);
163
+ padding: 0 0.25rem;
164
+ }
165
+
166
+ .divider {
167
+ border: none;
168
+ border-top: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
169
+ margin: 0.25rem 0.75rem;
170
+ }
171
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { FolderData } from '../types.js';
2
+ export interface Props {
3
+ folders: FolderData[];
4
+ activeFolderId?: string;
5
+ showCounts?: boolean;
6
+ onfolderclick?: (folder: FolderData) => void;
7
+ }
8
+ declare const FolderNav: import("svelte").Component<Props, {}, "">;
9
+ type FolderNav = ReturnType<typeof FolderNav>;
10
+ export default FolderNav;
11
+ //# sourceMappingURL=FolderNav.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FolderNav.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/FolderNav.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CAC9C;AAiFD,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,166 @@
1
+ <script lang="ts" module>
2
+ import type { MessageData, RecipientEntry } from '../types.js';
3
+
4
+ export interface Props {
5
+ originalMessage: MessageData;
6
+ onsend?: (to: RecipientEntry[], body: string) => void;
7
+ oncancel?: () => void;
8
+ }
9
+ </script>
10
+
11
+ <script lang="ts">
12
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
13
+ import { M } from '../i18n.js';
14
+ import RecipientInput from './RecipientInput.svelte';
15
+
16
+ const { t } = useI18n();
17
+
18
+ let { originalMessage, onsend, oncancel }: Props = $props();
19
+
20
+ let to: RecipientEntry[] = $state([]);
21
+ let body = $state('');
22
+ let isSending = $state(false);
23
+
24
+ const forwardedBlock = $derived.by(() => {
25
+ const dateStr = originalMessage.date
26
+ ? new Date(originalMessage.date).toLocaleString()
27
+ : 'unknown date';
28
+ const from = originalMessage.senderName || originalMessage.senderAddress;
29
+ const toStr = originalMessage.recipientAddresses
30
+ ?.map((r) => (r.name ? `${r.name} <${r.address}>` : r.address))
31
+ .join(', ') || '';
32
+
33
+ return [
34
+ '---------- Forwarded message ----------',
35
+ `From: ${from}`,
36
+ `Date: ${dateStr}`,
37
+ `Subject: ${originalMessage.subject}`,
38
+ `To: ${toStr}`,
39
+ '',
40
+ originalMessage.body || '',
41
+ ].join('\n');
42
+ });
43
+
44
+ function handleSend() {
45
+ if (isSending || to.length === 0) return;
46
+ isSending = true;
47
+ onsend?.(to, body);
48
+ }
49
+ </script>
50
+
51
+ <div class="forward-form">
52
+ <div class="forward-header">{t(M['messages.forward_form.header'])}</div>
53
+
54
+ <RecipientInput
55
+ label="To"
56
+ recipients={to}
57
+ onchange={(r) => { to = r; }}
58
+ />
59
+
60
+ <textarea
61
+ class="forward-body"
62
+ bind:value={body}
63
+ placeholder={t(M['messages.forward_form.note_placeholder'])}
64
+ rows={3}
65
+ ></textarea>
66
+
67
+ <div class="forwarded-original">
68
+ <pre class="forwarded-text">{forwardedBlock}</pre>
69
+ </div>
70
+
71
+ <div class="actions">
72
+ <button
73
+ type="button"
74
+ class="btn-primary"
75
+ disabled={isSending || to.length === 0}
76
+ onclick={handleSend}
77
+ >
78
+ {isSending ? 'Sending...' : 'Forward'}
79
+ </button>
80
+ <button type="button" class="btn-text" onclick={() => oncancel?.()}>
81
+ Cancel
82
+ </button>
83
+ </div>
84
+ </div>
85
+
86
+ <style>
87
+ .forward-form {
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: var(--smrt-spacing-2, 8px);
91
+ padding: var(--smrt-spacing-3, 12px);
92
+ border: 1px solid var(--smrt-color-outline-variant, #cac4d0);
93
+ border-radius: var(--smrt-radius-md, 12px);
94
+ background: var(--smrt-color-surface, #fffbfe);
95
+ font-family: var(--smrt-font-family, system-ui);
96
+ }
97
+
98
+ .forward-header {
99
+ font-size: var(--smrt-typography-title-small-size, 13px);
100
+ color: var(--smrt-color-on-surface-variant, #49454f);
101
+ font-weight: var(--smrt-typography-weight-medium, 500);
102
+ }
103
+
104
+ .forward-body {
105
+ width: 100%;
106
+ border: 1px solid var(--smrt-color-outline-variant, #cac4d0);
107
+ border-radius: var(--smrt-radius-sm, 8px);
108
+ padding: var(--smrt-spacing-2, 8px);
109
+ font-family: var(--smrt-font-family, system-ui);
110
+ font-size: var(--smrt-typography-body-medium-size, 14px);
111
+ resize: vertical;
112
+ box-sizing: border-box;
113
+ }
114
+
115
+ .forward-body:focus {
116
+ outline: 2px solid var(--smrt-color-primary, #6750a4);
117
+ outline-offset: -1px;
118
+ }
119
+
120
+ .forwarded-original {
121
+ padding: var(--smrt-spacing-2, 8px);
122
+ background: var(--smrt-color-surface-variant, #e7e0ec);
123
+ border-radius: var(--smrt-radius-sm, 8px);
124
+ max-height: 300px;
125
+ overflow-y: auto;
126
+ }
127
+
128
+ .forwarded-text {
129
+ margin: 0;
130
+ font-size: var(--smrt-typography-body-small-size, 12px);
131
+ color: var(--smrt-color-on-surface-variant, #49454f);
132
+ white-space: pre-wrap;
133
+ font-family: var(--smrt-font-family, system-ui);
134
+ }
135
+
136
+ .actions {
137
+ display: flex;
138
+ gap: var(--smrt-spacing-2, 8px);
139
+ }
140
+
141
+ .btn-primary {
142
+ padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-6, 24px);
143
+ border-radius: var(--smrt-radius-full, 20px);
144
+ border: none;
145
+ background: var(--smrt-color-primary, #6750a4);
146
+ color: var(--smrt-color-on-primary, #fff);
147
+ font-family: var(--smrt-font-family, system-ui);
148
+ font-size: var(--smrt-typography-label-large-size, 14px);
149
+ cursor: pointer;
150
+ }
151
+
152
+ .btn-primary:disabled {
153
+ opacity: 0.6;
154
+ cursor: not-allowed;
155
+ }
156
+
157
+ .btn-text {
158
+ padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-4, 16px);
159
+ border: none;
160
+ background: transparent;
161
+ color: var(--smrt-color-on-surface-variant, #49454f);
162
+ font-family: var(--smrt-font-family, system-ui);
163
+ font-size: var(--smrt-typography-label-large-size, 14px);
164
+ cursor: pointer;
165
+ }
166
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { MessageData, RecipientEntry } from '../types.js';
2
+ export interface Props {
3
+ originalMessage: MessageData;
4
+ onsend?: (to: RecipientEntry[], body: string) => void;
5
+ oncancel?: () => void;
6
+ }
7
+ declare const ForwardForm: import("svelte").Component<Props, {}, "">;
8
+ type ForwardForm = ReturnType<typeof ForwardForm>;
9
+ export default ForwardForm;
10
+ //# sourceMappingURL=ForwardForm.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ForwardForm.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ForwardForm.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,KAAK;IACpB,eAAe,EAAE,WAAW,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AA0ED,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,336 @@
1
+ <script lang="ts">
2
+ /**
3
+ * MessageCard - Single message row with type-adaptive rendering
4
+ */
5
+
6
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
7
+ import type { Snippet } from 'svelte';
8
+ import { M } from '../i18n.messages.js';
9
+ import type { AccountData, MessageData } from '../types.js';
10
+ import MessageStatusIndicator from './MessageStatusIndicator.svelte';
11
+ import MessageTypeBadge from './MessageTypeBadge.svelte';
12
+
13
+ const { t } = useI18n();
14
+
15
+ export interface Props {
16
+ message: MessageData;
17
+ selected?: boolean;
18
+ compact?: boolean;
19
+ showAccount?: boolean;
20
+ showType?: boolean;
21
+ onclick?: (message: MessageData) => void;
22
+ onselect?: (message: MessageData) => void;
23
+ onflag?: (message: MessageData) => void;
24
+ account?: AccountData;
25
+ typeContent?: Snippet<[{ message: MessageData }]>;
26
+ }
27
+
28
+ const {
29
+ message,
30
+ selected = false,
31
+ compact = false,
32
+ showAccount = false,
33
+ showType = true,
34
+ onclick,
35
+ onselect,
36
+ onflag,
37
+ account,
38
+ typeContent,
39
+ }: Props = $props();
40
+
41
+ const _formattedDate = $derived.by(() => {
42
+ if (!message.date) return '';
43
+ const d =
44
+ typeof message.date === 'string' ? new Date(message.date) : message.date;
45
+ const now = new Date();
46
+ const isToday = d.toDateString() === now.toDateString();
47
+ if (isToday) {
48
+ return d.toLocaleTimeString('en-US', {
49
+ hour: 'numeric',
50
+ minute: '2-digit',
51
+ });
52
+ }
53
+ return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
54
+ });
55
+
56
+ const _preview = $derived.by(() => {
57
+ const text = message.body || '';
58
+ if (text.length <= 120) return text;
59
+ return `${text.slice(0, 120)}...`;
60
+ });
61
+
62
+ const _emailMeta = $derived.by(() => {
63
+ if (message.type !== 'email') return null;
64
+ return {
65
+ folderPath: message.folderPath,
66
+ ccCount: message.ccAddresses?.length || 0,
67
+ };
68
+ });
69
+
70
+ const _tweetMeta = $derived.by(() => {
71
+ if (message.type !== 'tweet') return null;
72
+ const meta = message.meta || {};
73
+ return {
74
+ retweetCount: meta.retweetCount || 0,
75
+ likeCount: meta.likeCount || 0,
76
+ replyCount: meta.replyCount || 0,
77
+ };
78
+ });
79
+
80
+ const _slackMeta = $derived.by(() => {
81
+ if (message.type !== 'slack') return null;
82
+ const meta = message.meta || {};
83
+ return {
84
+ channelName: meta.channelName || '',
85
+ reactions: meta.reactions || [],
86
+ };
87
+ });
88
+ </script>
89
+
90
+ <div
91
+ class="message-card"
92
+ class:message-card--selected={selected}
93
+ class:message-card--unread={!message.isRead}
94
+ class:message-card--compact={compact}
95
+ role="row"
96
+ aria-selected={selected}
97
+ >
98
+ {#if onselect}
99
+ <div class="select-col">
100
+ <input
101
+ type="checkbox"
102
+ checked={selected}
103
+ onchange={() => onselect?.(message)}
104
+ aria-label={t(M['messages.message_card.select_message'])}
105
+ />
106
+ </div>
107
+ {/if}
108
+
109
+ <button
110
+ class="message-content"
111
+ type="button"
112
+ onclick={() => onclick?.(message)}
113
+ >
114
+ <div class="header-row">
115
+ <div class="sender">
116
+ <span class="sender-name" class:sender-name--unread={!message.isRead}>
117
+ {message.senderName || message.senderAddress}
118
+ </span>
119
+ {#if showAccount && account}
120
+ <span class="account-label">{account.name}</span>
121
+ {/if}
122
+ </div>
123
+ <div class="header-right">
124
+ <MessageStatusIndicator
125
+ isRead={message.isRead}
126
+ isFlagged={message.isFlagged}
127
+ hasAttachments={message.hasAttachments}
128
+ />
129
+ <time class="date">{_formattedDate}</time>
130
+ </div>
131
+ </div>
132
+
133
+ <div class="subject-row">
134
+ {#if showType}
135
+ <MessageTypeBadge type={message.type} />
136
+ {/if}
137
+ <span class="subject" class:subject--unread={!message.isRead}>
138
+ {message.subject || '(no subject)'}
139
+ </span>
140
+ </div>
141
+
142
+ {#if !compact}
143
+ <div class="preview-row">
144
+ {#if typeContent}
145
+ {@render typeContent({ message })}
146
+ {:else if _tweetMeta}
147
+ <span class="meta-inline">
148
+ 🔄 {_tweetMeta.retweetCount}
149
+ ❤ {_tweetMeta.likeCount}
150
+ 💬 {_tweetMeta.replyCount}
151
+ </span>
152
+ {:else if _slackMeta && _slackMeta.channelName}
153
+ <span class="meta-inline">
154
+ #{_slackMeta.channelName}
155
+ </span>
156
+ {:else if _emailMeta?.folderPath}
157
+ <span class="meta-inline">{_emailMeta.folderPath}</span>
158
+ {/if}
159
+ <span class="preview">{_preview}</span>
160
+ </div>
161
+ {/if}
162
+ </button>
163
+
164
+ {#if onflag}
165
+ <button
166
+ class="flag-btn"
167
+ type="button"
168
+ onclick={() => onflag?.(message)}
169
+ title={message.isFlagged ? 'Unflag' : 'Flag'}
170
+ aria-label={message.isFlagged ? 'Unflag' : 'Flag'}
171
+ >
172
+ {message.isFlagged ? '⚑' : '⚐'}
173
+ </button>
174
+ {/if}
175
+ </div>
176
+
177
+ <style>
178
+ .message-card {
179
+ display: flex;
180
+ align-items: stretch;
181
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
182
+ transition: background var(--smrt-duration-short2, 150ms) var(--smrt-easing-standard, ease);
183
+ }
184
+
185
+ .message-card:hover {
186
+ background: var(--smrt-color-surface-variant, #e1e2ec);
187
+ }
188
+
189
+ .message-card--selected {
190
+ background: var(--smrt-color-secondary-container, #d7e3f7);
191
+ }
192
+
193
+ .message-card--unread {
194
+ background: var(--smrt-color-surface, #fefbff);
195
+ }
196
+
197
+ .select-col {
198
+ display: flex;
199
+ align-items: center;
200
+ padding: 0.75rem 0.5rem 0.75rem 0.75rem;
201
+ }
202
+
203
+ .message-content {
204
+ flex: 1;
205
+ display: flex;
206
+ flex-direction: column;
207
+ gap: 0.25rem;
208
+ padding: 0.75rem 0.75rem 0.75rem 0.5rem;
209
+ border: none;
210
+ background: none;
211
+ text-align: left;
212
+ cursor: pointer;
213
+ min-width: 0;
214
+ color: inherit;
215
+ font: inherit;
216
+ }
217
+
218
+ .message-content:focus-visible {
219
+ outline: 2px solid var(--smrt-color-primary, #005ac1);
220
+ outline-offset: -2px;
221
+ }
222
+
223
+ .header-row {
224
+ display: flex;
225
+ justify-content: space-between;
226
+ align-items: center;
227
+ gap: 0.5rem;
228
+ }
229
+
230
+ .sender {
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 0.5rem;
234
+ min-width: 0;
235
+ }
236
+
237
+ .sender-name {
238
+ font: var(--smrt-typography-body-medium-font, 0.875rem / 1.25 sans-serif);
239
+ color: var(--smrt-color-on-surface, #1a1c1e);
240
+ overflow: hidden;
241
+ text-overflow: ellipsis;
242
+ white-space: nowrap;
243
+ }
244
+
245
+ .sender-name--unread {
246
+ font-weight: var(--smrt-typography-weight-semibold, 600);
247
+ }
248
+
249
+ .account-label {
250
+ font: var(--smrt-typography-label-small-font, 500 0.6875rem / 1 sans-serif);
251
+ color: var(--smrt-color-on-surface-variant, #43474e);
252
+ background: var(--smrt-color-surface-variant, #e1e2ec);
253
+ padding: 0.125rem 0.375rem;
254
+ border-radius: var(--smrt-radius-small, 0.25rem);
255
+ white-space: nowrap;
256
+ }
257
+
258
+ .header-right {
259
+ display: flex;
260
+ align-items: center;
261
+ gap: 0.5rem;
262
+ flex-shrink: 0;
263
+ }
264
+
265
+ .date {
266
+ font: var(--smrt-typography-label-small-font, 500 0.6875rem / 1 sans-serif);
267
+ color: var(--smrt-color-on-surface-variant, #43474e);
268
+ white-space: nowrap;
269
+ }
270
+
271
+ .subject-row {
272
+ display: flex;
273
+ align-items: center;
274
+ gap: 0.375rem;
275
+ min-width: 0;
276
+ }
277
+
278
+ .subject {
279
+ font: var(--smrt-typography-body-medium-font, 0.875rem / 1.25 sans-serif);
280
+ color: var(--smrt-color-on-surface, #1a1c1e);
281
+ overflow: hidden;
282
+ text-overflow: ellipsis;
283
+ white-space: nowrap;
284
+ }
285
+
286
+ .subject--unread {
287
+ font-weight: var(--smrt-typography-weight-semibold, 600);
288
+ }
289
+
290
+ .preview-row {
291
+ display: flex;
292
+ align-items: center;
293
+ gap: 0.5rem;
294
+ min-width: 0;
295
+ }
296
+
297
+ .preview {
298
+ font: var(--smrt-typography-body-small-font, 0.75rem / 1.33 sans-serif);
299
+ color: var(--smrt-color-on-surface-variant, #43474e);
300
+ overflow: hidden;
301
+ text-overflow: ellipsis;
302
+ white-space: nowrap;
303
+ }
304
+
305
+ .meta-inline {
306
+ flex-shrink: 0;
307
+ font: var(--smrt-typography-label-small-font, 500 0.6875rem / 1 sans-serif);
308
+ color: var(--smrt-color-on-surface-variant, #43474e);
309
+ }
310
+
311
+ .flag-btn {
312
+ display: flex;
313
+ align-items: center;
314
+ justify-content: center;
315
+ width: 2rem;
316
+ border: none;
317
+ background: none;
318
+ cursor: pointer;
319
+ font-size: var(--smrt-typography-body-large-size, 1rem);
320
+ color: var(--smrt-color-on-surface-variant, #43474e);
321
+ opacity: 0.5;
322
+ transition: opacity var(--smrt-duration-short2, 150ms);
323
+ }
324
+
325
+ .flag-btn:hover {
326
+ opacity: 1;
327
+ }
328
+
329
+ .message-card--compact .message-content {
330
+ padding: 0.5rem 0.75rem 0.5rem 0.5rem;
331
+ }
332
+
333
+ .message-card--compact .preview-row {
334
+ display: none;
335
+ }
336
+ </style>