mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 08:15:38 +00:00
feat: tell reader plugin frames whether they are on a phone, a tablet or a desktop
This commit is contained in:
@@ -246,6 +246,16 @@ describe('reader-selection-menu and the side panel\'s selection', () => {
|
||||
});
|
||||
|
||||
describe('reader-header and reader-below-subject', () => {
|
||||
it('tell the frames which layout they are in', () => {
|
||||
offerSlots('p1', ['email-banner']);
|
||||
const { unmount } = viewer(textMail('Hi'));
|
||||
expect(slotProps(screen.getByTestId('slot-email-banner')).deviceType).toBe('desktop');
|
||||
unmount();
|
||||
device.isMobile = true;
|
||||
viewer(textMail('Hi'));
|
||||
expect(slotProps(screen.getByTestId('slot-email-banner')).deviceType).toBe('mobile');
|
||||
});
|
||||
|
||||
it('put the frames above the subject and under the sender, with the message', () => {
|
||||
offerSlots('p1', ['reader-header', 'reader-below-subject']);
|
||||
const { container } = viewer(textMail('Hi'));
|
||||
@@ -344,12 +354,13 @@ describe('thread-summary', () => {
|
||||
];
|
||||
|
||||
it('sits above the messages of the phone conversation view', () => {
|
||||
device.isMobile = true;
|
||||
offerSlots('p1', ['thread-summary']);
|
||||
const thread = { threadKey: 't1', latestEmail: conversation[1], emails: conversation, emailCount: 2 } as unknown as ThreadGroup;
|
||||
render(<ThreadConversationView thread={thread} emails={[conversation[1], conversation[0]]} onBack={() => {}} />);
|
||||
|
||||
const frame = screen.getByTestId('slot-thread-summary');
|
||||
expect(slotProps(frame)).toEqual({ threadId: 't1', emailIds: ['m2', 'm1'] });
|
||||
expect(slotProps(frame)).toEqual({ threadId: 't1', emailIds: ['m2', 'm1'], deviceType: 'mobile' });
|
||||
expect(follows(frame, screen.getAllByText('Alice')[0])).toBe(true);
|
||||
});
|
||||
|
||||
@@ -358,7 +369,7 @@ describe('thread-summary', () => {
|
||||
useSettingsStore.setState({ interfaceLayout: 'mountain-view', markAsReadDelay: 0 });
|
||||
viewer(conversation[1], { mvConversation: conversation });
|
||||
|
||||
expect(slotProps(screen.getByTestId('slot-thread-summary'))).toEqual({ threadId: 't1', emailIds: ['m1', 'm2'] });
|
||||
expect(slotProps(screen.getByTestId('slot-thread-summary'))).toEqual({ threadId: 't1', emailIds: ['m1', 'm2'], deviceType: 'desktop' });
|
||||
});
|
||||
|
||||
it('draws nothing without a plugin, and nothing for a single message', () => {
|
||||
|
||||
@@ -1170,8 +1170,8 @@ export function EmailViewer({
|
||||
const mvAfter = mvOpenIndex >= 0 ? mvConversation!.slice(mvOpenIndex + 1) : [];
|
||||
const mvConversationIds = mvOpenIndex >= 0 ? mvConversation!.map((e) => e.id).join(',') : '';
|
||||
const threadSummaryProps = useMemo(
|
||||
() => ({ threadId: email?.threadId ?? null, emailIds: mvConversationIds ? mvConversationIds.split(',') : [] }),
|
||||
[email?.threadId, mvConversationIds],
|
||||
() => ({ threadId: email?.threadId ?? null, emailIds: mvConversationIds ? mvConversationIds.split(',') : [], deviceType: isMobile ? 'mobile' : isTablet ? 'tablet' : 'desktop' }),
|
||||
[email?.threadId, mvConversationIds, isMobile, isTablet],
|
||||
);
|
||||
// Mountain View: which other messages of the conversation are unfolded. A
|
||||
// conversation opens with its unread messages unfolded, as Gmail does, so
|
||||
@@ -1512,7 +1512,10 @@ export function EmailViewer({
|
||||
() => (email && wantsPluginEmailView ? emailToReadView(email) : null),
|
||||
[email, wantsPluginEmailView],
|
||||
);
|
||||
const pluginEmailProps = useMemo(() => ({ email: pluginEmailView }), [pluginEmailView]);
|
||||
// Reader frames learn the layout: the bar a plugin shows on phones has no
|
||||
// place next to a side panel on a desktop.
|
||||
const pluginDeviceType = isMobile ? 'mobile' : isTablet ? 'tablet' : 'desktop';
|
||||
const pluginEmailProps = useMemo(() => ({ email: pluginEmailView, deviceType: pluginDeviceType }), [pluginEmailView, pluginDeviceType]);
|
||||
// The text selected in the body, for the side panel (a plugin working on
|
||||
// what the user highlights) and the right-click menu on it. Reset per
|
||||
// message; the body documents report changes a moment after they happen.
|
||||
|
||||
@@ -49,6 +49,7 @@ import { PluginSlot } from "@/components/plugins/plugin-slot";
|
||||
import { ReaderSelectionMenu, useReaderSelectionMenu, watchBodySelection } from "./reader-selection-menu";
|
||||
import { PluginActions } from "@/components/plugins/plugin-actions";
|
||||
import { usePluginSlotOffers } from "@/hooks/use-plugin-slot-offers";
|
||||
import { useDeviceDetection } from "@/hooks/use-media-query";
|
||||
import { usePluginRenderedBody } from "@/hooks/use-plugin-rendered-body";
|
||||
import { emailToReadView } from "@/lib/plugin-projection";
|
||||
import { AttachmentPluginMenu, ReaderAboveQuoteSlot } from "./reader-plugin-surfaces";
|
||||
@@ -191,9 +192,10 @@ export function ThreadConversationView({
|
||||
const t = useTranslations();
|
||||
const externalContent = useConversationExternalContent();
|
||||
const emailIdList = emails.map((email) => email.id).join(',');
|
||||
const { isMobile: summaryOnPhone, isTablet: summaryOnTablet } = useDeviceDetection();
|
||||
const threadSummaryProps = useMemo(
|
||||
() => ({ threadId: thread.latestEmail.threadId ?? null, emailIds: emailIdList ? emailIdList.split(',') : [] }),
|
||||
[thread.latestEmail.threadId, emailIdList],
|
||||
() => ({ threadId: thread.latestEmail.threadId ?? null, emailIds: emailIdList ? emailIdList.split(',') : [], deviceType: summaryOnPhone ? 'mobile' : summaryOnTablet ? 'tablet' : 'desktop' }),
|
||||
[thread.latestEmail.threadId, emailIdList, summaryOnPhone, summaryOnTablet],
|
||||
);
|
||||
|
||||
// Track which emails are expanded (most recent by default)
|
||||
@@ -325,7 +327,9 @@ function useConversationPluginProps(email: Email) {
|
||||
const belowSubjectOffers = usePluginSlotOffers('reader-below-subject').length;
|
||||
const bannerOffers = usePluginSlotOffers('email-banner').length;
|
||||
const wantsView = headerOffers + belowSubjectOffers + bannerOffers > 0;
|
||||
const emailProps = useMemo(() => ({ email: wantsView ? emailToReadView(email) : null }), [email, wantsView]);
|
||||
const { isMobile, isTablet } = useDeviceDetection();
|
||||
const deviceType = isMobile ? 'mobile' : isTablet ? 'tablet' : 'desktop';
|
||||
const emailProps = useMemo(() => ({ email: wantsView ? emailToReadView(email) : null, deviceType }), [email, wantsView, deviceType]);
|
||||
const idProps = useMemo(() => ({ emailId: email.id }), [email.id]);
|
||||
return { emailProps, idProps };
|
||||
}
|
||||
|
||||
@@ -65,10 +65,10 @@ export const SLOT_CATALOG = {
|
||||
// ─── Reader ────────────────────────────────────────────────
|
||||
'reader-header': S('reader', 'frame', ['ui:email-banner'], { email: 'email:read' }, 'at the top of the open message, above the subject'),
|
||||
'reader-below-subject': S('reader', 'frame', ['ui:email-banner'], { email: 'email:read' }, 'right under the subject and sender of the open message'),
|
||||
'email-banner': S('reader', 'frame', ['ui:email-banner'], { email: 'email:read' }, 'a banner above the message body'),
|
||||
'email-banner': S('reader', 'frame', ['ui:email-banner'], { email: 'email:read', deviceType: null }, 'a banner above the message body; `deviceType` ("mobile" | "tablet" | "desktop") says whether the reader has a side panel (not on phones)'),
|
||||
'reader-above-quote': S('reader', 'frame', ['ui:email-banner'], { email: 'email:read' }, 'between the new text of a message and the quoted text below it'),
|
||||
'email-footer': S('reader', 'frame', ['ui:email-footer'], { emailId: 'email:read' }, 'below the message body'),
|
||||
'thread-summary': S('reader', 'frame', ['ui:email-banner'], { threadId: 'email:read', emailIds: 'email:read' }, 'above the messages of an open conversation (the phone conversation view and Mountain View; the default desktop layout opens one message at a time and has no conversation view)'),
|
||||
'thread-summary': S('reader', 'frame', ['ui:email-banner'], { threadId: 'email:read', emailIds: 'email:read', deviceType: null }, 'above the messages of an open conversation (the phone conversation view and Mountain View; the default desktop layout opens one message at a time and has no conversation view)'),
|
||||
'reader-actions': S('reader', 'action', ['ui:toolbar'], { emailId: 'email:read' }, 'buttons in the open message\'s toolbar', true),
|
||||
'email-detail-sidebar': S('reader', 'frame', ['ui:email-details'], { email: 'email:read', selection: 'email:read', focusRequest: null }, 'a side panel next to the open message; `selection` is the text selected in the message as the user changes it ({ text }); `focusRequest` counts up when the plugin opened the panel with api.ui.openPanel'),
|
||||
'email-details-section': S('reader', 'frame', ['ui:email-details'], { email: 'email:read', category: null }, 'a section in the message details'),
|
||||
|
||||
Reference in New Issue
Block a user