Merge branch 'main' of https://github.com/bulwarkmail/webmail
This commit is contained in:
@@ -40,6 +40,7 @@ export function EmailListItem({ email, selected, onClick, onDoubleClick, onConte
|
||||
const density = useSettingsStore((state) => state.density);
|
||||
const mailLayout = useSettingsStore((state) => state.mailLayout);
|
||||
const emailKeywords = useSettingsStore((state) => state.emailKeywords);
|
||||
const tintListRowsByTag = useSettingsStore((state) => state.tintListRowsByTag);
|
||||
const showAvatarsInJunk = useSettingsStore((state) => state.showAvatarsInJunk);
|
||||
const { identities } = useAuthStore();
|
||||
const isChecked = selectedEmailIds.has(email.id);
|
||||
@@ -68,7 +69,7 @@ export function EmailListItem({ email, selected, onClick, onDoubleClick, onConte
|
||||
const keywordDefs = colorTagIds.map(id => emailKeywords.find(k => k.id === id) ?? { id, label: id, color: 'gray' });
|
||||
// Use first tag for background coloring
|
||||
const keywordDef = keywordDefs[0] ?? null;
|
||||
const colorTag = keywordDef ? KEYWORD_PALETTE[keywordDef.color]?.bg ?? null : null;
|
||||
const colorTag = (tintListRowsByTag && keywordDef) ? KEYWORD_PALETTE[keywordDef.color]?.bg ?? null : null;
|
||||
|
||||
// Drag and drop functionality
|
||||
const { dragHandlers, isDragging } = useEmailDrag({
|
||||
|
||||
@@ -2117,6 +2117,12 @@ export function EmailViewer({
|
||||
fit - the latter wraps header text to one character per line, which reads
|
||||
as 90deg-rotated vertical headers (issue #409). */
|
||||
html { overflow: hidden; height: auto !important; }
|
||||
/* Some emails put height:100% on a full-bleed wrapper table/div (not html/body),
|
||||
which - with body's overflow:hidden - clips the content to a sliver, and the
|
||||
scrollHeight-based auto-resize then locks the iframe short (a Box.co.il
|
||||
verification email rendered as a logo-only 150px strip). Neutralise the
|
||||
full-height trick on any element so the body grows to its content. */
|
||||
[style*="height:100%"], [style*="height: 100%"] { height: auto !important; }
|
||||
body { margin: 0; padding: ${bodyPadding}; overflow-x: auto; overflow-y: hidden; height: auto !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 14px; line-height: 1.6; color: #1a1a1a; background: #ffffff; word-wrap: break-word; overflow-wrap: break-word; }
|
||||
@media (max-width: 640px) { body { padding-left: ${mobileBodyPaddingX}; padding-right: ${mobileBodyPaddingX}; } }
|
||||
img { max-width: 100% !important; height: auto !important; }
|
||||
@@ -2164,15 +2170,30 @@ export function EmailViewer({
|
||||
const doc = iframe.contentDocument;
|
||||
if (doc?.body) {
|
||||
// Auto-resize iframe to fit content
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
const height = doc.documentElement.scrollHeight;
|
||||
// Measure max(documentElement, body): a height:100% wrapper can leave
|
||||
// documentElement.scrollHeight short while the real content lives in body.
|
||||
const applyHeight = () => {
|
||||
if (iframe.contentDocument !== doc) return; // navigated away; stale
|
||||
const height = Math.max(doc.documentElement.scrollHeight, doc.body.scrollHeight);
|
||||
iframe.style.height = height + 'px';
|
||||
lastBodyHeightRef.current = height;
|
||||
});
|
||||
};
|
||||
const resizeObserver = new ResizeObserver(applyHeight);
|
||||
resizeObserver.observe(doc.body);
|
||||
const initialHeight = doc.documentElement.scrollHeight;
|
||||
iframe.style.height = initialHeight + 'px';
|
||||
lastBodyHeightRef.current = initialHeight;
|
||||
applyHeight();
|
||||
// The ResizeObserver only fires on body's border box; a content overflow
|
||||
// that grows scrollHeight without resizing that box (e.g. a height:100%
|
||||
// wrapper, or images that reflow the layout after onload) is otherwise
|
||||
// missed and the iframe stays short. Re-measure on a fixed cadence over a
|
||||
// short settle window, then stop — a self-clearing catch-all that does
|
||||
// not depend on image load/error events firing (blocked images may fire
|
||||
// neither). Cheap: ~12 scrollHeight reads, no early-stop heuristic to
|
||||
// mis-trigger on a brief-stable-then-grow reflow.
|
||||
const poll = window.setInterval(() => {
|
||||
if (iframe.contentDocument !== doc) { window.clearInterval(poll); return; }
|
||||
applyHeight();
|
||||
}, 200);
|
||||
window.setTimeout(() => window.clearInterval(poll), 2400);
|
||||
setIframeReady(true);
|
||||
|
||||
// Hide images that fail to load (dead/mixed-content/unreachable external
|
||||
|
||||
@@ -90,6 +90,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
const showRecipient = currentMailboxRole === 'sent' || currentMailboxRole === 'drafts';
|
||||
const sender = showRecipient ? (email.to?.[0] ?? email.from?.[0]) : email.from?.[0];
|
||||
const emailKeywords = useSettingsStore((state) => state.emailKeywords);
|
||||
const tintListRowsByTag = useSettingsStore((state) => state.tintListRowsByTag);
|
||||
const density = useSettingsStore((state) => state.density);
|
||||
const mailLayout = useSettingsStore((state) => state.mailLayout);
|
||||
const timeFormat = useSettingsStore((state) => state.timeFormat);
|
||||
@@ -113,7 +114,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
||||
const tagIds = getEmailColorTags(email.keywords);
|
||||
const resolvedKeywordDefs = tagIds.map(id => emailKeywords.find(k => k.id === id) ?? { id, label: id, color: 'gray' });
|
||||
const resolvedKeywordDef = resolvedKeywordDefs[0] ?? null;
|
||||
const resolvedColorTag = (() => {
|
||||
const resolvedColorTag = !tintListRowsByTag ? null : (() => {
|
||||
if (colorTag) return colorTag;
|
||||
return resolvedKeywordDef ? KEYWORD_PALETTE[resolvedKeywordDef.color]?.bg ?? null : null;
|
||||
})();
|
||||
@@ -494,8 +495,9 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
||||
|
||||
const threadColor = getThreadColorTag(thread.emails);
|
||||
const emailKeywordDefs = useSettingsStore((state) => state.emailKeywords);
|
||||
const tintListRowsByTag = useSettingsStore((state) => state.tintListRowsByTag);
|
||||
const keywordDef = threadColor ? (emailKeywordDefs.find(k => k.id === threadColor) ?? { id: threadColor, label: threadColor, color: 'gray' }) : null;
|
||||
const colorTag = keywordDef ? KEYWORD_PALETTE[keywordDef.color]?.bg ?? null : null;
|
||||
const colorTag = (tintListRowsByTag && keywordDef) ? KEYWORD_PALETTE[keywordDef.color]?.bg ?? null : null;
|
||||
|
||||
const isSelected = selectedEmailId === latestEmail.id ||
|
||||
thread.emails.some(e => e.id === selectedEmailId);
|
||||
|
||||
@@ -118,7 +118,7 @@ function MailLayoutPreview({
|
||||
export function LayoutSettings() {
|
||||
const t = useTranslations('settings.appearance');
|
||||
const tEmail = useTranslations('settings.email_behavior');
|
||||
const { toolbarPosition, showToolbarLabels, hideAccountSwitcher, showRailAccountList, enableUnifiedMailbox, includeGroupInUnified, enableAllMailView, allMailFolderIds, enableCrossUnreadView, enableCrossStarredView, enableCrossAllView, colorfulSidebarIcons, showFolderTotalCount, mailLayout, proInterface, updateSetting } = useSettingsStore();
|
||||
const { toolbarPosition, showToolbarLabels, hideAccountSwitcher, showRailAccountList, enableUnifiedMailbox, includeGroupInUnified, enableAllMailView, allMailFolderIds, enableCrossUnreadView, enableCrossStarredView, enableCrossAllView, colorfulSidebarIcons, tintListRowsByTag, showFolderTotalCount, mailLayout, proInterface, updateSetting } = useSettingsStore();
|
||||
const { isSettingLocked, isSettingHidden, isFeatureEnabled } = usePolicyStore();
|
||||
const accounts = useAccountStore(s => s.accounts);
|
||||
const activeAccountId = useAccountStore(s => s.activeAccountId);
|
||||
@@ -217,6 +217,13 @@ export function LayoutSettings() {
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t('tint_list_rows.label')} description={t('tint_list_rows.description')}>
|
||||
<ToggleSwitch
|
||||
checked={tintListRowsByTag}
|
||||
onChange={(checked) => updateSetting('tintListRowsByTag', checked)}
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t('show_folder_total_count.label')} description={t('show_folder_total_count.description')}>
|
||||
<ToggleSwitch
|
||||
checked={showFolderTotalCount}
|
||||
|
||||
Reference in New Issue
Block a user