diff --git a/app/internal_packages/composer/lib/composer-view.tsx b/app/internal_packages/composer/lib/composer-view.tsx index 3cd74294c..54fde0262 100644 --- a/app/internal_packages/composer/lib/composer-view.tsx +++ b/app/internal_packages/composer/lib/composer-view.tsx @@ -165,8 +165,10 @@ export default class ComposerView extends React.Component +
{ if (el) { @@ -333,8 +335,9 @@ export default class ComposerView extends React.Component +
{ // the `border-collapse: collapse` css property while setting a // `padding`. const styles = EmailFrameStylesStore.styles(); + const restrictWidth = AppEnv.config.get('core.reading.restrictMaxWidth'); + doc.open(); doc.write( `` + @@ -92,6 +94,10 @@ export default class EmailFrame extends React.Component { ); doc.close(); + if (doc.body && restrictWidth) { + doc.body.classList.add('restrict-width'); + } + // Notify the EventedIFrame that we've replaced it's document (with `open`) // so it can attach event listeners again. this._iframeComponent.didReplaceDocument(); diff --git a/app/internal_packages/message-list/lib/message-item-container.tsx b/app/internal_packages/message-list/lib/message-item-container.tsx index 579223ae5..4f6068641 100644 --- a/app/internal_packages/message-list/lib/message-item-container.tsx +++ b/app/internal_packages/message-list/lib/message-item-container.tsx @@ -1,13 +1,6 @@ import classNames from 'classnames'; import React from 'react'; -import { - PropTypes, - Utils, - DraftStore, - ComponentRegistry, - Thread, - Message, -} from 'mailspring-exports'; +import { Utils, DraftStore, ComponentRegistry, Thread, Message } from 'mailspring-exports'; import MessageItem from './message-item'; @@ -21,9 +14,13 @@ interface MessageItemContainerProps { scrollTo: () => void; } +interface MessageItemContainerState { + isSending: boolean; +} + export default class MessageItemContainer extends React.Component< MessageItemContainerProps, - { isSending: boolean } + MessageItemContainerState > { static displayName = 'MessageItemContainer'; diff --git a/app/internal_packages/message-list/lib/message-list-scroll-tooltip.tsx b/app/internal_packages/message-list/lib/message-list-scroll-tooltip.tsx new file mode 100644 index 000000000..17b8bb103 --- /dev/null +++ b/app/internal_packages/message-list/lib/message-list-scroll-tooltip.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { localized, PropTypes, Utils } from 'mailspring-exports'; + +export class MessageListScrollTooltip extends React.Component< + { viewportCenter: number; totalHeight: number }, + { idx: number; count: number } +> { + static displayName = 'MessageListScrollTooltip'; + static propTypes = { + viewportCenter: PropTypes.number.isRequired, + totalHeight: PropTypes.number.isRequired, + }; + + componentWillMount() { + this.setupForProps(this.props); + } + + componentWillReceiveProps(newProps) { + this.setupForProps(newProps); + } + + shouldComponentUpdate(newProps, newState) { + return !Utils.isEqualReact(this.state, newState); + } + + setupForProps(props) { + // Technically, we could have MessageList provide the currently visible + // item index, but the DOM approach is simple and self-contained. + // + const els = document.querySelectorAll('.message-item-wrap'); + let idx = Array.from(els).findIndex(el => (el as HTMLElement).offsetTop > props.viewportCenter); + if (idx === -1) { + idx = els.length; + } + + this.setState({ + idx: idx, + count: els.length, + }); + } + + render() { + return ( +
+ {localized('%1$@ of %2$@', this.state.idx, this.state.count)} +
+ ); + } +} diff --git a/app/internal_packages/message-list/lib/message-list.tsx b/app/internal_packages/message-list/lib/message-list.tsx index 7e4473773..5831fbc27 100644 --- a/app/internal_packages/message-list/lib/message-list.tsx +++ b/app/internal_packages/message-list/lib/message-list.tsx @@ -1,10 +1,8 @@ import classNames from 'classnames'; import React from 'react'; import ReactDOM from 'react-dom'; - import { localized, - PropTypes, Utils, Actions, MessageStore, @@ -27,53 +25,8 @@ import { import FindInThread from './find-in-thread'; import MessageItemContainer from './message-item-container'; - -class MessageListScrollTooltip extends React.Component< - { viewportCenter: number; totalHeight: number }, - { idx: number; count: number } -> { - static displayName = 'MessageListScrollTooltip'; - static propTypes = { - viewportCenter: PropTypes.number.isRequired, - totalHeight: PropTypes.number.isRequired, - }; - - componentWillMount() { - this.setupForProps(this.props); - } - - componentWillReceiveProps(newProps) { - this.setupForProps(newProps); - } - - shouldComponentUpdate(newProps, newState) { - return !Utils.isEqualReact(this.state, newState); - } - - setupForProps(props) { - // Technically, we could have MessageList provide the currently visible - // item index, but the DOM approach is simple and self-contained. - // - const els = document.querySelectorAll('.message-item-wrap'); - let idx = Array.from(els).findIndex(el => (el as HTMLElement).offsetTop > props.viewportCenter); - if (idx === -1) { - idx = els.length; - } - - this.setState({ - idx: idx, - count: els.length, - }); - } - - render() { - return ( -
- {localized('%1$@ of %2$@', this.state.idx, this.state.count)} -
- ); - } -} +import { MessageListScrollTooltip } from './message-list-scroll-tooltip'; +import { SubjectLineIcons } from './subject-line-icons'; interface MessageListState { messages: Message[]; @@ -87,6 +40,10 @@ interface MessageListState { minified: boolean; } +const PREF_REPLY_TYPE = 'core.sending.defaultReplyType'; +const PREF_RESTRICT_WIDTH = 'core.reading.restrictMaxWidth'; +const PREF_DESCENDING_ORDER = 'core.reading.descendingOrderMessageList'; + class MessageList extends React.Component<{}, MessageListState> { static displayName = 'MessageList'; static containerStyles = { @@ -172,7 +129,7 @@ class MessageList extends React.Component<{}, MessageListState> { // Returns either "reply" or "reply-all" _replyType() { - const defaultReplyType = AppEnv.config.get('core.sending.defaultReplyType'); + const defaultReplyType = AppEnv.config.get(PREF_REPLY_TYPE); const lastMessage = this._lastMessage(); if (!lastMessage) { return 'reply'; @@ -232,7 +189,7 @@ class MessageList extends React.Component<{}, MessageListState> { const hasReplyArea = mostRecentMessage && !mostRecentMessage.draft; // Invert the message list if the descending option is set - if (AppEnv.config.get('core.reading.descendingOrderMessageList')) { + if (AppEnv.config.get(PREF_DESCENDING_ORDER)) { messages = messages.reverse(); } @@ -396,61 +353,13 @@ class MessageList extends React.Component<{}, MessageListState> { thread={this.state.currentThread} />
- {this._renderIcons()} -
- ); - } - - _renderIcons() { - return ( -
- {this._renderExpandToggle()} -
- -
- {this._renderPopoutToggle()} -
- ); - } - - _renderExpandToggle() { - if (!this.state.canCollapse) { - return ; - } - - return ( -
- -
- ); - } - - _renderPopoutToggle() { - if (AppEnv.isThreadWindow()) { - return ( -
- -
- ); - } - return ( -
-
); @@ -500,15 +409,17 @@ class MessageList extends React.Component<{}, MessageListState> { ready: !this.state.loading, }); - const messageListClass = classNames({ - 'message-list': true, - 'height-fix': SearchableComponentStore.searchTerm !== null, - }); - return ( -
+
{
diff --git a/app/internal_packages/message-list/lib/subject-line-icons.tsx b/app/internal_packages/message-list/lib/subject-line-icons.tsx new file mode 100644 index 000000000..200ade0af --- /dev/null +++ b/app/internal_packages/message-list/lib/subject-line-icons.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { RetinaImg } from 'mailspring-component-kit'; +import { localized } from 'mailspring-exports'; + +interface SubjectLineIconsProps { + canCollapse: boolean; + hasCollapsedItems: boolean; + + onPrint: () => void; + onPopIn: () => void; + onPopOut: () => void; + onToggleAllExpanded: () => void; +} + +export const SubjectLineIcons: React.FunctionComponent = props => ( +
+ {props.canCollapse && ( +
+ +
+ )} +
+ +
+ {AppEnv.isThreadWindow() ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+); diff --git a/app/internal_packages/message-list/styles/message-list.less b/app/internal_packages/message-list/styles/message-list.less index e896865ef..7afdf5e55 100644 --- a/app/internal_packages/message-list/styles/message-list.less +++ b/app/internal_packages/message-list/styles/message-list.less @@ -136,6 +136,30 @@ body.platform-win32 { padding: 0; order: 2; + &.restrict-width { + .message-subject-wrap { + max-width: @message-max-width; + } + .message-list-headers { + max-width: @message-max-width; + } + .message-item-wrap { + max-width: @message-max-width; + } + .msg-lines { + max-width: @message-max-width; + } + .message-item-area { + max-width: @message-max-width; + } + .footer-reply-area-wrap { + max-width: @message-max-width; + } + .footer-reply-area { + max-width: @message-max-width; + } + } + search-match, .search-match { background: @text-color-search-match; @@ -179,7 +203,6 @@ body.platform-win32 { } .message-subject-wrap { - max-width: @message-max-width; margin: 5px auto 10px auto; -webkit-user-select: text; line-height: @font-size-large * 1.8; @@ -215,10 +238,11 @@ body.platform-win32 { .thread-injected-mail-labels { vertical-align: top; } + .message-list-headers { margin: 0 auto; width: 100%; - max-width: @message-max-width; + display: block; .participants { @@ -250,7 +274,7 @@ body.platform-win32 { .message-item-wrap { transition: height 0.1s; position: relative; - max-width: @message-max-width; + margin: 0 auto; .message-item-white-wrap { @@ -355,7 +379,6 @@ body.platform-win32 { } } .msg-lines { - max-width: @message-max-width; margin: 0 auto; width: 100%; margin-top: -13px; @@ -479,7 +502,7 @@ body.platform-win32 { .message-item-area { width: 100%; - max-width: @message-max-width; + margin: 0 auto; padding: 0 20px @spacing-standard 20px; @@ -520,7 +543,6 @@ body.platform-win32 { .footer-reply-area-wrap { overflow: hidden; - max-width: @message-max-width; margin: -3px auto 10px auto; position: relative; @@ -543,7 +565,7 @@ body.platform-win32 { .footer-reply-area { width: 100%; - max-width: @message-max-width; + margin: 0 auto; padding: 12px @spacing-standard * 1.5; } diff --git a/app/src/config-schema.ts b/app/src/config-schema.ts index e2430ed96..024be8912 100644 --- a/app/src/config-schema.ts +++ b/app/src/config-schema.ts @@ -114,6 +114,11 @@ export default { default: true, title: localized('Automatically load images in viewed messages'), }, + restrictMaxWidth: { + type: 'boolean', + default: false, + title: localized('Restrict width of messages to maximize readability'), + }, backspaceDelete: { type: 'boolean', default: false, diff --git a/app/static/email-frame.less b/app/static/email-frame.less index 86907aa2c..344ee40b6 100644 --- a/app/static/email-frame.less +++ b/app/static/email-frame.less @@ -85,8 +85,10 @@ body { padding: 0; margin: auto; - max-width: 840px; -webkit-font-smoothing: antialiased; + &.restrict-width { + max-width: 840px; + } } a {