Add 'Add to contacts' feature

Toolbar More menu now saves sender to Contact Manager with green
success toast or grey 'Already in contacts' toast for duplicates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Devon
2026-05-25 10:19:48 -04:00
parent 22aca3cfd1
commit f7873d14c7
5 changed files with 51 additions and 96 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "dashmail",
"runtimeExecutable": "npx",
"runtimeArgs": ["serve", "-p", "3000", "."],
"port": 3000
}
]
}
+7
View File
@@ -15,3 +15,10 @@ desktop.ini
# Node (if added later) # Node (if added later)
node_modules/ node_modules/
# Local scripts
push.bat
push.ps1
# Claude Code local settings (machine-specific)
.claude/settings.local.json
+33 -4
View File
@@ -2896,11 +2896,19 @@ function OnboardingWizard({accountId,onComplete,C}){
// Email Toolbar // Email Toolbar
function EmailToolbar({email,folder,C,onReply,onReplyAll,onForward,onArchive,onDelete,onJunk,onStar,onUnread,onPrint}){ function EmailToolbar({email,folder,C,onReply,onReplyAll,onForward,onArchive,onDelete,onJunk,onStar,onUnread,onPrint,onAddToContacts}){
const[moreOpen,setMore]=useState(false); const[moreOpen,setMore]=useState(false);
const[addedToast,setAddedToast]=useState(null); // null | "added" | "exists"
const isSent=folder==="sent"||folder==="drafts"||folder==="out"; const isSent=folder==="sent"||folder==="drafts"||folder==="out";
function handleAddToContacts(){
setMore(false);
const result=onAddToContacts?.({name:email.from,email:email.from_email,av:email.av,avColor:email.avColor});
setAddedToast(result||"added");
setTimeout(()=>setAddedToast(null),3000);
}
return <div style={{display:"flex",alignItems:"center",gap:2,padding:"6px 14px", return <div style={{display:"flex",alignItems:"center",gap:2,padding:"6px 14px",
borderBottom:`1px solid ${C.border}`,background:C.surface,flexShrink:0,flexWrap:"wrap"}}> borderBottom:`1px solid ${C.border}`,background:C.surface,flexShrink:0,flexWrap:"wrap",position:"relative"}}>
{!isSent&&<> {!isSent&&<>
<IBtn icon="ti-arrow-back-up" title="Reply" C={C} onClick={onReply} label="Reply"/> <IBtn icon="ti-arrow-back-up" title="Reply" C={C} onClick={onReply} label="Reply"/>
<IBtn icon="ti-arrows-left" title="Reply All" C={C} onClick={onReplyAll} label="Reply All"/> <IBtn icon="ti-arrows-left" title="Reply All" C={C} onClick={onReplyAll} label="Reply All"/>
@@ -2922,7 +2930,7 @@ function EmailToolbar({email,folder,C,onReply,onReplyAll,onForward,onArchive,onD
{[["ti-alert-triangle","Mark as junk",()=>{onJunk();setMore(false);}], {[["ti-alert-triangle","Mark as junk",()=>{onJunk();setMore(false);}],
["ti-copy","Copy to folder",()=>setMore(false)], ["ti-copy","Copy to folder",()=>setMore(false)],
["ti-code","View source",()=>setMore(false)], ["ti-code","View source",()=>setMore(false)],
["ti-user-plus","Add to contacts",()=>setMore(false)]].map(([ic,lbl,fn])=>( ["ti-user-plus","Add to contacts",handleAddToContacts]].map(([ic,lbl,fn])=>(
<button key={lbl} onClick={fn} style={{display:"flex",alignItems:"center",gap:9, <button key={lbl} onClick={fn} style={{display:"flex",alignItems:"center",gap:9,
width:"100%",padding:"9px 14px",background:"transparent",border:"none", width:"100%",padding:"9px 14px",background:"transparent",border:"none",
cursor:"pointer",fontFamily:FONT,fontSize:13,color:C.text,textAlign:"left"}} cursor:"pointer",fontFamily:FONT,fontSize:13,color:C.text,textAlign:"left"}}
@@ -2933,6 +2941,16 @@ function EmailToolbar({email,folder,C,onReply,onReplyAll,onForward,onArchive,onD
))} ))}
</div>} </div>}
</div> </div>
{addedToast&&<div style={{position:"absolute",top:44,right:14,zIndex:60,
display:"flex",alignItems:"center",gap:8,padding:"8px 14px",borderRadius:8,
background:addedToast==="added"?"#166534":"#374151",
boxShadow:"0 4px 16px rgba(0,0,0,0.25)",animation:"loginIn 0.2s ease both"}}>
<i className={`ti ${addedToast==="added"?"ti-user-check":"ti-user-x"}`}
style={{fontSize:15,color:addedToast==="added"?"#86efac":"#9ca3af"}} aria-hidden="true"/>
<span style={{fontSize:13,color:"#fff",fontFamily:FONT,whiteSpace:"nowrap"}}>
{addedToast==="added"?"Contact added successfully":"Already in contacts"}
</span>
</div>}
</div>; </div>;
} }
@@ -3694,6 +3712,16 @@ function EmailApp({activeProfile,profiles,setActiveProfile,saveProfile,loginBg,s
function doDelete(){if(selEmail){setEmails(p=>p.map(e=>e.id===selEmail.id?{...e,folder:"trash"}:e));setSelEmail(null);}} function doDelete(){if(selEmail){setEmails(p=>p.map(e=>e.id===selEmail.id?{...e,folder:"trash"}:e));setSelEmail(null);}}
function doJunk(){if(selEmail){setEmails(p=>p.map(e=>e.id===selEmail.id?{...e,folder:"trash"}:e));setSelEmail(null);}} function doJunk(){if(selEmail){setEmails(p=>p.map(e=>e.id===selEmail.id?{...e,folder:"trash"}:e));setSelEmail(null);}}
function doStar(){if(selEmail)setEmails(p=>p.map(e=>e.id===selEmail.id?{...e,starred:!e.starred}:e));} function doStar(){if(selEmail)setEmails(p=>p.map(e=>e.id===selEmail.id?{...e,starred:!e.starred}:e));}
function doAddToContacts(data){
if(!data?.email)return"added";
const exists=contacts.some(c=>c.email?.toLowerCase()===data.email.toLowerCase());
if(!exists){
setContacts(p=>[...p,{id:"ct"+uid(),name:data.name||data.email,email:data.email,
av:data.av||data.email.slice(0,2).toUpperCase(),avColor:data.avColor||"#0078d4",phone:"",address:""}]);
return"added";
}
return"exists";
}
function doUnread(){if(selEmail){setEmails(p=>p.map(e=>e.id===selEmail.id?{...e,read:false}:e));setSelEmail(null);}} function doUnread(){if(selEmail){setEmails(p=>p.map(e=>e.id===selEmail.id?{...e,read:false}:e));setSelEmail(null);}}
function openReply(mode){ function openReply(mode){
if(!selEmail)return; if(!selEmail)return;
@@ -4029,7 +4057,8 @@ function EmailApp({activeProfile,profiles,setActiveProfile,saveProfile,loginBg,s
<EmailToolbar email={selEmail} folder={selFolder} C={C} <EmailToolbar email={selEmail} folder={selFolder} C={C}
onReply={()=>openReply("reply")} onReplyAll={()=>openReply("replyAll")} onReply={()=>openReply("reply")} onReplyAll={()=>openReply("replyAll")}
onForward={()=>openReply("forward")} onArchive={doArchive} onDelete={doDelete} onForward={()=>openReply("forward")} onArchive={doArchive} onDelete={doDelete}
onJunk={doJunk} onStar={doStar} onUnread={doUnread} onPrint={()=>printEmail(selEmail)}/> onJunk={doJunk} onStar={doStar} onUnread={doUnread} onPrint={()=>printEmail(selEmail)}
onAddToContacts={doAddToContacts}/>
<div style={{padding:"16px 22px 14px",borderBottom:`1px solid ${bgDark?"rgba(255,255,255,0.12)":C.border}`,background:bgDark?"rgba(0,0,0,0.25)":"rgba(255,255,255,0.7)",flexShrink:0,backdropFilter:bgIsImage(bg)?"blur(12px)":"none"}}> <div style={{padding:"16px 22px 14px",borderBottom:`1px solid ${bgDark?"rgba(255,255,255,0.12)":C.border}`,background:bgDark?"rgba(0,0,0,0.25)":"rgba(255,255,255,0.7)",flexShrink:0,backdropFilter:bgIsImage(bg)?"blur(12px)":"none"}}>
<h2 style={{margin:"0 0 12px",fontSize:17,fontWeight:600,color:readingTextColor,lineHeight:1.3}}> <h2 style={{margin:"0 0 12px",fontSize:17,fontWeight:600,color:readingTextColor,lineHeight:1.3}}>
{selEmail.subject} {selEmail.subject}
-35
View File
@@ -1,35 +0,0 @@
@echo off
cd /d "%~dp0"
echo.
echo DashMail ^— Push to Gitea
echo =========================================
echo.
git status --short
echo.
set /p msg=" Commit message: "
if "%msg%"=="" (
echo.
echo Cancelled -- no message entered.
echo.
pause
exit /b 1
)
echo.
git add -A
git commit -m "%msg%"
git push
echo.
if %errorlevel%==0 (
echo Pushed successfully.
) else (
echo Push failed -- check your connection to Gitea.
)
echo.
pause
-57
View File
@@ -1,57 +0,0 @@
# DashMail — quick push to Gitea
Set-Location $PSScriptRoot
Write-Host ""
Write-Host " DashMail — Push to Gitea" -ForegroundColor Cyan
Write-Host " ─────────────────────────────────────" -ForegroundColor DarkGray
# Show what has changed
$status = git status --short
if (-not $status) {
Write-Host ""
Write-Host " Nothing to commit — everything is up to date." -ForegroundColor Green
Write-Host ""
Read-Host " Press Enter to close"
exit 0
}
Write-Host ""
Write-Host " Changed files:" -ForegroundColor Yellow
$status | ForEach-Object { Write-Host " $_" -ForegroundColor DarkYellow }
Write-Host ""
# Prompt for commit message
$msg = Read-Host " Commit message"
if (-not $msg.Trim()) {
Write-Host ""
Write-Host " Cancelled — no message entered." -ForegroundColor Red
Write-Host ""
Read-Host " Press Enter to close"
exit 1
}
Write-Host ""
# Stage, commit, push
git add -A
git commit -m $msg
if ($LASTEXITCODE -ne 0) {
Write-Host ""
Write-Host " Commit failed." -ForegroundColor Red
Read-Host " Press Enter to close"
exit 1
}
git push
if ($LASTEXITCODE -eq 0) {
Write-Host ""
Write-Host " Pushed successfully." -ForegroundColor Green
} else {
Write-Host ""
Write-Host " Push failed — check your connection to Gitea." -ForegroundColor Red
}
Write-Host ""
Read-Host " Press Enter to close"