diff --git a/internal_packages/composer/lib/contenteditable-component.cjsx b/internal_packages/composer/lib/contenteditable-component.cjsx index 2eac75315..85c4fc55f 100644 --- a/internal_packages/composer/lib/contenteditable-component.cjsx +++ b/internal_packages/composer/lib/contenteditable-component.cjsx @@ -106,9 +106,17 @@ class ContenteditableComponent extends React.Component onInput={@_onInput} onKeyDown={@_onKeyDown} dangerouslySetInnerHTML={@_dangerouslySetInnerHTML()}> - + {@_renderQuotedTextControl()} + _renderQuotedTextControl: -> + if QuotedHTMLParser.hasQuotedHTML(@props.html) + text = if @props.mode?.showQuotedText then "Hide" else "Show" + + •••{text} previous + + else return null + focus: => @_editableNode().focus() @@ -1096,7 +1104,5 @@ class ContenteditableComponent extends React.Component _quotedTextClasses: => classNames "quoted-text-control": true - "no-quoted-text": not QuotedHTMLParser.hasQuotedHTML(@props.html) - "show-quoted-text": @props.mode?.showQuotedText module.exports = ContenteditableComponent diff --git a/internal_packages/composer/spec/composer-view-spec.cjsx b/internal_packages/composer/spec/composer-view-spec.cjsx index c8f5f167a..4dcb15e36 100644 --- a/internal_packages/composer/spec/composer-view-spec.cjsx +++ b/internal_packages/composer/spec/composer-view-spec.cjsx @@ -107,9 +107,11 @@ useDraft = (draftAttributes={}) -> @draft = new Message _.extend({draft: true, body: ""}, draftAttributes) draft = @draft proxy = draftStoreProxyStub(DRAFT_CLIENT_ID, @draft) + @proxy = proxy spyOn(ComposerView.prototype, "componentWillMount").andCallFake -> + # NOTE: This is called in the context of the component. @_prepareForDraft(DRAFT_CLIENT_ID) @_setupSession(proxy) @@ -118,8 +120,7 @@ useDraft = (draftAttributes={}) -> # `componentWillMount`, we manually call sessionForClientId to make this # part of the test synchronous. We need to make the `then` block of the # sessionForClientId do nothing so `_setupSession` is not called twice! - spyOn(DraftStore, "sessionForClientId").andCallFake -> - then: -> + spyOn(DraftStore, "sessionForClientId").andCallFake -> then: -> useFullDraft = -> useDraft.call @, @@ -141,6 +142,76 @@ describe "populated composer", -> @isSending = {state: false} spyOn(DraftStore, "isSendingDraft").andCallFake => @isSending.state + describe "when sending a new message", -> + it 'makes a request with the message contents', -> + useDraft.call @ + makeComposer.call @ + editableNode = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithAttr(@composer, 'contentEditable')) + spyOn(@proxy.changes, "add") + editableNode.innerHTML = "Hello world" + ReactTestUtils.Simulate.input(editableNode) + expect(@proxy.changes.add).toHaveBeenCalled() + expect(@proxy.changes.add.calls.length).toBe 1 + body = @proxy.changes.add.calls[0].args[0].body + expect(body).toBe "Hello world" + + describe "when sending a reply-to message", -> + beforeEach -> + @replyBody = """
On Sep 3 2015, at 12:14 pm, Evan Morikawa <evan@evanmorikawa.com> wrote:
This is a test!
""" + + useDraft.call @, + from: [u1] + to: [u2] + subject: "Test Reply Message 1" + body: @replyBody + + makeComposer.call @ + @editableNode = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithAttr(@composer, 'contentEditable')) + spyOn(@proxy.changes, "add") + + it 'begins with the replying message collapsed', -> + expect(@editableNode.innerHTML).toBe "" + + it 'saves the full new body, plus quoted text', -> + @editableNode.innerHTML = "Hello world" + ReactTestUtils.Simulate.input(@editableNode) + expect(@proxy.changes.add).toHaveBeenCalled() + expect(@proxy.changes.add.calls.length).toBe 1 + body = @proxy.changes.add.calls[0].args[0].body + expect(body).toBe """Hello world#{@replyBody}""" + + describe "when sending a forwarded message message", -> + beforeEach -> + @fwdBody = """

+ Begin forwarded message: +

+ From: Evan Morikawa <evan@evanmorikawa.com>
Subject: Test Forward Message 1
Date: Sep 3 2015, at 12:14 pm
To: Evan Morikawa <evan@nylas.com> +

+ + This is a test! +
""" + + useDraft.call @, + from: [u1] + to: [u2] + subject: "Fwd: Test Forward Message 1" + body: @fwdBody + + makeComposer.call @ + @editableNode = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithAttr(@composer, 'contentEditable')) + spyOn(@proxy.changes, "add") + + it 'begins with the forwarded message expanded', -> + expect(@editableNode.innerHTML).toBe @fwdBody + + it 'saves the full new body, plus forwarded text', -> + @editableNode.innerHTML = "Hello world#{@fwdBody}" + ReactTestUtils.Simulate.input(@editableNode) + expect(@proxy.changes.add).toHaveBeenCalled() + expect(@proxy.changes.add.calls.length).toBe 1 + body = @proxy.changes.add.calls[0].args[0].body + expect(body).toBe """Hello world#{@fwdBody}""" + describe "When displaying info from a draft", -> beforeEach -> useFullDraft.apply(@) diff --git a/internal_packages/composer/spec/contenteditable-quoted-text-spec.cjsx b/internal_packages/composer/spec/contenteditable-quoted-text-spec.cjsx index 8c5177b95..bb98cf1b2 100644 --- a/internal_packages/composer/spec/contenteditable-quoted-text-spec.cjsx +++ b/internal_packages/composer/spec/contenteditable-quoted-text-spec.cjsx @@ -11,106 +11,112 @@ ContenteditableComponent = require "../lib/contenteditable-component", describe "ContenteditableComponent", -> beforeEach -> @onChange = jasmine.createSpy('onChange') - @html = 'Test HTML
' - @component = ReactTestUtils.renderIntoDocument( - - ) + @htmlNoQuote = 'Test HTML
' + @htmlWithQuote = 'Test HTML
QUOTE
' - @htmlWithQuote = 'Test HTML

QUOTE
' - @componentWithQuote = ReactTestUtils.renderIntoDocument( - - ) + # Must be called with the test's scope + setHTML = (newHTML) -> + @$contentEditable.innerHTML = newHTML + ReactTestUtils.Simulate.input(@$contentEditable, {target: {value: newHTML}}) - describe "quoted-text-control", -> - it "should be rendered", -> - expect(ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'quoted-text-control')).toBeDefined() + describe "quoted-text-control toggle button", -> - it "should be visible if the html contains quoted text", -> - @toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@componentWithQuote, 'quoted-text-control') - expect(@toggle.props.className.indexOf('no-quoted-text') >= 0).toBe(false) - - it "should be have `show-quoted-text` if showQuotedText is true", -> - @componentWithQuote = ReactTestUtils.renderIntoDocument( - - ) - @toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@componentWithQuote, 'quoted-text-control') - expect(@toggle.props.className.indexOf('show-quoted-text') >= 0).toBe(true) - - it "should not have `show-quoted-text` if showQuotedText is false", -> - @componentWithQuote.setState(showQuotedText: false) - @toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@componentWithQuote, 'quoted-text-control') - expect(@toggle.props.className.indexOf('show-quoted-text') >= 0).toBe(false) - - it "should be hidden otherwise", -> - @toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'quoted-text-control') - expect(@toggle.props.className.indexOf('no-quoted-text') >= 0).toBe(true) - - describe "when showQuotedText is false", -> - it "should not display quoted text", -> - @editDiv = ReactTestUtils.findRenderedDOMComponentWithAttr(@componentWithQuote, 'contentEditable') - expect(React.findDOMNode(@editDiv).innerHTML).toEqual @html - - describe "when showQuotedText is true", -> + describe "when there's no quoted text", -> beforeEach -> - @componentWithQuote = ReactTestUtils.renderIntoDocument( + @contentEditable = ReactTestUtils.renderIntoDocument( + ) + @$contentEditable = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithAttr(@contentEditable, 'contentEditable')) + + it 'should not display any quoted text', -> + expect(@$contentEditable.innerHTML).toBe @htmlNoQuote + + it "allows the text to update", -> + textToAdd = "MORE TEXT!" + expect(@$contentEditable.innerHTML).toBe @htmlNoQuote + setHTML.call(@, textToAdd + @htmlNoQuote) + ev = @onChange.mostRecentCall.args[0] + expect(ev.target.value).toEqual(textToAdd + @htmlNoQuote) + + it 'should not render the quoted-text-control toggle', -> + toggles = ReactTestUtils.scryRenderedDOMComponentsWithClass(@contentEditable, 'quoted-text-control') + expect(toggles.length).toBe 0 + + + describe 'when showQuotedText is true', -> + beforeEach -> + @contentEditable = ReactTestUtils.renderIntoDocument( - ) + mode={showQuotedText: true}/>) + @$contentEditable = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithAttr(@contentEditable, 'contentEditable')) - it "should display all the HTML", -> - @componentWithQuote.setState(showQuotedText: true) - @editDiv = ReactTestUtils.findRenderedDOMComponentWithAttr(@componentWithQuote, 'contentEditable') - expect(React.findDOMNode(@editDiv).innerHTML.indexOf('gmail_quote') >= 0).toBe(true) + it 'should display the quoted text', -> + expect(@$contentEditable.innerHTML).toBe @htmlWithQuote - describe "showQuotedText", -> - it "should default to false", -> - expect(@component.props.mode?.showQuotedText).toBeUndefined() + it "should call `props.onChange` with the entire HTML string", -> + textToAdd = "MORE TEXT!" + expect(@$contentEditable.innerHTML).toBe @htmlWithQuote + setHTML.call(@, textToAdd + @htmlWithQuote) + ev = @onChange.mostRecentCall.args[0] + expect(ev.target.value).toEqual(textToAdd + @htmlWithQuote) - describe "when the html is changed", -> - beforeEach -> - @changedHtmlWithoutQuote = 'Changed NEW 1 HTML

' - @changedHtmlWithQuote = 'Changed NEW 1 HTML

QUOTE
' + it "should allow the quoted text to be changed", -> + newText = 'Test NEW 1 HTML
QUOTE CHANGED!!!
' + expect(@$contentEditable.innerHTML).toBe @htmlWithQuote + setHTML.call(@, newText) + ev = @onChange.mostRecentCall.args[0] + expect(ev.target.value).toEqual(newText) - @performEdit = (newHTML, component = @componentWithQuote) => - editDiv = ReactTestUtils.findRenderedDOMComponentWithAttr(component, 'contentEditable') - React.findDOMNode(editDiv).innerHTML = newHTML - ReactTestUtils.Simulate.input(editDiv, {target: {value: newHTML}}) - - describe "when showQuotedText is true", -> + describe 'quoted text control toggle button', -> beforeEach -> - @componentWithQuote = ReactTestUtils.renderIntoDocument( - - ) + @toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@contentEditable, 'quoted-text-control') - it "should call `props.onChange` with the entire HTML string", -> - @componentWithQuote.setState(showQuotedText: true) - @performEdit(@changedHtmlWithQuote) - ev = @onChange.mostRecentCall.args[0] - expect(ev.target.value).toEqual(@changedHtmlWithQuote) + it 'should be rendered', -> + expect(@toggle).toBeDefined() - it "should allow the quoted text to be changed", -> - changed = 'Test NEW 1 HTML
QUOTE CHANGED!!!
' - @componentWithQuote.setState(showQuotedText: true) - @performEdit(changed) - ev = @onChange.mostRecentCall.args[0] - expect(ev.target.value).toEqual(changed) + it 'prompts to hide the quote', -> + expect(React.findDOMNode(@toggle).textContent).toEqual "•••Hide previous" - describe "when showQuotedText is false", -> - it "should let you change the text, and then append the quoted text part to the end before firing `onChange`", -> - @componentWithQuote.setState(showQuotedText: false) - @performEdit(@changedHtmlWithoutQuote) - ev = @onChange.mostRecentCall.args[0] - withQuote = "#{@changedHtmlWithQuote}" - expect(ev.target.value).toEqual(withQuote) + describe 'when showQuotedText is false', -> + beforeEach -> + @contentEditable = ReactTestUtils.renderIntoDocument( + ) + @$contentEditable = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithAttr(@contentEditable, 'contentEditable')) - it "should work if the component does not contain quoted text", -> - changed = 'Hallooo! NEW 1 HTML HTML HTML
' - @component.setState(showQuotedText: true) - @performEdit(changed, @component) - ev = @onChange.mostRecentCall.args[0] - expect(ev.target.value).toEqual(changed) + # The quoted text dom parser wraps stuff inertly in body tags + wrapBody = (html) -> "#{html}" + + it 'should not display any quoted text', -> + expect(@$contentEditable.innerHTML).toBe @htmlNoQuote + + it "should let you change the text, and then append the quoted text part to the end before firing `onChange`", -> + textToAdd = "MORE TEXT!" + expect(@$contentEditable.innerHTML).toBe @htmlNoQuote + setHTML.call(@, textToAdd + @htmlNoQuote) + ev = @onChange.mostRecentCall.args[0] + # Note that we expect the version WITH a quote while setting the + # version withOUT a quote. + expect(ev.target.value).toEqual(wrapBody(textToAdd + @htmlWithQuote)) + + it "should let you add more html that looks like quoted text, and still properly appends the old quoted text", -> + textToAdd = "Yo
I'm a fake quote
" + expect(@$contentEditable.innerHTML).toBe @htmlNoQuote + setHTML.call(@, textToAdd + @htmlNoQuote) + ev = @onChange.mostRecentCall.args[0] + # Note that we expect the version WITH a quote while setting the + # version withOUT a quote. + expect(ev.target.value).toEqual(wrapBody(textToAdd + @htmlWithQuote)) + + describe 'quoted text control toggle button', -> + beforeEach -> + @toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@contentEditable, 'quoted-text-control') + + it 'should be rendered', -> + expect(@toggle).toBeDefined() + + it 'prompts to hide the quote', -> + expect(React.findDOMNode(@toggle).textContent).toEqual "•••Show previous" diff --git a/internal_packages/message-list/lib/email-frame.cjsx b/internal_packages/message-list/lib/email-frame.cjsx index 1f6438749..98d8cff9f 100644 --- a/internal_packages/message-list/lib/email-frame.cjsx +++ b/internal_packages/message-list/lib/email-frame.cjsx @@ -35,7 +35,6 @@ class EmailFrame extends React.Component !_.isEqual(newProps, @props) _writeContent: => - wrapperClass = if @props.showQuotedText then "show-quoted-text" else "" doc = React.findDOMNode(@).contentDocument doc.open() @@ -50,7 +49,7 @@ class EmailFrame extends React.Component EmailFixingStyles = EmailFixingStyles.replace(/.ignore-in-parent-frame/g, '') if (EmailFixingStyles) doc.write("") - doc.write("
#{@_emailContent()}
") + doc.write("
#{@_emailContent()}
") doc.close() # Notify the EventedIFrame that we've replaced it's document (with `open`) @@ -80,7 +79,7 @@ class EmailFrame extends React.Component if @props.showQuotedText @props.content else - QuotedHTMLParser.hideQuotedHTML(@props.content) + QuotedHTMLParser.removeQuotedHTML(@props.content, keepIfWholeBodyIsQuote: true) module.exports = EmailFrame diff --git a/internal_packages/message-list/lib/message-item.cjsx b/internal_packages/message-list/lib/message-item.cjsx index 0058fe227..164d44c04 100644 --- a/internal_packages/message-list/lib/message-item.cjsx +++ b/internal_packages/message-list/lib/message-item.cjsx @@ -80,13 +80,21 @@ class MessageItem extends React.Component
{@_renderHeader()} - + {@_renderQuotedTextControl()} {@_renderEvents()} {@_renderAttachments()}
+ _renderQuotedTextControl: -> + if QuotedHTMLParser.hasQuotedHTML(@props.message.body) + text = if @state.showQuotedText then "Hide" else "Show" + + •••{text} previous + + else return null + _renderHeader: => classes = classNames "message-header": true @@ -168,11 +176,6 @@ class MessageItem extends React.Component else
- _quotedTextClasses: => classNames - "quoted-text-control": true - 'no-quoted-text': not QuotedHTMLParser.hasQuotedHTML(@props.message.body) - 'show-quoted-text': @state.showQuotedText - _renderHeaderSideItems: -> styles = position: "absolute" diff --git a/internal_packages/message-list/spec/message-item-spec.cjsx b/internal_packages/message-list/spec/message-item-spec.cjsx index 842f5228c..beccdc29d 100644 --- a/internal_packages/message-list/spec/message-item-spec.cjsx +++ b/internal_packages/message-list/spec/message-item-spec.cjsx @@ -246,21 +246,33 @@ describe "MessageItem", -> describe "showQuotedText", -> + it "should be initialized to false", -> @createComponent() expect(@component.state.showQuotedText).toBe(false) - it "should show the `show quoted text` toggle in the off state", -> + it "shouldn't render the quoted text control if there's no quoted text", -> + @message.body = "no quotes here!" @createComponent() - toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'quoted-text-control') - expect(React.findDOMNode(toggle).className.indexOf('show-quoted-text')).toBe(-1) + toggles = ReactTestUtils.scryRenderedDOMComponentsWithClass(@component, 'quoted-text-control') + expect(toggles.length).toBe 0 - it "should have the `no quoted text` class if there is no quoted text in the message", -> - spyOn(QuotedHTMLParser, 'hasQuotedHTML').andReturn false + describe 'quoted text control toggle button', -> + beforeEach -> + @message.body = """ + Message +
+ Quoted message +
+ """ + @createComponent() + @toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'quoted-text-control') - @createComponent() - toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'quoted-text-control') - expect(React.findDOMNode(toggle).className.indexOf('no-quoted-text')).not.toBe(-1) + it 'should be rendered', -> + expect(@toggle).toBeDefined() + + it 'prompts to hide the quote', -> + expect(React.findDOMNode(@toggle).textContent).toEqual "•••Show previous" it "should be initialized to true if the message contains `Forwarded`...", -> @message.body = """ @@ -294,14 +306,26 @@ describe "MessageItem", -> @createComponent() expect(@component.state.showQuotedText).toBe(false) - describe "when true", -> + describe "when showQuotedText is true", -> beforeEach -> + @message.body = """ + Message +
+ Quoted message +
+ """ @createComponent() @component.setState(showQuotedText: true) - it "should show the `show quoted text` toggle in the on state", -> - toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'quoted-text-control') - expect(React.findDOMNode(toggle).className.indexOf('show-quoted-text') > 0).toBe(true) + describe 'quoted text control toggle button', -> + beforeEach -> + @toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'quoted-text-control') + + it 'should be rendered', -> + expect(@toggle).toBeDefined() + + it 'prompts to hide the quote', -> + expect(React.findDOMNode(@toggle).textContent).toEqual "•••Hide previous" it "should pass the value into the EmailFrame", -> frame = ReactTestUtils.findRenderedComponentWithType(@component, EmailFrameStub) diff --git a/spec-nylas/quoted-html-parser-spec.coffee b/spec-nylas/quoted-html-parser-spec.coffee index 3851c8039..aa7a697a7 100644 --- a/spec-nylas/quoted-html-parser-spec.coffee +++ b/spec-nylas/quoted-html-parser-spec.coffee @@ -12,8 +12,8 @@ describe "QuotedHTMLParser", -> hideQuotedHTML = (fname) -> return QuotedHTMLParser.hideQuotedHTML(readFile(fname)) - removeQuotedHTML = (fname) -> - return QuotedHTMLParser.removeQuotedHTML(readFile(fname)) + removeQuotedHTML = (fname, opts={}) -> + return QuotedHTMLParser.removeQuotedHTML(readFile(fname), opts) numQuotes = (html) -> re = new RegExp(QuotedHTMLParser.annotationClass, 'g') @@ -21,7 +21,8 @@ describe "QuotedHTMLParser", -> [1..16].forEach (n) -> it "properly parses email_#{n}", -> - expect(removeQuotedHTML("email_#{n}.html")).toEqual readFile("email_#{n}_stripped.html") + opts = keepIfWholeBodyIsQuote: true + expect(removeQuotedHTML("email_#{n}.html", opts)).toEqual readFile("email_#{n}_stripped.html") describe 'manual quote detection tests', -> @@ -258,7 +259,8 @@ describe "QuotedHTMLParser", -> it 'works with these manual test cases', -> for {before, after} in tests - test = clean(QuotedHTMLParser.removeQuotedHTML(before)) + opts = keepIfWholeBodyIsQuote: true + test = clean(QuotedHTMLParser.removeQuotedHTML(before, opts)) expect(test).toEqual clean(after) it 'removes all trailing
tags except one', -> diff --git a/src/services/quoted-html-parser.coffee b/src/services/quoted-html-parser.coffee index 98b04357e..c3ebe5930 100644 --- a/src/services/quoted-html-parser.coffee +++ b/src/services/quoted-html-parser.coffee @@ -8,20 +8,19 @@ class QuotedHTMLParser # Given an html string, it will add the `annotationClass` to the DOM # element - hideQuotedHTML: (html) -> + hideQuotedHTML: (html, {keepIfWholeBodyIsQuote}={}) -> doc = @_parseHTML(html) quoteElements = @_findQuoteLikeElements(doc) - if not @_wholeBodyIsQuote(doc, quoteElements) + if keepIfWholeBodyIsQuote and @_wholeBodyIsQuote(doc, quoteElements) + return doc.children[0].innerHTML + else @_annotateElements(quoteElements) - return doc.children[0].innerHTML + return doc.children[0].innerHTML hasQuotedHTML: (html) -> doc = @_parseHTML(html) quoteElements = @_findQuoteLikeElements(doc) - if @_wholeBodyIsQuote(doc, quoteElements) - return false - else - return quoteElements.length > 0 + return quoteElements.length > 0 # Public: Removes quoted text from an HTML string # @@ -34,12 +33,17 @@ class QuotedHTMLParser # - `options` # - `includeInline` Defaults false. If true, inline quotes are removed # too + # - `keepIfWholeBodyIsQuote` Defaults false. If true, then it will + # check to see if the whole html body is a giant quote. If so, it will + # preserve it. # # Returns HTML without quoted text removeQuotedHTML: (html, options={}) -> doc = @_parseHTML(html) quoteElements = @_findQuoteLikeElements(doc, options) - if not @_wholeBodyIsQuote(doc, quoteElements) + if options.keepIfWholeBodyIsQuote and @_wholeBodyIsQuote(doc, quoteElements) + return doc.children[0].innerHTML + else DOMUtils.removeElements(quoteElements, options) childNodes = doc.body.childNodes @@ -53,14 +57,13 @@ class QuotedHTMLParser break DOMUtils.removeElements(extraTailBrTags) - return doc.children[0].innerHTML + return doc.children[0].innerHTML appendQuotedHTML: (htmlWithoutQuotes, originalHTML) -> doc = @_parseHTML(originalHTML) quoteElements = @_findQuoteLikeElements(doc) - if not @_wholeBodyIsQuote(doc, quoteElements) - doc = @_parseHTML(htmlWithoutQuotes) - doc.body.appendChild(node) for node in quoteElements + doc = @_parseHTML(htmlWithoutQuotes) + doc.body.appendChild(node) for node in quoteElements return doc.children[0].innerHTML restoreAnnotatedHTML: (html) -> diff --git a/static/components/extra.less b/static/components/extra.less index 9e863cfac..5d246d92a 100644 --- a/static/components/extra.less +++ b/static/components/extra.less @@ -10,29 +10,19 @@ font-weight: 600; font-size: @font-size-smaller; - &.no-quoted-text { - display:none; - } - &:hover { cursor: pointer; color: @text-color-subtle; border: 1px solid fade(@text-color-subtle, 35%); } - &:before { - content:'\2022\2022\2022'; + .dots { + display: inline-block; font-size: @font-size-smaller * 0.8; top:-1px; position:relative; padding-right:8px; } - &.show-quoted-text:after { - content:'Hide previous'; - } - &:after { - content:'Show previous'; - } } .mail-label {