Implement attachment list navigation, selection and removal
parent
2ec51f96a3
commit
ca97953d23
1
mail.css
1
mail.css
|
@ -1668,6 +1668,7 @@ div
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose .headers .attach .attach-list .name
|
.compose .headers .attach .attach-list .name
|
||||||
|
|
82
mail.js
82
mail.js
|
@ -130,6 +130,7 @@ var DropDownBase = {
|
||||||
}
|
}
|
||||||
if (top + this.refs.dd.offsetHeight > wh)
|
if (top + this.refs.dd.offsetHeight > wh)
|
||||||
top = wh-this.refs.dd.offsetHeight;
|
top = wh-this.refs.dd.offsetHeight;
|
||||||
|
this.refs.dd.style.display = '';
|
||||||
this.setState({ visible: true, top: top, left: left, calloutLeft: calloutLeft });
|
this.setState({ visible: true, top: top, left: left, calloutLeft: calloutLeft });
|
||||||
this.refs.dd.focus();
|
this.refs.dd.focus();
|
||||||
this.onClose = onClose;
|
this.onClose = onClose;
|
||||||
|
@ -782,6 +783,78 @@ var ComposeWindow = React.createClass({
|
||||||
{
|
{
|
||||||
this.setState({ text: ev.target.value });
|
this.setState({ text: ev.target.value });
|
||||||
},
|
},
|
||||||
|
attachKeyDown: function(ev)
|
||||||
|
{
|
||||||
|
if (ev.keyCode == 46) // delete
|
||||||
|
{
|
||||||
|
for (var i = this.state.attachments.length-1; i >= 0; i--)
|
||||||
|
if (this.state.attachments[i].selected)
|
||||||
|
this.state.attachments.splice(i, 1);
|
||||||
|
this.setState({ attachments: this.state.attachments });
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
else if (ev.keyCode == 40 || ev.keyCode == 38) // down, up
|
||||||
|
{
|
||||||
|
var sel = null, dir = (ev.keyCode == 40 ? 1 : -1);
|
||||||
|
for (var i = 0; i < this.state.attachments.length; i++)
|
||||||
|
{
|
||||||
|
if (this.state.attachments[i].selected)
|
||||||
|
{
|
||||||
|
sel = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sel !== null && (sel+dir) >= 0 && (sel+dir) < this.state.attachments.length)
|
||||||
|
{
|
||||||
|
sel += dir;
|
||||||
|
for (var i = 0; i < this.state.attachments.length; i++)
|
||||||
|
this.state.attachments[i].selected = (i == sel);
|
||||||
|
this.setState({ attachments: this.state.attachments });
|
||||||
|
var item = this.refs['a'+sel];
|
||||||
|
if (item.offsetTop + item.offsetHeight > this.refs.scroll.scrollTop + this.refs.scroll.offsetHeight)
|
||||||
|
this.refs.scroll.scrollTop = item.offsetTop + item.offsetHeight - this.refs.scroll.offsetHeight;
|
||||||
|
else if (item.offsetTop < this.refs.scroll.scrollTop+this.refs.title.offsetHeight)
|
||||||
|
this.refs.scroll.scrollTop = item.offsetTop-this.refs.title.offsetHeight;
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault(); // prevent scroll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectAttachment: function(ev)
|
||||||
|
{
|
||||||
|
var t = ev.target;
|
||||||
|
while (t && !t.getAttribute('data-i'))
|
||||||
|
t = t.parentNode;
|
||||||
|
if (t)
|
||||||
|
{
|
||||||
|
var ns = parseInt(t.getAttribute('data-i'));
|
||||||
|
if (ev.shiftKey && this.lastSel)
|
||||||
|
{
|
||||||
|
if (ns < this.lastSel)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.state.attachments.length; i++)
|
||||||
|
this.state.attachments[i].selected = i >= ns && i <= this.lastSel;
|
||||||
|
}
|
||||||
|
else if (ns > this.lastSel)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.state.attachments.length; i++)
|
||||||
|
this.state.attachments[i].selected = i >= this.lastSel && i <= ns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ev.ctrlKey)
|
||||||
|
{
|
||||||
|
this.state.attachments[ns].selected = true;
|
||||||
|
this.lastSel = ns;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.lastSel = ns;
|
||||||
|
for (var i = 0; i < this.state.attachments.length; i++)
|
||||||
|
this.state.attachments[i].selected = (i == ns);
|
||||||
|
}
|
||||||
|
this.setState({ attachments: this.state.attachments });
|
||||||
|
}
|
||||||
|
},
|
||||||
render: function()
|
render: function()
|
||||||
{
|
{
|
||||||
return <div className="compose">
|
return <div className="compose">
|
||||||
|
@ -823,9 +896,9 @@ var ComposeWindow = React.createClass({
|
||||||
<div className="no-attach" style={this.state.attachments.length ? { display: 'none' } : null}>
|
<div className="no-attach" style={this.state.attachments.length ? { display: 'none' } : null}>
|
||||||
<input type="file" multiple="multiple" onChange={this.addAttachments} />
|
<input type="file" multiple="multiple" onChange={this.addAttachments} />
|
||||||
</div>
|
</div>
|
||||||
<div className="attach-list" tabIndex="1" onScroll={this.scrollAttachList}
|
<div ref="scroll" className="attach-list" tabIndex="1" onScroll={this.scrollAttachList} onKeyDown={this.attachKeyDown}
|
||||||
style={this.state.attachments.length ? null : { display: 'none' }}>
|
style={this.state.attachments.length ? null : { display: 'none' }}>
|
||||||
<div className="title" style={{ top: this.state.attachScroll }}>
|
<div ref="title" className="title" style={{ top: this.state.attachScroll }}>
|
||||||
<div className="name">Attachment <a className="button">
|
<div className="name">Attachment <a className="button">
|
||||||
<img src="icons/attach.png" />
|
<img src="icons/attach.png" />
|
||||||
<input type="file" multiple="multiple" onChange={this.addAttachments} />
|
<input type="file" multiple="multiple" onChange={this.addAttachments} />
|
||||||
|
@ -833,7 +906,8 @@ var ComposeWindow = React.createClass({
|
||||||
<div className="size">Size</div>
|
<div className="size">Size</div>
|
||||||
</div>
|
</div>
|
||||||
{this.state.attachments.map((a, i) =>
|
{this.state.attachments.map((a, i) =>
|
||||||
<div key={'a'+i} className={'attachment'+(a.selected ? ' selected' : '')}>
|
<div ref={'a'+i} title={a.name+' ('+formatBytes(a.size)+')'} key={'a'+i} data-i={i}
|
||||||
|
className={'attachment'+(a.selected ? ' selected' : '')} onMouseDown={this.selectAttachment}>
|
||||||
<div className="name">{a.name}</div>
|
<div className="name">{a.name}</div>
|
||||||
<div className="size">{formatBytes(a.size)}</div>
|
<div className="size">{formatBytes(a.size)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -966,7 +1040,7 @@ var MessageView = React.createClass({
|
||||||
render: function()
|
render: function()
|
||||||
{
|
{
|
||||||
var msg = this.state.msg;
|
var msg = this.state.msg;
|
||||||
return <div className={'message-view'+(!msg ? ' no-mail-shown' : '')}>
|
return <div className={'message-view'+(!msg ? ' no-mail-shown' : '')+(!this.state.quickReply ? ' no-quick' : '')}>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<DropDownButton dropdownId="reply" icon="mail_reply" text="Reply" />
|
<DropDownButton dropdownId="reply" icon="mail_reply" text="Reply" />
|
||||||
<a className="button"><img src="icons/mail_reply_all.png" />Reply All</a>
|
<a className="button"><img src="icons/mail_reply_all.png" />Reply All</a>
|
||||||
|
|
Loading…
Reference in New Issue