Skip to content

Commit

Permalink
Use single click for email selection (#837)
Browse files Browse the repository at this point in the history
  • Loading branch information
harryzcy authored Jan 23, 2024
1 parent 7150b1a commit f6e83b7
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 53 deletions.
84 changes: 49 additions & 35 deletions web/src/components/emails/EmailTableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CheckIcon } from '@heroicons/react/20/solid'
import { useContext } from 'react'
import { useNavigate } from 'react-router-dom'

Expand All @@ -11,14 +12,12 @@ import { formatDate } from 'utils/time'
interface EmailTableRowProps {
email: EmailInfo
selected: boolean
onClick: (action: 'add' | 'replace') => void
onClick: () => void
}

export default function EmailTableRow(props: EmailTableRowProps) {
const { email, onClick } = props
const backgroundClassName = props.selected
? ' bg-blue-100 dark:bg-neutral-700'
: ''
const { email, onClick, selected } = props
const backgroundClassName = selected ? ' bg-blue-100 dark:bg-neutral-700' : ''
const unreadClassName = email.unread ? ' font-bold' : ' dark:font-light'

const draftEmailsContext = useContext(DraftEmailsContext)
Expand All @@ -33,61 +32,76 @@ export default function EmailTableRow(props: EmailTableRowProps) {
})
}

const isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(navigator.userAgent)
const openEmail = () => {
if (email.type === 'draft') {
if (email.threadID) {
navigate(`/inbox/thread/${email.threadID}`)
return
}
void openDraftEmail(email.messageID)
} else if (email.type === 'inbox' || email.type === 'sent') {
if (email.threadID) {
navigate(`/inbox/thread/${email.threadID}`)
return
}
navigate(`/inbox/${email.messageID}`)
}
}

return (
<div
className="group contents"
onClick={(event) => {
const shouldAdd =
(isMacLike && event.metaKey) || (!isMacLike && event.ctrlKey)
if (isMacLike) onClick(shouldAdd ? 'add' : 'replace')
}}
onDoubleClick={() => {
if (email.type === 'draft') {
if (email.threadID) {
navigate(`/inbox/thread/${email.threadID}`)
return
}
void openDraftEmail(email.messageID)
} else if (email.type === 'inbox' || email.type === 'sent') {
if (email.threadID) {
navigate(`/inbox/thread/${email.threadID}`)
return
}
navigate(`/inbox/${email.messageID}`)
<div className="group contents">
<div
className={
'cursor-pointer border-t border-neutral-200 px-3 md:px-4 py-2 group-first:border-0 dark:border-neutral-900 row-span-2 md:row-span-1' +
backgroundClassName +
unreadClassName
}
}}
>
onClick={() => {
onClick()
}}
>
<span className="h-full flex items-center">
<div
className={
'h-4 w-4 border rounded ' +
(selected
? 'border-neutral-900 dark:border-neutral-300'
: 'border-neutral-300 dark:border-neutral-500')
}
>
{selected && <CheckIcon className="h-3.5 w-3.5" />}
</div>
</span>
</div>
<div
className={
'relative cursor-pointer truncate border-t border-neutral-200 px-4 py-2 group-first:border-0 dark:border-neutral-900' +
'cursor-pointer truncate border-t border-neutral-200 pl-1 pr-4 py-1 pt-2 md:py-2 group-first:border-0 dark:border-neutral-900' +
backgroundClassName +
unreadClassName
}
onClick={openEmail}
>
{email.unread && (
<span className="absolute inset-1/2 left-[7px] h-1 w-1 -translate-y-1/2 transform rounded-full bg-neutral-900 dark:bg-neutral-300"></span>
)}
<span title={email.from && email.from.length > 0 ? email.from[0] : ''}>
{getNameFromEmails(email.from)}
</span>
</div>
<div
className={
'cursor-pointer truncate border-t border-neutral-200 px-4 py-2 group-first:border-0 dark:border-neutral-900' +
'cursor-pointer truncate md:border-t border-neutral-200 pl-1 pr-4 pb-2 md:py-2 group-first:border-0 dark:border-neutral-900 col-span-2 md:col-span-1' +
backgroundClassName +
unreadClassName
}
onClick={openEmail}
>
{email.subject}
</div>
<div
className={
'cursor-pointer border-t border-neutral-200 px-4 py-2 text-right group-first:border-0 dark:border-neutral-900' +
'cursor-pointer border-t border-neutral-200 px-4 py-1 pt-3 md:pt-2 text-right group-first:border-0 dark:border-neutral-900 text-xs md:text-base' +
backgroundClassName +
unreadClassName
(email.unread ? ' md:font-bold' : ' md:dark:font-light')
}
onClick={openEmail}
>
{formatDate(
email.timeReceived || email.timeUpdated || email.timeSent || '',
Expand Down
15 changes: 5 additions & 10 deletions web/src/components/emails/EmailTableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import EmailTableRow from './EmailTableRow'
interface EmailTableViewProps {
emails: EmailInfo[]
selected: string[]
toggleSelected: (messageID: string, action: 'add' | 'replace') => void
toggleSelected: (messageID: string) => void
hasMore: boolean
loadMoreEmails: () => void
}
Expand All @@ -26,20 +26,15 @@ export default function EmailTableView(props: EmailTableViewProps) {
}, [shouldLoadMore])

return (
<div
className="grid select-none rounded bg-neutral-50 py-1 shadow-md dark:bg-neutral-800 dark:text-neutral-300 md:rounded-md"
style={{
gridTemplateColumns: '1fr 4fr 1fr'
}}
>
<div className="grid grid-flow-dense grid-cols-[min-content,1fr,1fr] md:grid-cols-[min-content,1fr,4fr,1fr] items-stretch select-none rounded bg-neutral-50 py-1 shadow-md dark:bg-neutral-800 dark:text-neutral-300 md:rounded-md">
{emails.map((email) => {
return (
<EmailTableRow
key={email.messageID}
email={email}
selected={props.selected.includes(email.messageID)}
onClick={(action) => {
toggleSelected(email.messageID, action)
onClick={() => {
toggleSelected(email.messageID)
}}
/>
)
Expand All @@ -48,7 +43,7 @@ export default function EmailTableView(props: EmailTableViewProps) {
<div
ref={loadMoreRef}
className={
'col-span-3 px-4 py-1 pb-1 pr-[4%] text-center text-sm font-bold dark:border-neutral-900 dark:text-neutral-500' +
'col-span-3 md:col-span-4 px-4 py-1 pb-1 pr-4 text-center text-sm font-bold dark:border-neutral-900 dark:text-neutral-500' +
(emails.length === 0 ? '' : ' border-t pt-2')
}
>
Expand Down
7 changes: 1 addition & 6 deletions web/src/pages/EmailList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,7 @@ export default function EmailList() {
emailViewRef.current?.scrollTo(0, scrollYPosition)
}, [emailViewRef.current])

const toggleSelected = (messageID: string, action: 'add' | 'replace') => {
if (action === 'replace') {
setSelected([messageID])
return
}

const toggleSelected = (messageID: string) => {
if (selected.includes(messageID)) {
setSelected(selected.filter((s) => s !== messageID))
} else {
Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/EmailRawView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function EmailRawView() {
</span>
</div>
<div className="relative mt-5 rounded-md border p-5 text-sm dark:border-neutral-700 dark:text-neutral-400">
<React.Suspense fallback={<div>Loading...</div>}>
<React.Suspense fallback={<div className="px-2">Loading...</div>}>
<Await resolve={data.raw}>
{(raw: string) => (
<>
Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/EmailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export default function EmailView() {
<React.Suspense
fallback={
<div className="px-2 md:px-0 mb-4 overflow-scroll rounded-md bg-neutral-50 p-3 dark:bg-neutral-800 dark:text-neutral-200">
<span>Loading...</span>
<span className="px-2">Loading...</span>
</div>
}
>
Expand Down

0 comments on commit f6e83b7

Please sign in to comment.