clay-server 2.30.0 → 2.31.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 (36) hide show
  1. package/lib/email-accounts.js +299 -0
  2. package/lib/email-mcp-server.js +646 -0
  3. package/lib/project-connection.js +26 -2
  4. package/lib/project-email.js +418 -0
  5. package/lib/project-sessions.js +16 -0
  6. package/lib/project-user-message.js +26 -5
  7. package/lib/project.js +72 -25
  8. package/lib/public/app.js +18 -5
  9. package/lib/public/css/filebrowser.css +80 -2
  10. package/lib/public/css/input.css +196 -0
  11. package/lib/public/css/notifications-center.css +3 -0
  12. package/lib/public/css/sidebar.css +77 -2
  13. package/lib/public/css/sticky-notes.css +0 -48
  14. package/lib/public/css/user-settings.css +85 -0
  15. package/lib/public/icons/email/gmail.svg +7 -0
  16. package/lib/public/icons/email/outlook.svg +35 -0
  17. package/lib/public/icons/email/yahoo.svg +1 -0
  18. package/lib/public/index.html +36 -3
  19. package/lib/public/modules/app-dm.js +4 -9
  20. package/lib/public/modules/app-messages.js +37 -2
  21. package/lib/public/modules/app-panels.js +2 -1
  22. package/lib/public/modules/context-sources.js +527 -1
  23. package/lib/public/modules/filebrowser.js +72 -0
  24. package/lib/public/modules/mate-sidebar.js +7 -0
  25. package/lib/public/modules/sidebar-mobile.js +1 -1
  26. package/lib/public/modules/sidebar.js +144 -2
  27. package/lib/public/modules/sticky-notes.js +1 -91
  28. package/lib/public/modules/terminal.js +0 -12
  29. package/lib/public/modules/theme.js +4 -0
  30. package/lib/public/modules/tools.js +23 -0
  31. package/lib/public/modules/user-settings.js +74 -0
  32. package/lib/sdk-bridge.js +16 -0
  33. package/lib/sdk-message-processor.js +33 -0
  34. package/lib/server-email.js +148 -0
  35. package/lib/server.js +5 -0
  36. package/package.json +3 -2
@@ -1,9 +1,16 @@
1
- // Context Sources — attach terminal output and browser tabs as context for Claude
1
+ // Context Sources — attach terminal output, browser tabs, and email accounts as context for Claude
2
2
 
3
3
  var ctx = null;
4
4
  var activeSourceIds = new Set();
5
5
  var terminalList = []; // synced from terminal module's term_list
6
6
  var browserTabList = []; // synced from Chrome extension via postMessage
7
+ var emailAccountList = []; // synced from server via email_accounts_list
8
+ var emailProviders = {}; // provider presets from server
9
+ var emailUnreadCounts = {}; // accountId -> unread count
10
+ var _emailTestPassed = false;
11
+ var _emailPendingSave = false;
12
+ var _emailDoSave = null; // set by showEmailSetupModal
13
+ var emailDefaultAccountIds = []; // project-level defaults
7
14
 
8
15
  export function initContextSources(_ctx) {
9
16
  ctx = _ctx;
@@ -110,6 +117,47 @@ export function updateBrowserTabList(tabs) {
110
117
  }
111
118
  }
112
119
 
120
+ // Called when email_accounts_list arrives from server
121
+ export function updateEmailAccountList(msg) {
122
+ emailAccountList = msg.accounts || [];
123
+ if (msg.providers) emailProviders = msg.providers;
124
+
125
+ // Remove active email sources that no longer exist
126
+ var changed = false;
127
+ for (var id of activeSourceIds) {
128
+ if (id.startsWith("email:")) {
129
+ var accId = id.split(":")[1];
130
+ var found = false;
131
+ for (var i = 0; i < emailAccountList.length; i++) {
132
+ if (emailAccountList[i].id === accId) { found = true; break; }
133
+ }
134
+ if (!found) {
135
+ activeSourceIds.delete(id);
136
+ changed = true;
137
+ }
138
+ }
139
+ }
140
+
141
+ if (changed) saveToServer();
142
+ renderChips();
143
+
144
+ var picker = document.getElementById("context-sources-picker");
145
+ if (!picker.classList.contains("hidden")) {
146
+ renderPicker();
147
+ }
148
+ }
149
+
150
+ // Called when email_unread_update arrives from server
151
+ export function updateEmailUnreadCounts(msg) {
152
+ emailUnreadCounts = msg.unread || {};
153
+ renderChips();
154
+
155
+ var picker = document.getElementById("context-sources-picker");
156
+ if (!picker.classList.contains("hidden")) {
157
+ renderPicker();
158
+ }
159
+ }
160
+
113
161
  function toggleSource(sourceId) {
114
162
  if (activeSourceIds.has(sourceId)) {
115
163
  activeSourceIds.delete(sourceId);
@@ -279,9 +327,387 @@ function renderPicker() {
279
327
  }
280
328
  }
281
329
 
330
+ // --- Email Accounts section ---
331
+ var emailSection = document.getElementById("context-picker-email");
332
+ emailSection.innerHTML = "";
333
+
334
+ var emailLabel = document.createElement("div");
335
+ emailLabel.className = "context-picker-section-label";
336
+ emailLabel.textContent = "Email Accounts";
337
+ emailSection.appendChild(emailLabel);
338
+
339
+ if (emailAccountList.length === 0) {
340
+ var emailEmpty = document.createElement("div");
341
+ emailEmpty.className = "context-picker-empty";
342
+ emailEmpty.textContent = "No email accounts connected";
343
+ emailSection.appendChild(emailEmpty);
344
+
345
+ var addAccBtn = document.createElement("div");
346
+ addAccBtn.className = "context-picker-item context-picker-add-item";
347
+ addAccBtn.style.justifyContent = "center";
348
+ addAccBtn.innerHTML = '<i data-lucide="mail-plus"></i><span>Add Account</span>';
349
+ addAccBtn.addEventListener("click", function (e) {
350
+ e.stopPropagation();
351
+ closePicker();
352
+ showEmailSetupModal();
353
+ });
354
+ emailSection.appendChild(addAccBtn);
355
+ } else {
356
+ for (var k = 0; k < emailAccountList.length; k++) {
357
+ var acc = emailAccountList[k];
358
+ var emailSourceId = "email:" + acc.id;
359
+ var emailActive = activeSourceIds.has(emailSourceId);
360
+ var unreadCount = emailUnreadCounts[acc.id] || 0;
361
+
362
+ var emailItem = document.createElement("div");
363
+ emailItem.className = "context-picker-item" + (emailActive ? " active" : "");
364
+ emailItem.setAttribute("data-source-id", emailSourceId);
365
+
366
+ var unreadBadge = unreadCount > 0
367
+ ? '<span class="context-picker-unread-badge">' + unreadCount + '</span>'
368
+ : '';
369
+
370
+ emailItem.innerHTML =
371
+ '<i data-lucide="mail"></i>' +
372
+ '<span>' + escapeHtml(acc.email) + '</span>' +
373
+ unreadBadge +
374
+ '<i data-lucide="check" class="context-picker-check"></i>';
375
+
376
+ emailItem.addEventListener("click", function() {
377
+ toggleSource(this.getAttribute("data-source-id"));
378
+ if (typeof lucide !== "undefined") lucide.createIcons();
379
+ });
380
+
381
+ emailSection.appendChild(emailItem);
382
+ }
383
+
384
+ // "Add Account" link at the bottom
385
+ var addMoreBtn = document.createElement("div");
386
+ addMoreBtn.className = "context-picker-item context-picker-add-item";
387
+ addMoreBtn.innerHTML = '<i data-lucide="plus"></i><span>Add Email Account</span>';
388
+ addMoreBtn.addEventListener("click", function () {
389
+ closePicker();
390
+ showEmailSetupModal();
391
+ });
392
+ emailSection.appendChild(addMoreBtn);
393
+ }
394
+
282
395
  if (typeof lucide !== "undefined") lucide.createIcons();
283
396
  }
284
397
 
398
+ // --- Email Setup Modal ---
399
+
400
+ export function getEmailAccountListCache() {
401
+ return emailAccountList;
402
+ }
403
+
404
+ export function showEmailSetupModal() {
405
+ // Remove any existing modal
406
+ var existing = document.getElementById("email-setup-modal");
407
+ if (existing) existing.remove();
408
+
409
+ var overlay = document.createElement("div");
410
+ overlay.id = "email-setup-modal";
411
+ overlay.className = "email-setup-overlay";
412
+
413
+ var providerOptions = '<option value="gmail">Gmail</option><option value="outlook">Outlook</option><option value="yahoo">Yahoo</option><option value="custom">Custom</option>';
414
+
415
+ var modal = document.createElement("div");
416
+ modal.className = "email-setup-dialog";
417
+ modal.innerHTML =
418
+ '<h3 class="email-setup-title">Add Email Account</h3>' +
419
+ '<div class="email-setup-field">' +
420
+ '<label class="email-setup-label">Provider</label>' +
421
+ '<select id="email-setup-provider" class="email-setup-input">' + providerOptions + '</select>' +
422
+ '</div>' +
423
+ '<div class="email-setup-field">' +
424
+ '<label class="email-setup-label">Email Address</label>' +
425
+ '<div class="email-setup-email-wrap">' +
426
+ '<input id="email-setup-address" type="text" placeholder="you" class="email-setup-input email-setup-email-input" />' +
427
+ '<span id="email-setup-domain" class="email-setup-domain">@gmail.com</span>' +
428
+ '</div>' +
429
+ '</div>' +
430
+ '<div class="email-setup-field">' +
431
+ '<label class="email-setup-label">App Password</label>' +
432
+ '<div class="email-setup-password-wrap">' +
433
+ '<input id="email-setup-password" type="password" placeholder="xxxx-xxxx-xxxx-xxxx" class="email-setup-input" />' +
434
+ '<button id="email-setup-password-toggle" type="button" class="email-setup-password-eye" title="Show password"><i data-lucide="eye"></i></button>' +
435
+ '</div>' +
436
+ '<a id="email-setup-help" href="https://support.google.com/accounts/answer/185833" target="_blank" rel="noopener" class="email-setup-help">How to create an App Password</a>' +
437
+ '</div>' +
438
+ '<div id="email-setup-custom-fields" style="display:none;">' +
439
+ '<div class="email-setup-field">' +
440
+ '<label class="email-setup-label">IMAP Host</label>' +
441
+ '<div class="email-setup-row">' +
442
+ '<input id="email-setup-imap-host" type="text" placeholder="imap.example.com" class="email-setup-input" style="flex:1;" />' +
443
+ '<input id="email-setup-imap-port" type="number" value="993" class="email-setup-input email-setup-port" />' +
444
+ '</div>' +
445
+ '</div>' +
446
+ '<div class="email-setup-field">' +
447
+ '<label class="email-setup-label">SMTP Host</label>' +
448
+ '<div class="email-setup-row">' +
449
+ '<input id="email-setup-smtp-host" type="text" placeholder="smtp.example.com" class="email-setup-input" style="flex:1;" />' +
450
+ '<input id="email-setup-smtp-port" type="number" value="587" class="email-setup-input email-setup-port" />' +
451
+ '</div>' +
452
+ '</div>' +
453
+ '</div>' +
454
+ '<div id="email-setup-status" class="email-setup-status"></div>' +
455
+ '<div class="email-setup-actions">' +
456
+ '<button id="email-setup-test" type="button" class="email-setup-btn email-setup-btn-secondary">Test Connection</button>' +
457
+ '<button id="email-setup-cancel" type="button" class="email-setup-btn email-setup-btn-ghost">Cancel</button>' +
458
+ '<button id="email-setup-save" type="button" class="email-setup-btn email-setup-btn-primary">Add Account</button>' +
459
+ '</div>';
460
+
461
+ overlay.appendChild(modal);
462
+ document.body.appendChild(overlay);
463
+
464
+ // Event: close on overlay click
465
+ overlay.addEventListener("click", function (e) {
466
+ if (e.target === overlay) overlay.remove();
467
+ });
468
+
469
+ // Event: cancel button
470
+ document.getElementById("email-setup-cancel").addEventListener("click", function () {
471
+ overlay.remove();
472
+ });
473
+
474
+ // Event: password visibility toggle
475
+ var pwInput = document.getElementById("email-setup-password");
476
+ var pwToggle = document.getElementById("email-setup-password-toggle");
477
+ pwToggle.addEventListener("click", function () {
478
+ var isHidden = pwInput.type === "password";
479
+ pwInput.type = isHidden ? "text" : "password";
480
+ pwToggle.innerHTML = isHidden ? '<i data-lucide="eye-off"></i>' : '<i data-lucide="eye"></i>';
481
+ pwToggle.title = isHidden ? "Hide password" : "Show password";
482
+ if (typeof lucide !== "undefined") lucide.createIcons();
483
+ });
484
+
485
+ // Event: provider change
486
+ var providerSelect = document.getElementById("email-setup-provider");
487
+ var helpLink = document.getElementById("email-setup-help");
488
+ var customFields = document.getElementById("email-setup-custom-fields");
489
+
490
+ var providerMeta = {
491
+ gmail: {
492
+ domain: "@gmail.com",
493
+ placeholder: "you",
494
+ helpUrl: "https://support.google.com/accounts/answer/185833",
495
+ },
496
+ outlook: {
497
+ domain: "@outlook.com",
498
+ placeholder: "you",
499
+ helpUrl: "https://support.microsoft.com/en-us/account-billing/using-app-passwords-with-apps-that-don-t-support-two-step-verification-5896ed9b-4263-e681-128a-a6f2979a7944",
500
+ },
501
+ yahoo: {
502
+ domain: "@yahoo.com",
503
+ placeholder: "you",
504
+ helpUrl: "https://help.yahoo.com/kb/generate-manage-third-party-passwords-sln15241.html",
505
+ },
506
+ custom: {
507
+ domain: null,
508
+ placeholder: "you@example.com",
509
+ helpUrl: null,
510
+ },
511
+ };
512
+
513
+ var emailInput = document.getElementById("email-setup-address");
514
+ var domainSuffix = document.getElementById("email-setup-domain");
515
+
516
+ function applyProviderMeta(val) {
517
+ var meta = providerMeta[val] || providerMeta.custom;
518
+ customFields.style.display = val === "custom" ? "block" : "none";
519
+ emailInput.placeholder = meta.placeholder;
520
+ if (meta.domain) {
521
+ domainSuffix.textContent = meta.domain;
522
+ domainSuffix.style.display = "";
523
+ emailInput.type = "text";
524
+ } else {
525
+ domainSuffix.style.display = "none";
526
+ emailInput.type = "email";
527
+ }
528
+ if (meta.helpUrl) {
529
+ helpLink.href = meta.helpUrl;
530
+ helpLink.style.display = "";
531
+ } else {
532
+ helpLink.style.display = "none";
533
+ }
534
+ }
535
+
536
+ providerSelect.addEventListener("change", function () {
537
+ applyProviderMeta(providerSelect.value);
538
+ });
539
+
540
+ // Strip @ and everything after it for known providers
541
+ emailInput.addEventListener("input", function () {
542
+ var meta = providerMeta[providerSelect.value];
543
+ if (meta && meta.domain) {
544
+ var val = emailInput.value;
545
+ var atIdx = val.indexOf("@");
546
+ if (atIdx !== -1) {
547
+ emailInput.value = val.substring(0, atIdx);
548
+ }
549
+ }
550
+ });
551
+
552
+ // Reset test state
553
+ _emailTestPassed = false;
554
+ _emailPendingSave = false;
555
+
556
+ // Reset test status when inputs change
557
+ function onInputChange() {
558
+ _emailTestPassed = false;
559
+ _emailPendingSave = false;
560
+ var saveBtn = document.getElementById("email-setup-save");
561
+ if (saveBtn) saveBtn.textContent = "Add Account";
562
+ }
563
+ document.getElementById("email-setup-address").addEventListener("input", onInputChange);
564
+ document.getElementById("email-setup-password").addEventListener("input", onInputChange);
565
+ providerSelect.addEventListener("change", onInputChange);
566
+
567
+ function runTest() {
568
+ var statusEl = document.getElementById("email-setup-status");
569
+ var emailAddr = getFullEmail();
570
+ var appPass = document.getElementById("email-setup-password").value;
571
+
572
+ if (!emailAddr || emailAddr.indexOf("@") === -1 || !appPass) {
573
+ statusEl.style.display = "block";
574
+ statusEl.style.color = "var(--error, #e74c3c)";
575
+ statusEl.textContent = "Email and app password are required.";
576
+ return;
577
+ }
578
+
579
+ statusEl.style.display = "block";
580
+ statusEl.style.color = "var(--text-muted)";
581
+ statusEl.textContent = "Testing connection...";
582
+
583
+ var msgData = buildEmailSetupData();
584
+ msgData.type = "email_account_test";
585
+
586
+ if (ctx && ctx.ws && ctx.connected) {
587
+ ctx.ws.send(JSON.stringify(msgData));
588
+ }
589
+ }
590
+
591
+ // Event: test connection
592
+ document.getElementById("email-setup-test").addEventListener("click", function () {
593
+ _emailPendingSave = false;
594
+ runTest();
595
+ });
596
+
597
+ // Event: save (requires test to pass first)
598
+ document.getElementById("email-setup-save").addEventListener("click", function () {
599
+ if (_emailTestPassed) {
600
+ // Test already passed, proceed with save
601
+ doSave();
602
+ return;
603
+ }
604
+ // Run test first, then auto-save on success
605
+ _emailPendingSave = true;
606
+ var saveBtn = document.getElementById("email-setup-save");
607
+ if (saveBtn) saveBtn.textContent = "Testing...";
608
+ runTest();
609
+ });
610
+
611
+ _emailDoSave = doSave;
612
+ function doSave() {
613
+ var statusEl = document.getElementById("email-setup-status");
614
+ statusEl.style.display = "block";
615
+ statusEl.style.color = "var(--text-muted)";
616
+ statusEl.textContent = "Adding account...";
617
+
618
+ var msgData = buildEmailSetupData();
619
+ msgData.type = "email_account_add";
620
+
621
+ if (ctx && ctx.ws && ctx.connected) {
622
+ ctx.ws.send(JSON.stringify(msgData));
623
+ }
624
+ }
625
+ }
626
+
627
+ function getFullEmail() {
628
+ var provider = document.getElementById("email-setup-provider").value;
629
+ var raw = document.getElementById("email-setup-address").value.trim();
630
+ var meta = { gmail: "@gmail.com", outlook: "@outlook.com", yahoo: "@yahoo.com" };
631
+ if (meta[provider] && raw.indexOf("@") === -1) {
632
+ return raw + meta[provider];
633
+ }
634
+ return raw;
635
+ }
636
+
637
+ function buildEmailSetupData() {
638
+ var provider = document.getElementById("email-setup-provider").value;
639
+ var email = getFullEmail();
640
+ var appPassword = document.getElementById("email-setup-password").value;
641
+ var data = { provider: provider, email: email, appPassword: appPassword };
642
+
643
+ if (provider === "custom") {
644
+ data.imap = {
645
+ host: document.getElementById("email-setup-imap-host").value.trim(),
646
+ port: parseInt(document.getElementById("email-setup-imap-port").value, 10) || 993,
647
+ tls: true,
648
+ };
649
+ data.smtp = {
650
+ host: document.getElementById("email-setup-smtp-host").value.trim(),
651
+ port: parseInt(document.getElementById("email-setup-smtp-port").value, 10) || 587,
652
+ };
653
+ }
654
+ return data;
655
+ }
656
+
657
+ // Handle email_account_test_result from server
658
+ export function handleEmailTestResult(msg) {
659
+ var statusEl = document.getElementById("email-setup-status");
660
+ if (!statusEl) return;
661
+ var saveBtn = document.getElementById("email-setup-save");
662
+
663
+ statusEl.style.display = "block";
664
+ if (msg.ok) {
665
+ _emailTestPassed = true;
666
+ if (_emailPendingSave && _emailDoSave) {
667
+ // Test passed from Add Account click, proceed to save
668
+ _emailPendingSave = false;
669
+ _emailDoSave();
670
+ return;
671
+ }
672
+ statusEl.style.color = "var(--success, #2ecc71)";
673
+ statusEl.textContent = "Connection successful! IMAP and SMTP both working.";
674
+ } else {
675
+ _emailTestPassed = false;
676
+ _emailPendingSave = false;
677
+ if (saveBtn) saveBtn.textContent = "Add Account";
678
+ statusEl.style.color = "var(--error, #e74c3c)";
679
+ var parts = [];
680
+ if (msg.imap && !msg.imap.ok) parts.push("IMAP: " + (msg.imap.error || "failed"));
681
+ if (msg.smtp && !msg.smtp.ok) parts.push("SMTP: " + (msg.smtp.error || "failed"));
682
+ statusEl.textContent = parts.length > 0 ? parts.join("; ") : (msg.error || "Connection failed");
683
+ }
684
+ }
685
+
686
+ // Handle email_account_add_result from server
687
+ export function handleEmailAddResult(msg) {
688
+ var statusEl = document.getElementById("email-setup-status");
689
+ if (!statusEl) return;
690
+
691
+ statusEl.style.display = "block";
692
+ if (msg.ok) {
693
+ statusEl.style.color = "#2ecc71";
694
+ statusEl.textContent = "Account added successfully!";
695
+ // Close modal after short delay
696
+ setTimeout(function () {
697
+ var modal = document.getElementById("email-setup-modal");
698
+ if (modal) modal.remove();
699
+ }, 800);
700
+ } else {
701
+ statusEl.style.color = "#e74c3c";
702
+ statusEl.textContent = msg.error || "Failed to add account.";
703
+ }
704
+ }
705
+
706
+ // Handle email_account_remove_result from server
707
+ export function handleEmailRemoveResult(msg) {
708
+ // Account removed; list will be refreshed via email_accounts_list message
709
+ }
710
+
285
711
  function getSourceLabel(id) {
286
712
  if (id.startsWith("term:")) {
287
713
  var termId = parseInt(id.split(":")[1], 10);
@@ -302,12 +728,23 @@ function getSourceLabel(id) {
302
728
  }
303
729
  return "Tab " + tabId;
304
730
  }
731
+ if (id.startsWith("email:")) {
732
+ var accId = id.split(":")[1];
733
+ for (var k = 0; k < emailAccountList.length; k++) {
734
+ if (emailAccountList[k].id === accId) {
735
+ var email = emailAccountList[k].email;
736
+ return email.length > 30 ? email.slice(0, 27) + "..." : email;
737
+ }
738
+ }
739
+ return "Email";
740
+ }
305
741
  return id;
306
742
  }
307
743
 
308
744
  function getSourceIcon(id) {
309
745
  if (id.startsWith("term:")) return "square-terminal";
310
746
  if (id.startsWith("tab:")) return "globe";
747
+ if (id.startsWith("email:")) return "mail";
311
748
  return "circle";
312
749
  }
313
750
 
@@ -321,6 +758,95 @@ export function hasActiveSources() {
321
758
  return activeSourceIds.size > 0;
322
759
  }
323
760
 
761
+ // --- Email Defaults Modal (project-level) ---
762
+
763
+ export function handleEmailDefaults(msg) {
764
+ emailDefaultAccountIds = msg.accounts || [];
765
+ renderEmailDefaultsList();
766
+ }
767
+
768
+ export function initEmailDefaultsModal() {
769
+ var btn = document.getElementById("email-sidebar-btn");
770
+ var modal = document.getElementById("email-defaults-modal");
771
+ var closeBtn = document.getElementById("email-defaults-close");
772
+ if (!btn || !modal) return;
773
+
774
+ btn.addEventListener("click", function () {
775
+ modal.classList.remove("hidden");
776
+ // Request current defaults from server
777
+ if (ctx && ctx.ws && ctx.connected) {
778
+ ctx.ws.send(JSON.stringify({ type: "email_defaults_get" }));
779
+ }
780
+ renderEmailDefaultsList();
781
+ if (typeof lucide !== "undefined") lucide.createIcons();
782
+ });
783
+
784
+ if (closeBtn) {
785
+ closeBtn.addEventListener("click", function () {
786
+ modal.classList.add("hidden");
787
+ });
788
+ }
789
+
790
+ var backdrop = modal.querySelector(".confirm-backdrop");
791
+ if (backdrop) {
792
+ backdrop.addEventListener("click", function () {
793
+ modal.classList.add("hidden");
794
+ });
795
+ }
796
+ }
797
+
798
+ function renderEmailDefaultsList() {
799
+ var listEl = document.getElementById("email-defaults-list");
800
+ if (!listEl) return;
801
+ listEl.innerHTML = "";
802
+
803
+ if (emailAccountList.length === 0) {
804
+ var emptyEl = document.createElement("div");
805
+ emptyEl.className = "mcp-empty";
806
+ emptyEl.innerHTML = '<p>No email accounts connected.</p>' +
807
+ '<button class="us-email-add-btn" type="button" style="margin:12px auto 0">+ Add Account</button>';
808
+ var addBtn = emptyEl.querySelector("button");
809
+ addBtn.addEventListener("click", function () {
810
+ document.getElementById("email-defaults-modal").classList.add("hidden");
811
+ showEmailSetupModal();
812
+ });
813
+ listEl.appendChild(emptyEl);
814
+ return;
815
+ }
816
+
817
+ for (var i = 0; i < emailAccountList.length; i++) {
818
+ var acc = emailAccountList[i];
819
+ var isOn = emailDefaultAccountIds.indexOf(acc.id) !== -1;
820
+
821
+ var row = document.createElement("label");
822
+ row.className = "settings-toggle-row";
823
+ row.innerHTML =
824
+ '<div>' +
825
+ '<span class="settings-label">' + escapeHtml(acc.email) + '</span>' +
826
+ '<div class="settings-hint">' + escapeHtml(acc.label || acc.provider || "Custom") + '</div>' +
827
+ '</div>' +
828
+ '<input type="checkbox" data-account-id="' + escapeHtml(acc.id) + '"' + (isOn ? ' checked' : '') + '>' +
829
+ '<span class="toggle-track"><span class="toggle-thumb"></span></span>';
830
+
831
+ var checkbox = row.querySelector("input");
832
+ checkbox.addEventListener("change", function () {
833
+ var accId = this.getAttribute("data-account-id");
834
+ var idx = emailDefaultAccountIds.indexOf(accId);
835
+ if (this.checked && idx === -1) {
836
+ emailDefaultAccountIds.push(accId);
837
+ } else if (!this.checked && idx !== -1) {
838
+ emailDefaultAccountIds.splice(idx, 1);
839
+ }
840
+ // Save to server
841
+ if (ctx && ctx.ws && ctx.connected) {
842
+ ctx.ws.send(JSON.stringify({ type: "email_defaults_save", accounts: emailDefaultAccountIds }));
843
+ }
844
+ });
845
+
846
+ listEl.appendChild(row);
847
+ }
848
+ }
849
+
324
850
  function escapeHtml(str) {
325
851
  var div = document.createElement("div");
326
852
  div.textContent = str;
@@ -7,6 +7,7 @@ import { renderUnifiedDiff, renderSplitDiff } from './diff.js';
7
7
  import { initFileIcons, getFileIconSvg, getFolderIconSvg } from './fileicons.js';
8
8
 
9
9
  var ctx;
10
+ var showDropHint = function () {};
10
11
  var treeData = {}; // path -> { loaded, children }
11
12
  var currentContent = null; // last read file content for copy
12
13
  var currentFilePath = null; // path of the currently viewed file
@@ -34,6 +35,66 @@ export function initFileBrowser(_ctx) {
34
35
  }
35
36
  });
36
37
 
38
+ // --- Drag-and-drop file paths into message input ---
39
+ var inputEl = document.getElementById("input");
40
+ var dropHintEl = null;
41
+ var dropHintTimer = null;
42
+
43
+ showDropHint = function () {
44
+ if (!inputEl) return;
45
+ if (!dropHintEl) {
46
+ dropHintEl = document.createElement("div");
47
+ dropHintEl.className = "fb-drop-hint";
48
+ dropHintEl.textContent = "Drop here to insert file path";
49
+ inputEl.parentElement.style.position = "relative";
50
+ inputEl.parentElement.appendChild(dropHintEl);
51
+ }
52
+ dropHintEl.classList.add("visible");
53
+ clearTimeout(dropHintTimer);
54
+ // Auto-hide after drag ends without drop
55
+ dropHintTimer = setTimeout(function () { hideDropHint(); }, 3000);
56
+ }
57
+
58
+ function hideDropHint() {
59
+ clearTimeout(dropHintTimer);
60
+ if (dropHintEl) dropHintEl.classList.remove("visible");
61
+ }
62
+
63
+ if (inputEl) {
64
+ inputEl.addEventListener("dragover", function (e) {
65
+ if (e.dataTransfer.types.indexOf("text/plain") !== -1) {
66
+ e.preventDefault();
67
+ e.dataTransfer.dropEffect = "copy";
68
+ inputEl.classList.add("drop-target");
69
+ }
70
+ });
71
+ inputEl.addEventListener("dragleave", function () {
72
+ inputEl.classList.remove("drop-target");
73
+ });
74
+ inputEl.addEventListener("drop", function (e) {
75
+ inputEl.classList.remove("drop-target");
76
+ hideDropHint();
77
+ var filePath = e.dataTransfer.getData("text/plain");
78
+ if (!filePath) return;
79
+ e.preventDefault();
80
+ var cursorPos = inputEl.selectionStart || 0;
81
+ var before = inputEl.value.substring(0, cursorPos);
82
+ var after = inputEl.value.substring(cursorPos);
83
+ var prefix = before.length > 0 && before[before.length - 1] !== " " && before[before.length - 1] !== "\n" ? " " : "";
84
+ var suffix = after.length > 0 && after[0] !== " " && after[0] !== "\n" ? " " : "";
85
+ inputEl.value = before + prefix + filePath + suffix + after;
86
+ var newPos = cursorPos + prefix.length + filePath.length + suffix.length;
87
+ inputEl.setSelectionRange(newPos, newPos);
88
+ inputEl.focus();
89
+ inputEl.dispatchEvent(new Event("input", { bubbles: true }));
90
+ });
91
+ // Hide hint when drag ends anywhere on the page
92
+ document.addEventListener("dragend", function () {
93
+ inputEl.classList.remove("drop-target");
94
+ hideDropHint();
95
+ });
96
+ }
97
+
37
98
  // Close button
38
99
  document.getElementById("file-viewer-close").addEventListener("click", function () {
39
100
  closeFileViewer();
@@ -418,6 +479,17 @@ function renderEntries(container, entries, depth) {
418
479
  row.className = "file-tree-item";
419
480
  row.style.paddingLeft = (8 + depth * 16) + "px";
420
481
 
482
+ row.draggable = true;
483
+ row.dataset.path = entry.path;
484
+ row.addEventListener("dragstart", function (e) {
485
+ var cwd = ctx.cwd || "";
486
+ var rel = this.dataset.path;
487
+ var abs = cwd ? cwd.replace(/\/$/, "") + "/" + rel : rel;
488
+ e.dataTransfer.setData("text/plain", abs);
489
+ e.dataTransfer.effectAllowed = "copy";
490
+ showDropHint();
491
+ });
492
+
421
493
  if (entry.type === "dir") {
422
494
  row.innerHTML =
423
495
  '<span class="file-tree-chevron">' + iconHtml("chevron-right") + '</span>' +
@@ -108,6 +108,13 @@ export function initMateSidebar(wsGetter) {
108
108
  if (origBtn) origBtn.click();
109
109
  });
110
110
  }
111
+ var mateEmailBtn = document.getElementById("mate-email-btn");
112
+ if (mateEmailBtn) {
113
+ mateEmailBtn.addEventListener("click", function () {
114
+ var origBtn = document.getElementById("email-sidebar-btn");
115
+ if (origBtn) origBtn.click();
116
+ });
117
+ }
111
118
  var mateSkillsBtn = document.getElementById("mate-skills-btn");
112
119
  if (mateSkillsBtn) {
113
120
  mateSkillsBtn.addEventListener("click", function () {
@@ -856,7 +856,7 @@ function renderSheetMateProfile(listEl) {
856
856
  // Action buttons
857
857
  var actions = [
858
858
  { icon: "book-open", label: "Knowledge", btnId: "mate-knowledge-btn", countId: "mate-knowledge-count" },
859
- { icon: "sticky-note", label: "Sticky Notes", btnId: "sticky-notes-toggle-btn", countId: "sticky-notes-sidebar-count" },
859
+ { icon: "sticky-note", label: "Sticky Notes", btnId: "sticky-notes-sidebar-btn", countId: "sticky-notes-sidebar-count" },
860
860
  { icon: "puzzle", label: "Skills", btnId: "mate-skills-btn" },
861
861
  { icon: "calendar", label: "Scheduled Tasks", btnId: "mate-scheduler-btn" }
862
862
  ];