mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-06 04:35:30 +08:00
feat(print): Add functionality to print currently focused thread
Summary: - Adds button inside the message list to print the thread - Adds cmdctrl-p binding to print thread - Adds new action and new internal_package to listen to this action. - Creates a standalone browser window with current thread html, and removes all collapsed messsages from the print view Test Plan: - Manual Reviewers: evan, bengotow Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2310
This commit is contained in:
parent
d5bf5e47b7
commit
931a93af4e
14 changed files with 281 additions and 3 deletions
|
@ -92,6 +92,7 @@ class MessageList extends React.Component
|
|||
'application:reply': => @_createReplyOrUpdateExistingDraft('reply')
|
||||
'application:reply-all': => @_createReplyOrUpdateExistingDraft('reply-all')
|
||||
'application:forward': => @_onForward()
|
||||
'application:print-thread': => @_onPrintThread()
|
||||
'core:messages-page-up': => @_onScrollByPage(-1)
|
||||
'core:messages-page-down': => @_onScrollByPage(1)
|
||||
|
||||
|
@ -230,7 +231,10 @@ class MessageList extends React.Component
|
|||
"Collapse All"
|
||||
<div className="message-icons-wrap">
|
||||
<div onClick={@_onToggleAllMessagesExpanded}>
|
||||
<RetinaImg name="expand.png" fallback="expand.png" title={expandTitle}/>
|
||||
<RetinaImg name="expand.png" fallback="expand.png" title={expandTitle} mode={RetinaImg.Mode.ContentPreserve}/>
|
||||
</div>
|
||||
<div onClick={@_onPrintThread}>
|
||||
<RetinaImg name="print.png" fallback="print.png" title="Print Thread" mode={RetinaImg.Mode.ContentPreserve}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -265,6 +269,10 @@ class MessageList extends React.Component
|
|||
_onToggleAllMessagesExpanded: ->
|
||||
Actions.toggleAllMessagesExpanded()
|
||||
|
||||
_onPrintThread: =>
|
||||
node = React.findDOMNode(@)
|
||||
Actions.printThread(@state.currentThread, node.innerHTML)
|
||||
|
||||
_onRemoveLabel: (label) =>
|
||||
task = new ChangeLabelsTask(thread: @state.currentThread, labelsToRemove: [label])
|
||||
Actions.queueTask(task)
|
||||
|
|
BIN
internal_packages/print/assets/nylas-print-logo.png
Normal file
BIN
internal_packages/print/assets/nylas-print-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
14
internal_packages/print/lib/main.es6
Normal file
14
internal_packages/print/lib/main.es6
Normal file
|
@ -0,0 +1,14 @@
|
|||
import Printer from './printer';
|
||||
|
||||
let printer = null;
|
||||
export function activate() {
|
||||
printer = new Printer();
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
if (printer) printer.deactivate();
|
||||
}
|
||||
|
||||
export function serialize() {
|
||||
|
||||
}
|
68
internal_packages/print/lib/print-window.es6
Normal file
68
internal_packages/print/lib/print-window.es6
Normal file
|
@ -0,0 +1,68 @@
|
|||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import {remote} from 'electron';
|
||||
|
||||
const {app, BrowserWindow} = remote;
|
||||
|
||||
export default class PrintWindow {
|
||||
|
||||
constructor({subject, account, participants, styleTags, htmlContent, printMessages}) {
|
||||
// This script will create the print prompt when loaded. We can also call
|
||||
// print directly from this process, but inside print.js we can make sure to
|
||||
// call window.print() after we've cleaned up the dom for printing
|
||||
const scriptPath = path.join(__dirname, '..', 'static', 'print.js');
|
||||
const stylesPath = path.join(__dirname, '..', 'static', 'print-styles.css');
|
||||
const imgPath = path.join(__dirname, '..', 'assets', 'nylas-print-logo.png');
|
||||
const participantsHtml = participants.map((part) => {
|
||||
return (`<li class="participant"><span>${part.name} <${part.email}></span></li>`);
|
||||
}).join('');
|
||||
|
||||
const content = (`
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
${styleTags}
|
||||
<link rel="stylesheet" type="text/css" href="${stylesPath}">
|
||||
</head>
|
||||
<body>
|
||||
<div id="print-header">
|
||||
<div class="logo-wrapper">
|
||||
<img src="${imgPath}" alt="nylas-logo"/>
|
||||
<span class="account">${account.name} <${account.email}></span>
|
||||
</div>
|
||||
<h1>${subject}</h1>
|
||||
<div class="participants">
|
||||
<ul>
|
||||
${participantsHtml}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
${htmlContent}
|
||||
<script type="text/javascript">
|
||||
window.printMessages = ${printMessages}
|
||||
</script>
|
||||
<script type="text/javascript" src="${scriptPath}"></script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
|
||||
this.tmpFile = path.join(app.getPath('temp'), 'print.html');
|
||||
this.browserWin = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
title: `Print - ${subject}`,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
},
|
||||
});
|
||||
fs.writeFileSync(this.tmpFile, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load our temp html file. Once the file is loaded it will run print.js, and
|
||||
* that script will pop out the print dialog.
|
||||
*/
|
||||
load() {
|
||||
this.browserWin.loadURL(`file://${this.tmpFile}`);
|
||||
}
|
||||
}
|
42
internal_packages/print/lib/printer.es6
Normal file
42
internal_packages/print/lib/printer.es6
Normal file
|
@ -0,0 +1,42 @@
|
|||
import {AccountStore, Actions} from 'nylas-exports';
|
||||
import PrintWindow from './print-window';
|
||||
|
||||
class Printer {
|
||||
|
||||
constructor() {
|
||||
this.unsub = Actions.printThread.listen(this._printThread);
|
||||
}
|
||||
|
||||
_printThread(thread, htmlContent) {
|
||||
if (!thread) throw new Error('Printing: No thread active!');
|
||||
|
||||
// Get the <nylas-styles> tag present in the document
|
||||
const styleTag = document.getElementsByTagName('nylas-styles')[0];
|
||||
// These iframes should correspond to the message iframes when a thread is
|
||||
// focused
|
||||
const iframes = document.getElementsByTagName('iframe');
|
||||
// Grab the html inside the iframes
|
||||
const messagesHtml = [].slice.call(iframes).map((iframe)=> {
|
||||
return iframe.contentDocument.documentElement.innerHTML;
|
||||
});
|
||||
|
||||
const win = new PrintWindow({
|
||||
subject: thread.subject,
|
||||
account: {
|
||||
name: AccountStore.current().name,
|
||||
email: AccountStore.current().emailAddress,
|
||||
},
|
||||
participants: thread.participants,
|
||||
styleTags: styleTag.innerHTML,
|
||||
htmlContent,
|
||||
printMessages: JSON.stringify(messagesHtml),
|
||||
});
|
||||
win.load();
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
this.unsub();
|
||||
}
|
||||
}
|
||||
|
||||
export default Printer;
|
11
internal_packages/print/package.json
Normal file
11
internal_packages/print/package.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "print",
|
||||
"version": "0.1.0",
|
||||
"main": "./lib/main",
|
||||
"description": "Print",
|
||||
"license": "GPLv3",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"nylas": "*"
|
||||
}
|
||||
}
|
74
internal_packages/print/static/print-styles.css
Normal file
74
internal_packages/print/static/print-styles.css
Normal file
|
@ -0,0 +1,74 @@
|
|||
body {
|
||||
overflow: auto !important;
|
||||
}
|
||||
#print-header {
|
||||
padding: 15px 20px 0 20px;
|
||||
}
|
||||
#print-header img {
|
||||
zoom: 0.5;
|
||||
}
|
||||
#print-header .logo-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: "Nylas-Pro", "Helvetica", "Lucidia Grande", sans-serif !important;
|
||||
}
|
||||
#print-header h1 {
|
||||
font-size: 1.5em !important;
|
||||
font-family: "Nylas-Pro", "Helvetica", "Lucidia Grande", sans-serif !important;
|
||||
}
|
||||
#print-header .account {
|
||||
margin-left: auto;
|
||||
font-size: 0.8em !important;
|
||||
}
|
||||
#print-header .participant {
|
||||
font-size: 0.7em;
|
||||
font-family: "Nylas-Pro", "Helvetica", "Lucidia Grande", sans-serif !important;
|
||||
}
|
||||
|
||||
/* Elements to hide */
|
||||
.message-subject-wrap {
|
||||
display: none !important;
|
||||
}
|
||||
.minified-bundle,.headers,.scrollbar-track,.message-icons-wrap,.header-toggle-control {
|
||||
display: none !important;
|
||||
}
|
||||
.message-actions-wrap {
|
||||
display: none;
|
||||
}
|
||||
.collapsed.message-item-wrap,.draft.message-item-wrap {
|
||||
display: none !important;
|
||||
}
|
||||
.message-item-area>div {
|
||||
display: none !important;
|
||||
}
|
||||
.quoted-text-control, .footer-reply-area-wrap {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only print {
|
||||
body,#message-list,.message-item-wrap,.message-item-white-wrap,
|
||||
.message-item-area,.inbox-html-wrapper {
|
||||
display: block !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
#message-list {
|
||||
min-height: initial;
|
||||
}
|
||||
#print-header {
|
||||
padding: 0;
|
||||
}
|
||||
#print-header .account {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
.message-item-wrap {
|
||||
display: block;
|
||||
}
|
||||
.message-item-area>span {
|
||||
page-break-before: avoid;
|
||||
}
|
||||
.message-item-area>header {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
}
|
41
internal_packages/print/static/print.js
Normal file
41
internal_packages/print/static/print.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
(function() {
|
||||
function rebuildMessages(messageNodes, messages) {
|
||||
// Simply insert the message html inside the appropriate node
|
||||
for (var idx = 0; idx < messageNodes.length; idx++) {
|
||||
var msgNode = messageNodes[idx];
|
||||
var msgHtml = messages[idx];
|
||||
msgNode.innerHTML = msgHtml;
|
||||
}
|
||||
}
|
||||
|
||||
function removeClassFromNodes(nodeList, className) {
|
||||
for (var idx = 0; idx < nodeList.length; idx++) {
|
||||
var node = nodeList[idx];
|
||||
var re = new RegExp('\\b' + className + '\\b', 'g');
|
||||
node.className = node.className.replace(re, '');
|
||||
}
|
||||
}
|
||||
|
||||
function removeScrollClasses() {
|
||||
var scrollRegions = document.querySelectorAll('.scroll-region');
|
||||
var scrollContents = document.querySelectorAll('.scroll-region-content');
|
||||
var scrollContentInners = document.querySelectorAll('.scroll-region-content-inner');
|
||||
removeClassFromNodes(scrollRegions, 'scroll-region');
|
||||
removeClassFromNodes(scrollContents, 'scroll-region-content');
|
||||
removeClassFromNodes(scrollContentInners, 'scroll-region-content-inner');
|
||||
}
|
||||
|
||||
function print() {
|
||||
window.print();
|
||||
// Close this print window after selecting to print
|
||||
// This is really hackish but appears to be the only working solution
|
||||
setTimeout(window.close, 500);
|
||||
}
|
||||
|
||||
var messageNodes = document.querySelectorAll('.message-item-area>span');
|
||||
|
||||
removeScrollClasses();
|
||||
rebuildMessages(messageNodes, window.printMessages);
|
||||
// Give it a few ms before poppint out the print dialog
|
||||
setTimeout(print, 50);
|
||||
})();
|
|
@ -41,6 +41,7 @@
|
|||
### Core application commands. ###
|
||||
'cmdctrl-q' : 'application:quit'
|
||||
'cmdctrl-w' : 'window:close'
|
||||
'cmdctrl-p' : 'application:print-thread'
|
||||
|
||||
### Universal N1 commands. ###
|
||||
'enter' : 'core:focus-item'
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
{ label: 'New Message', command: 'application:new-message' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Close Window', command: 'window:close' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Print Current Thread', command: 'application:print-thread' }
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
submenu: [
|
||||
{ label: '&New Message', command: 'application:new-message' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences', command: 'application:open-preferences' }
|
||||
{ label: 'Add Account...', command: 'application:add-account' }
|
||||
{ label: 'Clos&e Window', command: 'window:close' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Print Current Thread', command: 'application:print-thread' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Quit', command: 'application:quit' }
|
||||
]
|
||||
}
|
||||
|
@ -21,6 +23,8 @@
|
|||
{ label: 'C&opy', command: 'core:copy' }
|
||||
{ label: '&Paste', command: 'core:paste' }
|
||||
{ label: 'Select &All', command: 'core:select-all' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences', command: 'application:open-preferences' }
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,8 @@
|
|||
{ type: 'separator' }
|
||||
{ label: 'Preferences', command: 'application:open-preferences' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Print Current Thread', command: 'application:print-thread' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'E&xit', command: 'application:quit' }
|
||||
]
|
||||
|
||||
|
|
|
@ -231,12 +231,23 @@ class Actions
|
|||
*Scope: Window*
|
||||
|
||||
```
|
||||
message = <Message>
|
||||
Actions.toggleAllMessagesExpanded()
|
||||
```
|
||||
###
|
||||
@toggleAllMessagesExpanded: ActionScopeWindow
|
||||
|
||||
###
|
||||
Public: Print the currently selected thread.
|
||||
|
||||
*Scope: Window*
|
||||
|
||||
```
|
||||
thread = <Thread>
|
||||
Actions.printThread(thread)
|
||||
```
|
||||
###
|
||||
@printThread: ActionScopeWindow
|
||||
|
||||
###
|
||||
Public: Create a new reply to the provided threadId and messageId and populate
|
||||
it with the body provided.
|
||||
|
|
BIN
static/images/message-list/print@2x.png
Normal file
BIN
static/images/message-list/print@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Loading…
Add table
Reference in a new issue