@novacraft-engineering/mailbox 0.4.4 → 0.4.17

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 (41) hide show
  1. package/README.md +17 -0
  2. package/app/api/mail/accessors/reset-link/route.ts +46 -0
  3. package/app/api/mail/accessors/route.ts +16 -0
  4. package/app/api/mail/emails/[id]/route.ts +11 -3
  5. package/app/api/mail/emails/route.ts +14 -4
  6. package/app/api/mail/inbox/counts/route.ts +7 -7
  7. package/app/api/mail/inbox/route.ts +23 -4
  8. package/app/api/mail/maintenance/received/route.ts +11 -6
  9. package/app/api/mail/maintenance/rejudge/route.ts +32 -0
  10. package/app/api/mail/recovery/route.ts +95 -0
  11. package/app/api/mail/recovery/verify/route.ts +29 -0
  12. package/app/api/mail/request-reset/route.ts +36 -8
  13. package/app/api/mail/scheduled/route.ts +48 -0
  14. package/app/api/mail/scheduled/run/route.ts +19 -0
  15. package/app/api/mail/send/route.ts +39 -7
  16. package/app/api/mail/threads/route.ts +1 -1
  17. package/app/api/share/[id]/download/route.ts +71 -0
  18. package/app/api/share/[id]/route.ts +11 -6
  19. package/app/globals.css +7 -0
  20. package/app/mail/AttachmentLightbox.tsx +1 -1
  21. package/app/mail/ConfirmDialog.tsx +53 -8
  22. package/app/mail/InstallGuide.tsx +133 -0
  23. package/app/mail/page.module.css +254 -1
  24. package/app/mail/page.tsx +539 -87
  25. package/app/mail/pwa.test.ts +30 -0
  26. package/app/mail/pwa.ts +25 -1
  27. package/app/mail/search.ts +17 -0
  28. package/app/share/[id]/page.tsx +61 -11
  29. package/app/share/[id]/share.module.css +19 -0
  30. package/lib/dev-auth.test.ts +15 -0
  31. package/lib/dev-auth.ts +29 -9
  32. package/lib/mail-provider.test.ts +23 -0
  33. package/lib/mail-provider.ts +39 -3
  34. package/lib/mailbox.ts +578 -51
  35. package/lib/receive.test.ts +25 -0
  36. package/lib/receive.ts +117 -17
  37. package/lib/scheduled.ts +168 -0
  38. package/lib/ses-send.ts +14 -2
  39. package/lib/share-ticket.ts +42 -0
  40. package/package.json +1 -1
  41. package/tools/__pycache__/import-mbox.cpython-314.pyc +0 -0
@@ -915,7 +915,10 @@
915
915
  .item:hover { background: var(--nc-ink-1); }
916
916
  .itemActive {
917
917
  background: var(--nc-ink-2);
918
- border-color: var(--nc-line-1);
918
+ /* The same ring the selected folder wears, so "this is the one you are on" reads the
919
+ same in the list as it does in the sidebar. */
920
+ border-color: transparent;
921
+ box-shadow: inset 0 0 0 1.5px var(--nc-violet-500);
919
922
  }
920
923
  .avatar {
921
924
  width: var(--row-avatar);
@@ -1007,6 +1010,90 @@
1007
1010
  .chipDelivered { color: var(--nc-success); border-color: rgba(52,211,153,.3); }
1008
1011
  .chipBounced { color: var(--nc-danger); border-color: color-mix(in srgb, var(--nc-danger) 30%, transparent); }
1009
1012
  .chipDraft { color: var(--nc-violet-200); border-color: rgba(165,120,255,.3); }
1013
+ .chipAuto { color: var(--nc-fg-3); border-color: var(--nc-line-2); }
1014
+
1015
+ /* Copied-in notice. The brand accent carries it, darkened to 70%: white on pulsar's own
1016
+ accent measures 2.5:1, which is unreadable, and every brand clears AA once darkened. */
1017
+ .copiedNote,
1018
+ .copiedBanner {
1019
+ --copied-bg: color-mix(in srgb, var(--nc-violet-500, #6d28d9) 70%, black);
1020
+ display: flex;
1021
+ align-items: center;
1022
+ gap: 6px;
1023
+ background: var(--copied-bg);
1024
+ color: #fff;
1025
+ }
1026
+
1027
+ .copiedNote {
1028
+ margin-top: 6px;
1029
+ padding: 3px 8px;
1030
+ border-radius: 6px;
1031
+ font: 500 11px/1.35 var(--nc-font-sans);
1032
+ width: fit-content;
1033
+ max-width: 100%;
1034
+ }
1035
+
1036
+ .copiedBanner {
1037
+ margin: 10px 0 0;
1038
+ padding: 9px 14px;
1039
+ border-radius: 8px;
1040
+ font: 500 13px/1.45 var(--nc-font-sans);
1041
+ width: 100%;
1042
+ box-sizing: border-box;
1043
+ text-wrap: pretty;
1044
+ }
1045
+
1046
+ .copiedNoteIcon,
1047
+ .copiedBannerIcon {
1048
+ display: inline-flex;
1049
+ flex-shrink: 0;
1050
+ color: #fff;
1051
+ }
1052
+
1053
+ .copiedNoteIcon svg { width: 12px; height: 12px; }
1054
+
1055
+ /* A warning, not the brand: spam and spoofing are not house colours. Amber for a sender
1056
+ that cannot be verified, red where a scanner actually failed the message. */
1057
+ .riskNote,
1058
+ .riskBanner {
1059
+ display: flex;
1060
+ gap: 8px;
1061
+ color: #fff;
1062
+ background: #b45309;
1063
+ }
1064
+
1065
+ .riskNote {
1066
+ align-items: center;
1067
+ gap: 6px;
1068
+ margin-top: 6px;
1069
+ padding: 3px 8px;
1070
+ border-radius: 6px;
1071
+ font: 600 11px/1.35 var(--nc-font-sans);
1072
+ width: fit-content;
1073
+ max-width: 100%;
1074
+ }
1075
+
1076
+ .riskBanner {
1077
+ align-items: flex-start;
1078
+ margin: 10px 0 0;
1079
+ padding: 11px 14px;
1080
+ border-radius: 8px;
1081
+ font: 400 13px/1.5 var(--nc-font-sans);
1082
+ width: 100%;
1083
+ box-sizing: border-box;
1084
+ text-wrap: pretty;
1085
+ }
1086
+
1087
+ .riskBanner strong { display: block; font-weight: 650; }
1088
+ .riskBannerWhy { display: block; margin-top: 3px; opacity: 0.92; font-size: 12.5px; }
1089
+
1090
+ .riskNoteIcon,
1091
+ .riskBannerIcon { display: inline-flex; flex-shrink: 0; }
1092
+ .riskNoteIcon svg { width: 12px; height: 12px; }
1093
+ .riskBannerIcon svg { width: 17px; height: 17px; margin-top: 1px; }
1094
+
1095
+ .copiedBannerIcon svg { width: 16px; height: 16px; }
1096
+
1010
1097
 
1011
1098
  .emptyList {
1012
1099
  display: flex;
@@ -4055,6 +4142,59 @@
4055
4142
  font: 500 14px/1.4 var(--nc-font-sans);
4056
4143
  color: var(--nc-fg-1);
4057
4144
  }
4145
+
4146
+ .installGuide {
4147
+ display: flex;
4148
+ flex-direction: column;
4149
+ gap: 12px;
4150
+ }
4151
+ .installWarn {
4152
+ margin: 0;
4153
+ padding: 10px 12px;
4154
+ border: 1px solid var(--nc-line-2);
4155
+ border-left: 3px solid var(--nc-violet-400);
4156
+ border-radius: 10px;
4157
+ background: var(--nc-ink-2);
4158
+ font: 400 12px/1.55 var(--nc-font-sans);
4159
+ color: var(--nc-fg-2);
4160
+ }
4161
+ .installSteps {
4162
+ list-style: none;
4163
+ margin: 0;
4164
+ padding: 0;
4165
+ display: flex;
4166
+ flex-direction: column;
4167
+ gap: 10px;
4168
+ }
4169
+ .installStep {
4170
+ display: grid;
4171
+ grid-template-columns: 116px minmax(0, 1fr);
4172
+ align-items: center;
4173
+ gap: 14px;
4174
+ padding: 12px;
4175
+ border: 1px solid var(--nc-line-1);
4176
+ border-radius: 12px;
4177
+ background: var(--nc-ink-2);
4178
+ }
4179
+ .installStep strong {
4180
+ display: block;
4181
+ margin-bottom: 4px;
4182
+ font: 600 13px/1.3 var(--nc-font-sans);
4183
+ color: var(--nc-fg-1);
4184
+ }
4185
+ .installArt {
4186
+ width: 100%;
4187
+ height: auto;
4188
+ display: block;
4189
+ border-radius: 10px;
4190
+ background: var(--nc-ink-1);
4191
+ }
4192
+ @media (max-width: 560px) {
4193
+ .installStep {
4194
+ grid-template-columns: 84px minmax(0, 1fr);
4195
+ gap: 11px;
4196
+ }
4197
+ }
4058
4198
  .tickerGroup {
4059
4199
  display: flex;
4060
4200
  flex-direction: column;
@@ -5395,6 +5535,87 @@
5395
5535
  word-break: break-word;
5396
5536
  }
5397
5537
 
5538
+ .bodyFileImage {
5539
+ display: block;
5540
+ max-width: 100%;
5541
+ max-height: 72vh;
5542
+ margin: 4px 0;
5543
+ border-radius: 10px;
5544
+ border: 1px solid var(--nc-line-1);
5545
+ object-fit: contain;
5546
+ background: var(--nc-ink-1);
5547
+ }
5548
+
5549
+ .bodyFileFrame {
5550
+ display: block;
5551
+ width: 100%;
5552
+ height: min(72vh, 900px);
5553
+ margin: 4px 0;
5554
+ border: 1px solid var(--nc-line-1);
5555
+ border-radius: 10px;
5556
+ background: var(--nc-ink-1);
5557
+ }
5558
+
5559
+ .bodyFileAudio {
5560
+ display: block;
5561
+ width: 100%;
5562
+ margin: 12px 0;
5563
+ }
5564
+
5565
+ .bodyFileCard {
5566
+ display: flex;
5567
+ align-items: center;
5568
+ gap: 12px;
5569
+ margin: 4px 0;
5570
+ padding: 16px 18px;
5571
+ border: 1px solid var(--nc-line-1);
5572
+ border-radius: 12px;
5573
+ color: inherit;
5574
+ text-decoration: none;
5575
+ }
5576
+
5577
+ .bodyFileCard:hover { border-color: var(--nc-violet-400); }
5578
+ .bodyFileCard span { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
5579
+ .bodyFileCard strong { font: 600 13.5px/1.35 var(--nc-font-sans); overflow-wrap: anywhere; }
5580
+ .bodyFileCard small { font: 400 12px/1.4 var(--nc-font-sans); color: var(--nc-fg-3); }
5581
+
5582
+ .bodyFileGlyph {
5583
+ display: grid;
5584
+ place-items: center;
5585
+ width: 34px;
5586
+ height: 34px;
5587
+ flex-shrink: 0;
5588
+ border-radius: 9px;
5589
+ background: var(--nc-ink-3);
5590
+ color: var(--nc-fg-2);
5591
+ }
5592
+
5593
+ .confirmField {
5594
+ display: flex;
5595
+ flex-direction: column;
5596
+ gap: 6px;
5597
+ margin-top: 14px;
5598
+ }
5599
+
5600
+ .confirmField > span {
5601
+ font: 500 12.5px/1.4 var(--nc-font-sans);
5602
+ color: var(--nc-fg-2);
5603
+ }
5604
+
5605
+ .confirmField .pwWrap { width: 100%; }
5606
+
5607
+ .confirmInput {
5608
+ width: 100%;
5609
+ padding: 10px 12px;
5610
+ border: 1px solid var(--nc-line-1);
5611
+ border-radius: 10px;
5612
+ background: var(--nc-ink-1);
5613
+ color: var(--nc-fg-1);
5614
+ font: 400 14px/1.4 var(--nc-font-sans);
5615
+ }
5616
+
5617
+ .confirmInput:focus { border-color: var(--nc-violet-400); outline: none; }
5618
+
5398
5619
  .confirmActions {
5399
5620
  display: flex;
5400
5621
  justify-content: flex-end;
@@ -7007,3 +7228,35 @@
7007
7228
  width: auto;
7008
7229
  }
7009
7230
  }
7231
+
7232
+
7233
+ /* ── Field-level validation ─────────────────────────────────────────────── */
7234
+ /* Shake stays on transform so it composites; the ring is a box-shadow rather
7235
+ than a border so the field does not reflow by a pixel when it errors. */
7236
+ @keyframes ncFieldShake {
7237
+ 0%, 100% { transform: translate3d(0, 0, 0); }
7238
+ 15% { transform: translate3d(-5px, 0, 0); }
7239
+ 30% { transform: translate3d(4px, 0, 0); }
7240
+ 45% { transform: translate3d(-3px, 0, 0); }
7241
+ 60% { transform: translate3d(2px, 0, 0); }
7242
+ 80% { transform: translate3d(-1px, 0, 0); }
7243
+ }
7244
+
7245
+ .fieldBad,
7246
+ .fieldBad input {
7247
+ border-color: var(--nc-danger) !important;
7248
+ box-shadow: 0 0 0 1px var(--nc-danger);
7249
+ }
7250
+ .fieldBad {
7251
+ animation: ncFieldShake .4s cubic-bezier(.36, .07, .19, .97) both;
7252
+ will-change: transform;
7253
+ }
7254
+ .msgBad {
7255
+ color: var(--nc-danger);
7256
+ animation: ncFieldShake .4s cubic-bezier(.36, .07, .19, .97) both;
7257
+ }
7258
+
7259
+ @media (prefers-reduced-motion: reduce) {
7260
+ .fieldBad,
7261
+ .msgBad { animation: none; }
7262
+ }