Browse Source

clean webmail

master
ale 4 years ago
parent
commit
152c78cc71
47 changed files with 4 additions and 4612 deletions
  1. +0
    -85
      webmail/views/account/2fa.hbs
  2. +0
    -142
      webmail/views/account/autoreply.hbs
  3. +0
    -140
      webmail/views/account/create.hbs
  4. +0
    -88
      webmail/views/account/filters.hbs
  5. +0
    -18
      webmail/views/account/filters/create.hbs
  6. +0
    -18
      webmail/views/account/filters/edit.hbs
  7. +0
    -131
      webmail/views/account/identities.hbs
  8. +0
    -46
      webmail/views/account/identities/create.hbs
  9. +0
    -46
      webmail/views/account/identities/edit.hbs
  10. +0
    -103
      webmail/views/account/index.hbs
  11. +0
    -68
      webmail/views/account/login.hbs
  12. +0
    -52
      webmail/views/account/login.hbs.new
  13. +0
    -68
      webmail/views/account/login.hbs.orig
  14. +0
    -98
      webmail/views/account/profile.hbs
  15. +0
    -26
      webmail/views/account/security.hbs
  16. +0
    -131
      webmail/views/account/security/2fa.hbs
  17. +0
    -35
      webmail/views/account/security/asp.hbs
  18. +0
    -151
      webmail/views/account/security/asps.hbs
  19. +0
    -34
      webmail/views/account/security/enable-totp.hbs
  20. +0
    -45
      webmail/views/account/security/enable-u2f.hbs
  21. +0
    -115
      webmail/views/account/security/events.hbs
  22. +0
    -98
      webmail/views/account/security/gpg.hbs
  23. +0
    -67
      webmail/views/account/security/password.hbs
  24. +0
    -43
      webmail/views/account/update-password.hbs
  25. +0
    -7
      webmail/views/error.hbs
  26. +0
    -160
      webmail/views/help.hbs
  27. +1
    -1
      webmail/views/layout-popup.hbs
  28. +2
    -1
      webmail/views/layout-webmail.hbs
  29. +1
    -1
      webmail/views/layout.hbs
  30. +0
    -3
      webmail/views/partials/accountmenu.hbs
  31. +0
    -150
      webmail/views/partials/filter.hbs
  32. +0
    -18
      webmail/views/partials/header.hbs
  33. +0
    -68
      webmail/views/partials/identity.hbs
  34. +0
    -44
      webmail/views/partials/mailbox.hbs
  35. +0
    -51
      webmail/views/partials/messagerow.hbs
  36. +0
    -85
      webmail/views/partials/navbar.hbs
  37. +0
    -71
      webmail/views/partials/scripts.hbs
  38. +0
    -8
      webmail/views/partials/searchfield.hbs
  39. +0
    -5
      webmail/views/partials/securitymenu.hbs
  40. +0
    -57
      webmail/views/partials/tos.hbs
  41. +0
    -13
      webmail/views/tos.hbs
  42. +0
    -134
      webmail/views/webmail/audit.hbs
  43. +0
    -46
      webmail/views/webmail/create.hbs
  44. +0
    -754
      webmail/views/webmail/index.hbs
  45. +0
    -75
      webmail/views/webmail/mailbox.hbs
  46. +0
    -630
      webmail/views/webmail/message.hbs
  47. +0
    -382
      webmail/views/webmail/send.hbs

+ 0
- 85
webmail/views/account/2fa.hbs View File

@ -1,85 +0,0 @@
<input type="hidden" id="_csrf" value="{{csrfToken}}" />
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Two factor authentication</h3>
</div>
<div class="panel-body">
<div id="show-u2f" style="display:{{#if enabledU2f}}block{{else}}none{{/if}}">
<div style="margin:10px 0;">
<div id="u2f-wait">
<img src="/images/u2f-wait.png" />
</div>
<div id="u2f-fail" style="display: none">
<img src="/images/u2f-fail.png" />
</div>
<div id="u2f-success" style="display: none">
<img src="/images/u2f-success.png" />
</div>
</div>
<p id="message">
Initializing...
</p>
<div>
<div class="pull-right">
<a href="#" id="enable-totp">or use security code</a>
</div>
<a href="/account/logout"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Cancel</a>
</div>
</div>
<div id="show-totp" style="display:{{#if enabledU2f}}none{{else}}block{{/if}}">
<form id="totp-form">
<p>
Open your authentication app and enter the code to log in
</p>
<div class="form-group" id="totp-token-field">
<label for="token">Security code</label>
<input type="number" class="form-control" id="token" placeholder="6 digit code" required autofocus>
<span class="help-block" id="totp-token-error" style="display: none"></span>
</div>
<div>
<div class="pull-right">
<button type="submit" id="totp-btn" class="btn btn-success" data-loading-text="Checking..."><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> Verify</button>
</div>
<a href="/account/logout"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Cancel</a>
</div>
<div class="clearfix"></div>
</form>
</div>
<div class="checkbox form-footer">
<label>
<input type="checkbox" id="remember2fa"> Trust this device for 30 days
</label>
</div>
</div>
</div>
<script>
// U2F support must be checked *before* loading /u2f-api.js
{{! only check if user has enabled u2f, otherwise no reason to use it }}
var U2FSUPPORT = {{#if enabledU2f}}typeof u2f === 'object' || typeof chrome === 'object'{{else}}false{{/if}};
</script>
<script src="/login-key-handler.js"></script>
<script src="/u2f-api.js"></script>
<script src="/2fa.js"></script>

+ 0
- 142
webmail/views/account/autoreply.hbs View File

@ -1,142 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-calendar" aria-hidden="true"></span> Autoreply</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form method="post" action="/account/autoreply">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="start" name="start" value="{{#if values.start}}{{values.start}}{{/if}}" />
<input type="hidden" id="end" name="end" value="{{#if values.end}}{{values.end}}{{/if}}" />
<fieldset>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Autoreply settings</h3>
</div>
<div class="panel-body">
<p>
If enabled then an autoreply message is sent to all incoming messages. If a contact sends multiple messages then the autoreply is sent at most once in every four hours.
</p>
<div class="radio">
<label>
<input type="radio" name="status" value="false" {{#unless values.status}}checked{{/unless}}>
Autoreply is {{#unless values.status}}<span class="label label-default">disabled</span>{{else}}disabled{{/unless}}
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="status" value="true" {{#if values.status}}checked{{/if}}>
Autoreply is {{#if values.status}}<span class="label label-info">enabled</span>{{else}}enabled{{/if}}
</label>
</div>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" name="name" value="{{values.name}}" placeholder="Sender name in the autoreply From: header">
</div>
<div class="form-group">
<label for="subject">Subject</label>
<input type="text" class="form-control" id="subject" name="subject" value="{{values.subject}}" placeholder="Leave blank to use the default subject">
</div>
<div class="form-group">
<label for="daterange">Time</label>
<div class="form-group-sm daterangeElm" style="position: relative">
<input type="text" id="daterange" class="form-control" value="">
<i class="glyphicon glyphicon-calendar fa fa-calendar" style="position: absolute; bottom: 10px; right: 24px; top: auto; cursor: pointer;"></i>
</div>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea class="form-control" name="text" value="{{values.text}}" rows="3">{{values.text}}</textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> Update</button>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
$('#daterange').daterangepicker({
"showDropdowns": true,
"showISOWeekNumbers": true,
"timePicker": true,
"timePicker24Hour": true,
"autoApply": true,
"autoUpdateInput": false,
"locale": {
"direction": "ltr",
"format": "DD/MM/YYYY HH:mm",
"separator": " - ",
"applyLabel": "Select",
"cancelLabel": "Cancel",
"fromLabel": "From",
"toLabel": "To",
"customRangeLabel": "Custom",
"daysOfWeek": [
"Su",
"Mo",
"Tu",
"We",
"Th",
"Fr",
"Sa"
],
"monthNames": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
"firstDay": 1
},
{{#if values.start}}
"startDate": moment("{{values.start}}").format('DD/MM/YYYY HH:mm'),
{{/if}}
{{#if values.end}}
"endDate": moment("{{values.end}}").format('DD/MM/YYYY HH:mm'),
{{/if}}
"alwaysShowCalendars": true
}, function(start, end, label) {
document.getElementById('start').value = start.valueOf();
document.getElementById('end').value = end.valueOf();
document.getElementById('daterange').value = start.format('DD/MM/YYYY HH:mm') + ' – ' + end.format('DD/MM/YYYY HH:mm');
});
$('.daterangeElm i').click(function() {
$(this).parent().find('input').click();
});
{{#if values.start}}
document.getElementById('daterange').value = moment("{{values.start}}").format('DD/MM/YYYY HH:mm') + ' – ' + moment("{{values.end}}").format('DD/MM/YYYY HH:mm');
{{/if}}
});
</script>

+ 0
- 140
webmail/views/account/create.hbs View File

@ -1,140 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Create new account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form method="post" id="create-form" action="/account/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="domain" name="domain" value="{{values.domain}}">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Account information</h3>
</div>
<div class="panel-body">
<p>
Enter your account details. Account username is allowed to include latin characters only. Activated accounts can add extra identity addresses that may contain unicode characters as well.
</p>
<div class="row">
<div class="col-md-6">
<div class="form-group{{#if errors.name}} has-error{{/if}}">
<label for="name">Your name</label>
<input type="text" class="form-control" name="name" id="name" placeholder="eg. &quot;Jaan Tamm&quot;" value="{{values.name}}" required>
{{#if errors.name}}
<span class="help-block">{{errors.name}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.username}} has-error{{/if}}">
<label for="name">Your new address (also the username)</label>
<div class="input-group">
<input type="text" class="form-control" name="username" id="username" placeholder="eg. &quot;username&quot; or &quot;user.name&quot;" value="{{values.username}}" pattern="^[A-Za-z0-9][A-Za-z\-\.0-9]*$" required>
<span class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@<span class="selected-domain" style="text-transform: lowercase;">{{values.domain}}</span><span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
{{#each domains}}
<li><a href="#" class="change-domain-link" data-domain="{{this}}">{{this}}</a></li>
{{/each}}
</ul>
</span>
</div>
{{#if errors.username}}
<span class="help-block">{{errors.username}}</span>
{{else}}
<span class="help-block">Latin letters and numbers only. Dots and dashes are allowed as separators.<span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password">Your password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="eg. &quot;supersecret&quot;" required>
{{#if errors.password}}
<span class="help-block">{{errors.password}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password2">Repeat password</label>
<input type="password" class="form-control" name="password2" id="password2" placeholder="repeat password" required>
</div>
<div class="form-group{{#if errors.remember}} has-error{{/if}}">
<div class="checkbox">
<label>
<input type="checkbox" name="remember" required> Agree to <a href="/tos" target="_blank">terms of service</a>
{{#if errors.remember}}
<span class="help-block">{{errors.remember}}</span>
{{/if}}
</label>
</div>
</div>
</div>
</div>
<div class="form-group">
{{#if recaptcha}}
<button
class="g-recaptcha btn btn-success"
data-sitekey="{{recaptcha}}"
data-callback="onCreateSubmit">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span>Create new account
</button>
{{else}}
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-user" aria-hidden="true"></span>Create new account</button>
{{/if}}
</div>
</div>
</div>
</form>
</div>
</div>
{{#if recaptcha}}
<script src='https://www.google.com/recaptcha/api.js'></script>
<script>
function onCreateSubmit(token) {
document.getElementById("create-form").submit();
}
</script>
{{/if}}
<script>
document.addEventListener('DOMContentLoaded', function() {
var domainElement = document.getElementById('domain');
var updateDomain = function(e,elm){
e.preventDefault();
var domain = elm.dataset.domain;
if(domain){
domainElement.value = domain;
document.querySelector('.selected-domain').textContent = domain;
}
};
var setupDomainButton = function(elm){
elm.addEventListener('click', function(e){updateDomain(e,elm)}, false);
elm.addEventListener('touch', function(e){updateDomain(e,elm)}, false);
}
var domainLinks = document.querySelectorAll('.change-domain-link');
for(var i=0, len = domainLinks.length; i<len; i++){
setupDomainButton(domainLinks[i]);
}
}, false);
</script>

+ 0
- 88
webmail/views/account/filters.hbs View File

@ -1,88 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Filters</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Mail Filters</h3></div>
<div class="panel-body">
<p>Here you can create and modify filters that apply on all incoming messages.</p>
</div>
<table class="table table-responsive">
<tbody>
{{#if filters}}
{{#each filters}}
<tr>
<th>
{{index}}
</th>
<td>
<div class="pull-right">
<a href="/account/filters/edit?id={{id}}" class="btn btn-info btn-xs"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit</a>
<button type="button" data-filter="{{id}}" class="btn btn-danger btn-xs" data-toggle="modal" data-target="#deleteModal"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete</button>
</div>
<div>
Query: <strong>{{query}}</strong><br /> Action: {{action}}
</div>
</td>
</tr>
{{/each}}
{{else}}
<tr>
<td colspan="3">
There are no filters created
</td>
</tr>
{{/if}}
</tbody>
</table>
<div class="panel-body">
<div class="form-group">
<a href="/account/filters/create" class="btn btn-success"><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Add new filter</a>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Delete filter</h4>
</div>
<div class="modal-body">
Are you sure you want to permanently delete selected filter?
</div>
<div class="modal-footer">
<form method="post" action="/account/filters/delete">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="delete-form-filter" name="id" value="">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger bulk-delete-confirm">Yes, delete</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
$('#deleteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var filter = button.data('filter'); // Extract info from data-* attributes
document.getElementById('delete-form-filter').value = filter;
});
}, false);
</script>

+ 0
- 18
webmail/views/account/filters/create.hbs View File

@ -1,18 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Create filter</h1>
</div>
</div>
<form method="post" action="/account/filters/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
{{> filter}}
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Create filter</button>
<a href="/account/filters" class="btn btn-warning"><span class="glyphicon glyphicon-menu-left" aria-hidden="true"></span> Cancel</a>
</div>
</form>

+ 0
- 18
webmail/views/account/filters/edit.hbs View File

@ -1,18 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Updated filter</h1>
</div>
</div>
<form method="post" action="/account/filters/edit">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{values.id}}">
{{> filter}}
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Update filter</button>
<a href="/account/filters" class="btn btn-warning"><span class="glyphicon glyphicon-menu-left" aria-hidden="true"></span> Cancel</a>
</div>
</form>

+ 0
- 131
webmail/views/account/identities.hbs View File

@ -1,131 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> accountmenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Manage identities</h3></div>
<div class="panel-body">
<p>Here you can add and modify alias addresses for your account. Aliases act just like your main address. You can not send out emails from identities that you do not own.</p>
</div>
<table class="table table-responsive">
<thead>
<th>
&nbsp;
</th>
<th>
Identity name
</th>
<th>
Alias Address
</th>
<th>
Created
</th>
<th>
&nbsp;
</th>
</thead>
<tbody>
{{#each identities}}
<tr class="{{#if main}}identity-main{{/if}}">
<th>
{{index}}
</th>
<td>
{{#if name}}
{{name}}
{{else}}
<em>–</em>
{{/if}}
</td>
<td>
{{#if main}}
{{address}} <span>(default)</span>
{{else}}
{{address}}
{{/if}}
</td>
<td class="datestring" title="{{created}}">
{{created}}
</td>
<td class="text-right">
{{#if ../canEdit}}
<a href="/account/identities/edit?id={{id}}" class="btn btn-info btn-xs"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit</a>
{{/if}}
<button class="btn btn-danger btn-xs" data-address="{{address}}" {{#if main}}disabled{{else}}data-identity="{{id}}" data-toggle="modal" data-target="#deleteModal"{{/if}}><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete</button>
</td>
</tr>
{{/each}}
</tbody>
</table>
<div class="panel-body">
<div class="form-group">
{{#if canCreate}}
<a href="/account/identities/create" class="btn btn-success"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> Add new address</a>
{{else}}
<p class="text-muted">
Maximum amount of identities created
</p>
{{/if}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Delete address</h4>
</div>
<div class="modal-body">
Are you sure you want to permanently delete <strong id="delete-form-identity-val">this address</strong>?
</div>
<div class="modal-footer">
<form method="post" action="/account/identities/delete">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="delete-form-identity" name="id" value="">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger bulk-delete-confirm">Yes, delete</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
$('#deleteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var identity = button.data('identity'); // Extract info from data-* attributes
document.getElementById('delete-form-identity').value = identity;
document.getElementById('delete-form-identity-val').textContent = button.data('address');
});
}, false);
</script>

+ 0
- 46
webmail/views/account/identities/create.hbs View File

@ -1,46 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> accountmenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<form method="post" action="/account/identities/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="domain" name="domain" value="{{values.domain}}">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Identity information</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
{{> identity}}
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> Add new address</button>
<a href="/account/identities" class="btn btn-warning"><span class="glyphicon glyphicon-menu-left" aria-hidden="true"></span> Cancel</a>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

+ 0
- 46
webmail/views/account/identities/edit.hbs View File

@ -1,46 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> accountmenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<form method="post" action="/account/identities/edit">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{values.id}}">
<input type="hidden" id="domain" name="domain" value="{{values.domain}}">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Identity information</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
{{> identity}}
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> Edit address</button>
<a href="/account/identities" class="btn btn-warning"><span class="glyphicon glyphicon-menu-left" aria-hidden="true"></span> Cancel</a>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

+ 0
- 103
webmail/views/account/index.hbs View File

@ -1,103 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> accountmenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<label class="control-label">Address</label>
<div class="input-group">
<p class="form-control-static">
<a href="mailto:{{address}}">{{address}}</a>
</p>
</div>
</div>
<div class="form-group ">
<div class="input-group ">
<button type="button" id="reg-proto" class="btn btn-primary btn-xs"><span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> Register {{serviceName}} as default webmail handler</button>
</div>
</div>
<div class="form-group ">
<label class="control-label ">Quota</label>
<div class="input-group ">
<p class="form-control-static ">
Used <strong>{{storageUsed}}</strong> of <strong>{{quota}}</strong>
</p>
</div>
</div>
<div class="progress ">
<div class="progress-bar " role="progressbar " aria-valuenow=" {{storageOverview}} " aria-valuemin="0 " aria-valuemax="100 " style="min-width: 2em; ">
{{storageOverview}}%
</div>
</div>
<div class="form-group ">
<label class="control-label ">Messages sent</label>
<div class="input-group ">
<p class="form-control-static ">
Sent <strong>{{recipientsSent}}</strong> messages, daily allowed quota <strong>{{recipients}}</strong> messages
</p>
</div>
</div>
<div class="progress ">
<div class="progress-bar " role="progressbar " aria-valuenow=" {{recipientsOverview}} " aria-valuemin="0 " aria-valuemax="100 " style="min-width: 2em; ">
{{recipientsOverview}}%
</div>
</div>
<div class="form-group ">
<label class="control-label ">Forwarded messages</label>
<div class="input-group ">
<p class="form-control-static ">
Forwarded <strong>{{forwardsSent}}</strong> messages, daily allowed quota <strong>{{forwards}}</strong> messages
</p>
</div>
</div>
<div class="progress ">
<div class="progress-bar " role="progressbar " aria-valuenow=" {{forwardsOverview}} " aria-valuemin="0 " aria-valuemax="100 " style="min-width: 2em; ">
{{forwardsOverview}}%
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<input type="hidden" id="service-name" value="{{serviceName}}" />
<script>
function registerMailClient(){
var origin = window.location.origin;
if (!origin) {
origin= window.location.protocol + '//' + window.location.hostname + (window.location.port ? (':' + window.location.port) : '');
}
navigator.registerProtocolHandler('mailto', origin + '/webmail/send?to=%s', document.getElementById('service-name').value);
}
document.getElementById('reg-proto').addEventListener('click', registerMailClient, false);
</script>

+ 0
- 68
webmail/views/account/login.hbs View File

@ -1,68 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Log in</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form method="post" action="/account/login">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="_2faToken" id="_2faToken" value="">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Account information</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div class="form-group{{#if errors.username}} has-error{{/if}}">
<label class="control-label" for="username">Username</label>
<input type="text" class="form-control lowercase" name="username" id="username" placeholder="username" value="{{values.username}}" required>
{{#if errors.username}}
<span class="help-block">{{errors.username}}{{#if errors.username_action}} – <a href="{{errors.username_action.target}}">{{errors.username_action.title}}</a>{{/if}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password">Your password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="v3rys3cret" required>
{{#if errors.password}}
<span class="help-block">{{errors.password}}</span>
{{/if}}
</div>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" name="remember"> Remember me
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-log-in" aria-hidden="true"></span> Log in</button>
</div>
</div>
</div>
</form>
</div>
</div>
<script src="/login-key-handler.js"></script>
<script>
loginKeyHandler.setup(
document.getElementById('username'),
document.getElementById('_2faToken'),
'2fa'
);
</script>

+ 0
- 52
webmail/views/account/login.hbs.new View File

@ -1,52 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Log in <small>(Autoconfig with <a href="https://www.mozilla.org/thunderbird/" target="_blank">thunderbird</a>)</small></h1>
</div>
</div>
<div class="row">
<div class="col-md-6">
<form method="post" action="/account/login">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<fieldset>
<div class="form-group{{#if errors.username}} has-error{{/if}}">
<label class="control-label" for="username">Username</label>
<div class="input-group">
<input type="text" class="form-control lowercase" name="username" id="username" placeholder="username" value="{{values.username}}" pattern="^[A-Za-z0-9][A-Za-z\-\.0-9]*[A-Za-z0-9]$" required title="Valid email address user">
<span class="input-group-addon">@{{serviceDomain}}</span>
</div>
{{#if errors.username}}
<span class="help-block">{{errors.username}}{{#if errors.username_action}} – <a href="{{errors.username_action.target}}">{{errors.username_action.title}}</a>{{/if}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password">Password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="v3rys3cret" required>
{{#if errors.password}}
<span class="help-block">{{errors.password}}</span>
{{/if}}
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" name="remember"> Remember me
</label>
</div>
</div>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-success">Log in</button>
</div>
</form>
</div>
</div>

+ 0
- 68
webmail/views/account/login.hbs.orig View File

@ -1,68 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Log in</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form method="post" action="/account/login">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="_2faToken" id="_2faToken" value="">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Account information</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div class="form-group{{#if errors.username}} has-error{{/if}}">
<label class="control-label" for="username">Username</label>
<input type="text" class="form-control lowercase" name="username" id="username" placeholder="username" value="{{values.username}}" required>
{{#if errors.username}}
<span class="help-block">{{errors.username}}{{#if errors.username_action}} – <a href="{{errors.username_action.target}}">{{errors.username_action.title}}</a>{{/if}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password">Your password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="v3rys3cret" required>
{{#if errors.password}}
<span class="help-block">{{errors.password}}</span>
{{/if}}
</div>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" name="remember"> Remember me
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-log-in" aria-hidden="true"></span> Log in</button>
</div>
</div>
</div>
</form>
</div>
</div>
<script src="/login-key-handler.js"></script>
<script>
loginKeyHandler.setup(
document.getElementById('username'),
document.getElementById('_2faToken'),
'2fa'
);
</script>

+ 0
- 98
webmail/views/account/profile.hbs View File

@ -1,98 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> accountmenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<form method="post" action="/account/profile">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<fieldset>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">General</h3>
</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label">Username</label>
<div class="input-group">
<p class="form-control-static">{{values.username}}</p>
</div>
</div>
<div class="form-group{{#if errors.name}} has-error{{/if}}">
<label for="name">Your name</label>
<input type="text" class="form-control" name="name" id="name" placeholder="eg. &quot;Jaan Tamm&quot;" value="{{values.name}}">
{{#if errors.name}}
<span class="help-block">{{errors.name}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.spamLevel}} has-error{{/if}}">
<label for="name">Spam detection level</label>
<select class="form-control" name="spamLevel">
<option value="">
-- Select --
</option>
{{#each spamLevels}}
<option value="{{value}}" {{#if selected}}selected{{/if}}>
{{description}}
</option>
{{/each}}
</select>
{{#if errors.spamLevel}}
<span class="help-block">{{errors.spamLevel}}</span>
{{/if}}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Message forwarding</h3>
</div>
<div class="panel-body">
<p>
Leave the following fields blank if you do not wish to forward all incoming emails
</p>
<div class="form-group{{#if errors.targets}} has-error{{/if}}">
<label for="targets">Forward incoming messages to:</label>
<input type="text" class="form-control" name="targets" id="targets" placeholder="user@example.com" value="{{values.targets}}">
{{#if errors.targets}}
<span class="help-block">{{errors.targets}}</span>
{{/if}}
<span class="help-block">Use comma separated list of addresses for multiple recipients</span>
</div>
</div>
</div>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> Update</button>
</div>
</form>
</div>
</div>
</div>
</div>

+ 0
- 26
webmail/views/account/security.hbs View File

@ -1,26 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> securitymenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<p>
Future feature
</p>
</div>
</div>
</div>
</div>

+ 0
- 131
webmail/views/account/security/2fa.hbs View File

@ -1,131 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> securitymenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Two factor authentication</h3>
</div>
<div class="panel-body">
<p>
If two-factor authentication is enabled then you will be required to enter a code from an authenticator app when logging in.
TOTP compatible authenticator app like Google Authenticator is needed to use two-factor authentication.
</p>
<p>
<a href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1' style="display:inline-block;overflow:hidden;background:url(/images/en_badge_web_generic.png) no-repeat;width:135px;height:40px;background-size:contain;background-position: center;" target="_blank"></a>
<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8" style="display:inline-block;overflow:hidden;background:url(//linkmaker.itunes.apple.com/assets/shared/badges/en-us/appstore-lrg.svg) no-repeat;width:135px;height:40px;background-size:contain;" target="_blank"></a>
</p>
<p>
External applications can not access IMAP, POP3 ja SMTP using the account password if two-factor authentication is enabled. <a href="/account/security/asps">Application specific passwords</a> must be generated instead for these applications.
</p>
</div>
<table class="table table-responsive">
<tr>
<td>
{{#if enabled2fa}}
Two factor authentication is <span class="label label-success"><span class="glyphicon glyphicon-qrcode" aria-hidden="true"></span> Enabled</span>
{{else}}
Two factor authentication is <span class="label label-default"><span class="glyphicon glyphicon-qrcode" aria-hidden="true"></span> Disabled</span>
{{/if}}
</td>
<td class="text-right">
{{#if enabled2fa}}
<button type="button" class="btn btn-danger btn-xs" data-toggle="modal" data-target="#deleteModal"><span class="glyphicon glyphicon-minus-sign" aria-hidden="true"></span> Disable</button>
{{else}}
<form method="post" id="enable-2fa" action="/account/security/2fa/enable-totp">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="submit" class="btn btn-success btn-xs"><span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span> Enable</button>
</form>
{{/if}}
</td>
</tr>
{{#if enabled2fa}}
<tr>
<td>
{{#if enabledU2f}}
U2F security key is <span class="label label-success"><span class="glyphicon glyphicon-flash" aria-hidden="true"></span> Enabled</span>
{{else}}
U2F security key is <span class="label label-default"><span class="glyphicon glyphicon-flash" aria-hidden="true"></span> Disabled</span>
{{/if}}
</td>
<td class="text-right">
{{#if enabledU2f}}
<button type="button" class="btn btn-danger btn-xs" data-toggle="modal" data-target="#revokeModal"><span class="glyphicon glyphicon-minus-sign" aria-hidden="true"></span> Disable</button>
{{else}}
<form method="post" id="enable-u2f" action="/account/security/2fa/enable-u2f">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="submit" class="btn btn-success btn-xs"><span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span> Enable</button>
</form>
{{/if}}
</td>
</tr>
{{/if}}
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Disable 2FA</h4>
</div>
<div class="modal-body">
Are you sure you want to disable two factor authentication?
</div>
<div class="modal-footer">
<form method="post" action="/account/security/2fa/disable-totp">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger bulk-delete-confirm">Yes, disable</button>
</form>
</div>
</div>
</div>
</div>
<div class="modal" id="revokeModal" tabindex="-1" role="dialog" aria-labelledby="revokeModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="revokeModalLabel">Revoke key</h4>
</div>
<div class="modal-body">
Are you sure you want to revoke U2F security key?
</div>
<div class="modal-footer">
<form method="post" action="/account/security/2fa/disable-u2f">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger bulk-delete-confirm">Yes, revoke</button>
</form>
</div>
</div>
</div>
</div>

+ 0
- 35
webmail/views/account/security/asp.hbs View File

@ -1,35 +0,0 @@
<form method="post" id="generate-autoconfig" action="/account/autoconfig">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="password" value="{{password}}">
</form>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Application specific password</h3>
</div>
<div class="panel-body">
<p>
Use the generated password in external application for IMAP, POP3 or SMTP
</p>
<p>
<strong>{{description}}</strong>
</p>
<p class="lead bg-info text-center">
{{passwordFormatted}}
</p>
<p>
For OSX and iOS you can download configuration profile to auto-configure your email application
</p>
<p>
<div class="pull-right">
<a href="data:application/x-apple-aspen-config;base64,{{mobileconfig}}" download="{{user.username}}.mobileconfig" class="btn btn-info btn-xs"><span class="glyphicon glyphicon-cloud-download" aria-hidden="true"></span> OSX / iOS</a>
</div>
<a href="/account/security/asps"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Go back</a>
</p>
</div>
</div>

+ 0
- 151
webmail/views/account/security/asps.hbs View File

@ -1,151 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> securitymenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Application specific passwords</h3></div>
<div class="panel-body">
<p>Here are listed passwords generated for specific applications. If the password is leaked then delete it and generate a new one.</p>
<p>
Application Specific Passwords must be used for external applications if two factor authentication is enabled.
</p>
</div>
<table class="table table-responsive">
<thead>
<tr>
<th>
#
</th>
<th>
Description
</th>
<th>
Created
</th>
<th>
Used
</th>
<th>
&nbsp;
</th>
</tr>
</thead>
<tbody>
{{#if asps}}
{{#each asps}}
<tr>
<th>
{{index}}
</th>
<td>
{{description}}
</td>
<td class="datestring" title="{{created}}">
{{created}}
</td>
<td>
{{#if lastUse.time}}
<a href="/account/security/events?event={{lastUse.event}}"><span class="datestring" title="{{lastUse.time}}">{{lastUse.time}}</span></a>
{{else}}
never
{{/if}}
</td>
<td>
<div class="pull-right">
<button type="button" data-asp="{{id}}" class="btn btn-danger btn-xs" data-toggle="modal" data-target="#deleteModal"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete</button>
</div>
</td>
</tr>
{{/each}}
{{else}}
<tr>
<td colspan="4">
No application specific passwords generated
</td>
</tr>
{{/if}}
</tbody>
</table>
</div>
<form method="post" action="/account/security/asps/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<fieldset>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Create new application specific password</h3>
</div>
<div class="panel-body">
<div class="form-group{{#if errors.description}} has-error{{/if}}">
<label for="description">Application description</label>
<input type="text" class="form-control" name="description" id="description" placeholder="Password for Outlook ..." required>
{{#if errors.description}}
<span class="help-block">{{errors.description}}</span>
{{/if}}
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Generate password</button>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Delete password</h4>
</div>
<div class="modal-body">
Are you sure you want to permanently delete Application Specific Password?
</div>
<div class="modal-footer">
<form method="post" action="/account/security/asps/delete">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="delete-form-asp" name="id" value="">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger bulk-delete-confirm">Yes, delete</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
$('#deleteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var asp = button.data('asp'); // Extract info from data-* attributes
document.getElementById('delete-form-asp').value = asp;
});
}, false);
</script>

+ 0
- 34
webmail/views/account/security/enable-totp.hbs View File

@ -1,34 +0,0 @@
<form id="totp-form">
<input type="hidden" id="_csrf" value="{{csrfToken}}">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Two factor authentication</h3>
</div>
<div class="panel-body">
<p>
Scan the code with an authenticator app and enter resulting security code below to verify
</p>
<p class="lead text-center">
<img src="{{imageUrl}}" style="width: 200px;" width="200">
</p>
<div class="form-group" id="totp-token-field">
<label for="token">Security code</label>
<input type="number" class="form-control" id="token" placeholder="6 digit code" required autofocus>
<span class="help-block" id="totp-token-error" style="display: none"></span>
</div>
<div>
<div class="pull-right">
<button type="submit" id="totp-btn" class="btn btn-success" data-loading-text="Checking..."><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> Verify</button>
</div>
<a href="/account/security"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Cancel</a>
</div>
</div>
</div>
</form>
<script src="/enable-totp.js"></script>

+ 0
- 45
webmail/views/account/security/enable-u2f.hbs View File

@ -1,45 +0,0 @@
<input type="hidden" id="_csrf" value="{{csrfToken}}">
<input id="version" type="hidden" id="version" value="{{u2fRegRequest.version}}">
<input id="appId" type="hidden" id="appId" value="{{u2fRegRequest.appId}}">
<input id="challenge" type="hidden" id="challenge" value="{{u2fRegRequest.challenge}}">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Two factor authentication</h3>
</div>
<div class="panel-body">
<div style="margin:10px 0;">
<div id="u2f-wait">
<img src="/images/u2f-wait.png" />
</div>
<div id="u2f-fail" style="display: none">
<img src="/images/u2f-fail.png" />
</div>
<div id="u2f-success" style="display: none">
<img src="/images/u2f-success.png" />
</div>
</div>
<p id="message">
Initializing...
</p>
<div>
<a href="/account/security"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Cancel</a>
</div>
</div>
</div>
<script>
// U2F support must be checked *before* loading /u2f-api.js
var U2FSUPPORT = typeof u2f === 'object' || typeof chrome === 'object';
</script>
<script src="/u2f-api.js"></script>
<script src="/enable-u2f.js"></script>

+ 0
- 115
webmail/views/account/security/events.hbs View File

@ -1,115 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> securitymenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<table class="table table-responsive">
<thead>
<tr>
<th>
Environment
</th>
<th>
Action
</th>
<th>
Result
</th>
<th>
IP
</th>
<th>
Session
</th>
<th>
Time
</th>
</tr>
</thead>
<tbody>
{{#if results}}
{{#each results}}
<tr>
<td>
{{protocol}}
</td>
<td>
{{#if asp}}
<div class="pull-right">
<strong>{{asp.name}}</strong>
</div>
{{/if}}
{{action}}
({{events}})
</td>
<td>
{{#if label}}
<span class="label label-{{label}}">{{result}}</span>
{{else}}
{{result}}
{{/if}}
</td>
<td>
{{ip}}
</td>
<td>
{{#if sess}}
<em>{{sessStr}}</em>
{{else}}
{{/if}}
</td>
<td class="datestring-fixed text-right" title="{{created}}">
{{created}}
</td>
</tr>
{{/each}}
{{else}}
<tr>
<td colspan="6">
<em>No events found</em>
</td>
</tr>
{{/if}}
</tbody>
</table>
<nav aria-label="nav">
<ul class="pager">
{{#if previousCursor}}
<li class="previous"><a href="/account/security/events?previous={{previousCursor}}&amp;page={{previousPage}}"><span aria-hidden="true">&larr;</span> Newer</a></li>
{{else}}
<li class="previous disabled"><a href="#"><span aria-hidden="true">&larr;</span> Newer</a></li>
{{/if}}
{{#if nextCursor}}
<li class="next"><a href="/account/security/events?next={{nextCursor}}&amp;page={{nextPage}}">Older <span aria-hidden="true">&rarr;</span></a></li>
{{else}}
<li class="next disabled"><a href="#">Older <span aria-hidden="true">&rarr;</span></a></li>
{{/if}}
</ul>
</nav>
</div>
</div>
</div>
</div>

+ 0
- 98
webmail/views/account/security/gpg.hbs View File

@ -1,98 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> securitymenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<form method="post" action="/account/security/gpg">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<fieldset>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">GPG Encryption</h3>
</div>
<div class="panel-body">
<p>
If encryption is enabled then all cleartext messages that are archived to this
account are encrypted using provided public key. Private key is not known to the
service so if they key is lost then messages can not be recovered. {{serviceName}}
is able to display encrypted messages if <a
href="https://www.mailvelope.com/">Mailvelope browser extension</a> is
installed, otherwise you would have to download the messages and open these in a
GPG-compatible email client.
</p>
<div class="form-group{{#if errors.encryptMessages}} has-error{{/if}}">
<label class="radio-inline">
<input type="radio" name="encryptMessages" id="encryptMessagesNo" value="false"
{{#unless values.encryptMessages}}checked{{/unless}}> Disable encryption
</label>
<label class="radio-inline">
<input type="radio" name="encryptMessages" id="encryptMessagesYes" value="true"
{{#if values.encryptMessages}}checked{{/if}}> Enable encryption
</label>
{{#if errors.encryptMessages}}
<span class="help-block">{{errors.encryptMessages}}</span>
{{/if}}
</div>
{{#if fingerprint}}
<div class="form-group">
<label>Current key:</label>
<div class="form-control-static">
<div class="pull-right">
<label>
<input type="checkbox" name="removeKey" value="yes" /> Remove current
key
</label>
</div>
<div>
<code class="response">{{fingerprint}}</code>
{{#if keyAddress}}(<em>{{keyAddress}}</em>){{/if}}
</div>
</div>
</div>
{{/if}}
<div class="form-group{{#if errors.pubKey}} has-error{{/if}}">
<label for="pubKey">GPG Public Key{{#if fingerprint}} (replaces current key){{/if}}:
</label>
<textarea class="form-control" style="font-family: monospace;" rows="6" id="pubKey"
name="pubKey"
placeholder="Begins with &#39;-----BEGIN PGP PUBLIC KEY BLOCK-----&#39;">{{pubKey}}</textarea>
{{#if errors.pubKey}}
<span class="help-block">{{errors.pubKey}}</span>
{{/if}}
<span class="help-block">Leave empty if you do not want to replace the current
key</span>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-lock"
aria-hidden="true"></span> Update encryption settings</button>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>

+ 0
- 67
webmail/views/account/security/password.hbs View File

@ -1,67 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> securitymenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<form method="post" action="/account/security/password">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<fieldset>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Change Password</h3>
</div>
<div class="panel-body">
<p>
Change your account password here
</p>
<div class="form-group{{#if errors.existingPassword}} has-error{{/if}}">
<label for="existingPassword">Current password</label>
<input type="password" class="form-control" name="existingPassword" id="existingPassword" placeholder="eg. &quot;supersecret&quot;" autocomplete="off">
{{#if errors.existingPassword}}
<span class="help-block">{{errors.existingPassword}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password">New password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="eg. &quot;supersecret&quot;" autocomplete="off">
{{#if errors.password}}
<span class="help-block">{{errors.password}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password2">Repeat password</label>
<input type="password" class="form-control" name="password2" id="password2" placeholder="repeat password" autocomplete="off">
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> Change Password</button>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>

+ 0
- 43
webmail/views/account/update-password.hbs View File

@ -1,43 +0,0 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Change Password</h3>
</div>
<div class="panel-body">
<form method="post" action="/account/update-password">
<input type="hidden" name="_csrf" value="{{csrfToken}}" />
<p>
Your password needs to be changed. Enter your new account password below
</p>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password">New password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="eg. &quot;supersecret&quot;" autocomplete="off">
{{#if errors.password}}
<span class="help-block">{{errors.password}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password2}} has-error{{/if}}">
<label for="password2">Repeat password</label>
<input type="password" class="form-control" name="password2" id="password2" placeholder="repeat password" autocomplete="off">
{{#if errors.password2}}
<span class="help-block">{{errors.password2}}</span>
{{/if}}
</div>
<div>
<div class="pull-right">
<button type="submit" id="totp-btn" class="btn btn-success"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> Change Password</button>
</div>
<a href="/account/logout"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Cancel</a>
</div>
<div class="clearfix"></div>
</form>
</div>
</div>

+ 0
- 7
webmail/views/error.hbs View File

@ -1,7 +0,0 @@
<h3>{{error.status}} Error</h3>
<p class="lead">{{message}}</p>
{{#if error.stack}}
<pre>{{error.stack}}</pre>
{{/if}}

+ 0
- 160
webmail/views/help.hbs View File

@ -1,160 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Help</h1>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Account configuration</h3></div>
<div class="panel-body">
<p>
Use the following configuration for your desktop email client.
</p>
</div>
<table class="table table-responsive table-bordered">
<thead>
<tr>
<th>
&nbsp;
</th>
<th>
IMAP
</th>
<th>
POP3
</th>
<th>
SMTP
</th>
</tr>
</thead>
<tbody>
<tr>
<th>
Description
</th>
<td>
Access all messages and mailboxes
</td>
<td>
Access INBOX
</td>
<td>
Send messages
</td>
</tr>
{{#if user}}
<tr>
<th>
E-mail address
</th>
<td>
{{user.username}}@{{serviceDomain}}
</td>
<td>
{{user.username}}@{{serviceDomain}}
</td>
<td>
{{user.username}}@{{serviceDomain}}
</td>
</tr>
{{/if}}
<tr>
<th>
Server
</th>
<td>
{{setup.imap.hostname}}
</td>
<td>
{{setup.pop3.hostname}}
</td>
<td>
{{setup.smtp.hostname}}
</td>
</tr>
<tr>
<th>
Port
</th>
<td>
{{setup.imap.port}}
</td>
<td>
{{setup.pop3.port}}
</td>
<td>
{{setup.smtp.port}}
</td>
</tr>
<tr>
<th>
Security
</th>
<td>
{{#if setup.imap.secure}}
TLS/SSL
{{else}}
STARTTLS
{{/if}}
</td>
<td>
{{#if setup.pop3.secure}}
TLS/SSL
{{else}}
STARTTLS
{{/if}}
</td>
<td>
{{#if setup.smtp.secure}}
TLS/SSL
{{else}}
STARTTLS
{{/if}}
</td>
</tr>
<tr>
<th>
Username
</th>
{{#if user}}
<td>
{{user.username}}
</td>
<td>
{{user.username}}
</td>
<td>
{{user.username}}
</td>
{{else}}
<td>
Your username
</td>
<td>
Your username
</td>
<td>
Your username
</td>
{{/if}}
</tr>
<tr>
<th>
Password
</th>
<td>
********
</td>
<td>
********
</td>
<td>
********
</td>
</tr>
</tbody>
</table>
</div>

+ 1
- 1
webmail/views/layout-popup.hbs View File

@ -40,7 +40,7 @@
<footer class="footer">
<div class="container">
<p class="text-muted">&copy; 2019 <a href="/">{{serviceName}}</a>. <a href="mailto:info@{{serviceDomain}}">info@{{serviceDomain}}</a>. </p>
<p class="text-muted">&copy; 2020 <a href="/">{{serviceName}}</a>. <a href="mailto:info@{{serviceDomain}}">info@{{serviceDomain}}</a>. </p>
</div>
</footer>


+ 2
- 1
webmail/views/layout-webmail.hbs View File

@ -54,9 +54,10 @@
</div>
</div>
<footer class="footer">
<div class="container">
<p class="text-muted">&copy; 2019 <a href="/">{{serviceName}}</a>. <a href="mailto:info@{{serviceDomain}}">info@{{serviceDomain}}</a>. </p>
<p class="text-muted">&copy; 2020 <a href="/">{{serviceName}}</a>. <a href="mailto:info@{{serviceDomain}}">info@{{serviceDomain}}</a>. </p>
</div>
</footer>


+ 1
- 1
webmail/views/layout.hbs View File

@ -20,7 +20,7 @@
<footer class="footer">
<div class="container">
<p class="text-muted">&copy; 2019 <a href="/">{{serviceName}}</a>. <a href="mailto:info@{{serviceDomain}}">info@{{serviceDomain}}</a>. </p>
<p class="text-muted">&copy; 2020 <a href="/">{{serviceName}}</a>. <a href="mailto:info@{{serviceDomain}}">info@{{serviceDomain}}</a>. </p>
</div>
</footer>


+ 0
- 3
webmail/views/partials/accountmenu.hbs View File

@ -1,3 +0,0 @@
<li role="presentation" class="{{#if accMenuOverview}}active{{/if}}"><a href="/account/">Overview</a></li>
<li role="presentation" class="{{#if accMenuProfile}}active{{/if}}"><a href="/account/profile">Profile</a></li>
<li role="presentation" class="{{#if accMenuIdentities}}active{{/if}}"><a href="/account/identities">Identities</a></li>

+ 0
- 150
webmail/views/partials/filter.hbs View File

@ -1,150 +0,0 @@
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">Search messages by:</div>
<div class="panel-body">
<div class="form-group{{#if errors.query_from}} has-error{{/if}}">
<label for="query_from">From</label>
<input type="text" class="form-control input-sm" name="query_from" id="query_from" value="{{values.query_from}}">
{{#if errors.query_from}}
<span class="help-block">{{errors.query_from}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.query_to}} has-error{{/if}}">
<label for="query_to">To</label>
<input type="text" class="form-control input-sm" name="query_to" id="query_to" value="{{values.query_to}}">
{{#if errors.query_to}}
<span class="help-block">{{errors.query_to}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.query_subject}} has-error{{/if}}">
<label for="query_subject">Subject</label>
<input type="text" class="form-control input-sm" name="query_subject" id="query_subject" value="{{values.query_subject}}">
{{#if errors.query_subject}}
<span class="help-block">{{errors.query_subject}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.query_text}} has-error{{/if}}">
<label for="query_text">Includes the following text</label>
<input type="text" class="form-control input-sm" name="query_text" id="query_text" value="{{values.query_text}}">
{{#if errors.query_text}}
<span class="help-block">{{errors.query_text}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.query_listId}} has-error{{/if}}">
<label for="query_listId">List-ID</label>
<input type="text" class="form-control input-sm" name="query_listId" id="query_listId" value="{{values.query_listId}}">
{{#if errors.query_listId}}
<span class="help-block">{{errors.query_listId}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.query_ha}} has-error{{/if}}">
<label>Attachments</label>
<div>
<label class="checkbox-inline">
<input type="checkbox" name="query_haYes" id="query_haYes" value="true" {{#if values.query_haYes}}checked{{/if}}> Has attachments
</label>
<label class="checkbox-inline">
<input type="checkbox" name="query_haNo" id="query_haNo" value="true" {{#if values.query_haNo}}checked{{/if}}> Doesn't have attachments
</label>
</div>
{{#if errors.query_ha}}
<span class="help-block">{{errors.query_ha}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.query_size}} has-error{{/if}}">
<label for="query_sizeValue">Size</label>
<div class="form-inline">
<div class="form-group">
<span>Message size is</span>
<select class="form-control input-sm" id="query_sizeType" name="query_sizeType">
<option value="1" {{#if values.query_sizeTypeGt}}selected{{/if}}>greater than</option>
<option value="-1" {{#if values.query_sizeTypeLt}}selected{{/if}}>smaller than</option>
</select>
</div>
<div class="form-group">
<input type="number" class="form-control input-sm" id="query_sizeValue" name="query_sizeValue" value="{{values.query_sizeValue}}">
</div>
<div class="form-group">
<select class="form-control input-sm" id="query_sizeUnit" name="query_sizeUnit">
<option value="MB" {{#if values.query_sizeUnitMB}}selected{{/if}}>MB</option>
<option value="kB" {{#if values.query_sizeUnitKB}}selected{{/if}}>kB</option>
<option value="B" {{#if values.query_sizeUnitB}}selected{{/if}}>baiti</option>
</select>
</div>
</div>
{{#if errors.query_size}}
<span class="help-block">{{errors.query_size}}</span>
{{/if}}
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">When a message arrives that matches this search:</div>
<div class="panel-body">
<div class="checkbox">
<label>
<input type="checkbox" name="action_seenYes" id="action_seenYes" value="true" {{#if values.action_seenYes}}checked{{/if}}> Mark as seen
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="action_flagYes" id="action_flagYes" value="true" {{#if values.action_flagYes}}checked{{/if}}> Flag it
</label>
</div>
<div class="form-group{{#if errors.action_mailbox}} has-error{{/if}}">
<label for="action_mailbox">Move to mailbox:</label>
<select class="form-control input-sm" id="action_mailbox" name="action_mailbox">
<option value=""></option>
{{#each mailboxes}}
<option value="{{id}}" {{#if selected}}selected{{/if}}>{{path}}</option>
{{/each}}
</select>
{{#if errors.action_mailbox}}
<span class="help-block">{{errors.action_mailbox}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.action_targets}} has-error{{/if}}">
<label for="action_targets">Forward it to address:</label>
<input type="text" class="form-control input-sm" name="action_targets" id="action_targets" value="{{values.action_targets}}" placeholder="user@example.com">
<span class="help-block">Somma separated list of email addresses or URLs</span>
{{#if errors.action_targets}}
<span class="help-block">{{errors.action_targets}}</span>
{{/if}}
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="action_spamYes" id="action_spamYes" value="true" {{#if values.action_spamYes}}checked{{/if}}> Mark as spam
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="action_spamNo" id="action_spamNo" value="true" {{#if values.action_spamNo}}checked{{/if}}> Don't mark as spam
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="action_deleteYes" id="action_deleteYes" value="true" {{#if values.action_deleteYes}}checked{{/if}}> Delete it
</label>
</div>
</div>
</div>
</div>
</div>

+ 0
- 18
webmail/views/partials/header.hbs View File

@ -1,18 +0,0 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{serviceName}} web client">
<meta name="author" content="Andris Reinman">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<title>{{serviceName}}
{{#if title}} | {{title}}{{/if}}
</title>
<!--<link rel="stylesheet" href="/bootstrap-3.3.7/css/bootstrap.min.css">-->
<link rel="stylesheet" href="/bootstrap-3.3.7/css/lumen.css">
<link rel="stylesheet" href="/css/wildduck.css">

+ 0
- 68
webmail/views/partials/identity.hbs View File

@ -1,68 +0,0 @@
<div class="form-group{{#if errors.name}} has-error{{/if}}">
<label for="name">Name</label>
<input type="text" class="form-control" name="name" id="name" placeholder="eg &quot;John Smith&quot; or &quot;Accounting Department&quot;" value="{{#if values.name}}{{values.name}}{{/if}}" >
{{#if errors.name}}
<span class="help-block">{{errors.name}}</span>
{{else}}
<span class="help-block">This name is used as the sender name when using this identity. Keep blank to default to your account name</span>
{{/if}}
</div>
<div class="form-group{{#if errors.address}} has-error{{/if}}">
<label for="name">Alias address</label>
<div class="input-group">
<input type="text" class="form-control" name="address" id="address" placeholder="eg. &quot;username&quot; or &quot;user.name&quot; or &quot;андрис&quot;" value="{{values.address}}" required>
<span class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@<span class="selected-domain" style="text-transform: lowercase;">{{values.domain}}</span><span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
{{#each domains}}
<li><a href="#" class="change-domain-link" data-domain="{{this}}">{{this}}</a></li>
{{/each}}
</ul>
</span>
</div>
{{#if errors.address}}
<span class="help-block">{{errors.address}}</span>
{{else}}
<span class="help-block">Unicode characters are allowed in alias addresses.<span>
{{/if}}
</div>
{{#unless isMain}}
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" name="main" {{#if values.main}}checked{{/if}}> Set as default
</label>
</div>
</div>
{{/unless}}
<script>
document.addEventListener('DOMContentLoaded', function() {
var domainElement = document.getElementById('domain');
var updateDomain = function(e,elm){
e.preventDefault();
var domain = elm.dataset.domain;
if(domain){
domainElement.value = domain;
document.querySelector('.selected-domain').textContent = domain;
}
};
var setupDomainButton = function(elm){
elm.addEventListener('click', function(e){updateDomain(e,elm)}, false);
elm.addEventListener('touch', function(e){updateDomain(e,elm)}, false);
}
var domainLinks = document.querySelectorAll('.change-domain-link');
for(var i=0, len = domainLinks.length; i<len; i++){
setupDomainButton(domainLinks[i]);
}
}, false);
</script>

+ 0
- 44
webmail/views/partials/mailbox.hbs View File

@ -1,44 +0,0 @@
<fieldset>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Mailbox settings</h3>
</div>
<div class="panel-body">
{{#if isInbox}}
<div class="form-group">
<label for="name">Mailbox name</label>
<input type="text" class="form-control" name="name" id="name" placeholder="eg. &quot;Important Stuff&quot;" value="{{values.name}}" disabled>
<span class="help-block">INBOX folder can not be modified</span>
</div>
{{else}}
<div class="form-group{{#if errors.parent}} has-error{{/if}}">
<label for="parent">Mailbox Parent</label>
<select class="form-control" name="parent" id="parent">
<option value="">[No parent]</option>
{{#each parents}}
<option value="{{path}}" {{#if isParent}} selected {{/if}} >
{{name}}
</option>
{{/each}}
</select>
{{#if errors.parent}}
<span class="help-block">{{errors.parent}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.name}} has-error{{/if}}">
<label for="name">Mailbox name</label>
<input type="text" class="form-control" name="name" id="name" placeholder="eg. &quot;Important Stuff&quot;" value="{{values.name}}" required>
{{#if errors.name}}
<span class="help-block">{{errors.name}}</span>
{{/if}}
</div>
{{/if}}
</div>
</div>
</fieldset>

+ 0
- 51
webmail/views/partials/messagerow.hbs View File

@ -1,51 +0,0 @@
<tr id="msg_{{id}}" class="messagerow messagerow-{{mailbox}}-{{id}} {{#if seen}}message-seen{{else}}message-unseen{{/if}}">
<td class="messagerow-spacer"></td>
<td class="messagerow-checkbox">
<input type="checkbox" data-mailbox="{{mailbox}}" data-message="{{id}}" class="message-checkbox"/>
</td>
<td class="messagerow-star">
<a href="#" class="message-star {{#if flagged}}flagged{{else}}unflagged{{/if}}" data-mailbox="{{mailbox}}" data-message="{{id}}"><span class="glyphicon glyphicon-{{#if flagged}}star{{else}}star-empty{{/if}}" aria-hidden="true"></span></a>
</td>
<td class="messagerow-from">
<a href="/webmail/{{mailbox}}/message/{{id}}" class="messagerow-link">
{{{fromHtml}}}
</a>
</td>
<td class="messagerow-subject">
<a href="/webmail/{{mailbox}}/message/{{id}}" class="messagerow-link">
<span class="messagerow-subject-content">
{{#if mailboxName}}
<span class="label label-default">{{mailboxName}}</span>
{{/if}}
{{subject}}{{#if intro}} <span class="text-muted" style="font-weight: normal;">– {{intro}}</span>{{/if}}
</span>
</a>
</td>
<td class="messagerow-info">
<a href="/webmail/{{mailbox}}/message/{{id}}" class="messagerow-link">
{{#if encrypted}}
<span class="glyphicon glyphicon-lock" aria-hidden="true"></span>
{{else}}
{{#if attachments}}
<span class="glyphicon glyphicon-paperclip" aria-hidden="true"></span>
{{/if}}
{{/if}}
</a>
</td>
<td class="messagerow-date">
<a href="/webmail/{{mailbox}}/message/{{id}}" class="messagerow-link">
<span class="datestring-fixed" title="{{date}}">
{{date}}
</span>
</a>
</td>
<td class="messagerow-spacer"></td>
</tr>

+ 0
- 85
webmail/views/partials/navbar.hbs View File

@ -1,85 +0,0 @@
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{#if user}}/webmail{{else}}/{{/if}}">
<img alt="{{serviceName}}" src="/favicon-32x32.png" width="20" height="20">
</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
{{#if user}}
<li {{#if activeWebmail}} class="active" {{/if}}>
<a href="/webmail/">
<span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> Webmail
<span class="badge pull-right unseen-counter-{{inboxId}}" {{#if inboxUnseen}}style="display: block;"{{else}}style="display: none;"{{/if}}>{{inboxUnseen}}</span>
</a>
</li>
<li {{#if activeFilters}} class="active" {{/if}}>
<a href="/account/filters">
<span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Filters
</a>
</li>
<li {{#if activeAutoreply}} class="active" {{/if}}>
<a href="/account/autoreply">
<span class="glyphicon glyphicon-calendar" aria-hidden="true"></span> Autoreply
</a>
</li>
<li {{#if activeHelp}} class="active" {{/if}}>
<a href="/help">
<span class="glyphicon glyphicon glyphicon-question-sign" aria-hidden="true"></span> Help
</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<img src="{{user.gravatar}}" class="profile-image img-circle" width="20" height="20">
{{#if user.name}}
{{user.name}}
{{else}}
{{user.username}}
{{/if}}
<span class="caret"></span></a>
<ul class="dropdown-menu">
<li {{#if activeHome}} class="active" {{/if}}>
<a href="/account/">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account
</a>
</li>
<li {{#if activeSecurity}} class="active" {{/if}}>
<a href="/account/security">
<span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security
</a>
</li>
<li role="separator" class="divider"></li>
<li><a href="/account/logout"><span class="glyphicon glyphicon-log-out" aria-hidden="true"></span> Log out</a></li>
</ul>
</li>
{{else}}
{{#if allowJoin}}
<li {{#if activeCreate}} class="active" {{/if}}>
<a href="/account/create">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span> Create account
</a>
</li>
{{/if}}
<li {{#if activeLogin}} class="active" {{/if}}>
<a href="/account/login">
<span class="glyphicon glyphicon-log-in" aria-hidden="true"></span> Log in
</a>
</li>
{{/if}}
</ul>
</div>
</div>
</nav>

+ 0
- 71
webmail/views/partials/scripts.hbs View File

@ -1,71 +0,0 @@
<script src="/components/underscore/underscore-min.js"></script>
<script src="/components/jquery/dist/jquery.min.js"></script>
<script src="/components/promise-polyfill/dist/promise.min.js"></script>
<script src="/components/moment/min/moment-with-locales.min.js"></script>
<script src="/bootstrap-3.3.7/js/bootstrap.js"></script>
<link href="/components/bootstrap-daterangepicker/daterangepicker.css" rel="stylesheet">
<script src="/components/bootstrap-daterangepicker/daterangepicker.js"></script>
<link href="/components/summernote/dist/summernote.css" rel="stylesheet">
<script src="/components/summernote/dist/summernote.min.js"></script>
<script src="/components/fetch/fetch.js"></script>
<script src="/components/event-source-polyfill/src/eventsource.min.js"></script>
<script src="/components/handlebars/handlebars.min.js"></script>
<script src="/components/favico.js/favico.js"></script>
<script src="/wd.js"></script>
<script type="text/javascript">
$(function() {
$("[rel='tooltip']").tooltip();
});
</script>
{{#if inboxId}}
<script>
var INBOX_ID = '{{inboxId}}';
var INBOX_UNSEEN = {{inboxUnseen}};
var FAVICON = new Favico({
animation:'slide'
});
if(INBOX_UNSEEN){
FAVICON.badge(INBOX_UNSEEN);
}
</script>
{{else}}
<script>
var INBOX_ID = -1;
var INBOX_UNSEEN = 0;
var FAVICON = false;
</script>
{{/if}}
{{#if successlog}}
<script src="/login-key-handler.js"></script>
<script>
$(function() {
// store token about successful auth in this browser
var successlog = {{{successlog}}};
loginKeyHandler.set(successlog.username, successlog.value, 'recovery', successlog.days);
});
</script>
{{/if}}
<script>
window.setTimeout(function(){
var alerts = document.querySelectorAll('.flash-messages');
var elm;
for(var i=0, len = alerts.length; i<len; i++){
elm = alerts[i];
if(elm.parentNode){
elm.parentNode.removeChild(elm);
}
}
}, 10 * 1000);
</script>

+ 0
- 8
webmail/views/partials/searchfield.hbs View File

@ -1,8 +0,0 @@
<form method="get" action="/webmail/search">
<div class="input-group">
<input type="text" class="form-control input" name="query" placeholder="from:val to:val subject:val &quot;phrase&quot; word" value="{{query}}" required>
<span class="input-group-btn">
<button class="btn btn-default btn" type="submit"><span class="glyphicon glyphicon-search" aria-hidden="true"></span></button>
</span>
</div>
</form>

+ 0
- 5
webmail/views/partials/securitymenu.hbs View File

@ -1,5 +0,0 @@
<li role="presentation" class="{{#if secMenu2fa}}active{{/if}}"><a href="/account/security/2fa">Two factor authentication</a></li>
<li role="presentation" class="{{#if secMenuPassword}}active{{/if}}"><a href="/account/security/password">Account Password</a></li>
<li role="presentation" class="{{#if secMenuAsps}}active{{/if}}"><a href="/account/security/asps">Application specific passwords</a></li>
<li role="presentation" class="{{#if secMenuGpg}}active{{/if}}"><a href="/account/security/gpg">GPG Encryption</a></li>
<li role="presentation" class="{{#if secMenuEvents}}active{{/if}}"><a href="/account/security/events">Security events</a></li>

+ 0
- 57
webmail/views/partials/tos.hbs View File

@ -1,57 +0,0 @@
<p>Last updated: January 24, 2018</p>
<p>Please read these Terms and Conditions ("Terms", "Terms and Conditions") carefully before using the http://{{serviceDomain}} website (the "Service") operated by {{serviceName}} ("us", "we", or "our").</p>
<p>Your access to and use of the Service is conditioned on your acceptance of and compliance with these Terms. These Terms apply to all visitors, users and others who access or use the Service.</p>
<p>By accessing or using the Service you agree to be bound by these Terms. If you disagree with any part of the terms then you may not access the Service. Terms and Conditions for {{serviceName}} based on the <a href="https://termsfeed.com/blog/sample-terms-and-conditions-template/">T&amp;C example from TermsFeed</a>.</p>
<h2>Accounts</h2>
<p>When you create an account with us, you must provide us information that is accurate, complete, and current at all times. Failure to do so constitutes a breach of the Terms, which may result in immediate termination of your account on our Service.</p>
<p>You are responsible for safeguarding the password that you use to access the Service and for any activities or actions under your password, whether your password is with our Service or a third-party service.</p>
<p>You agree not to disclose your password to any third party. You must notify us immediately upon becoming aware of any breach of security or unauthorized use of your account.</p>
<h2>Links To Other Web Sites</h2>
<p>Our Service may contain links to third-party web sites or services that are not owned or controlled by {{serviceName}}.</p>
<p>{{serviceName}} has no control over, and assumes no responsibility for, the content, privacy policies, or practices of any third party web sites or services. You further acknowledge and agree that {{serviceName}} shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such web sites or services.</p>
<p>We strongly advise you to read the terms and conditions and privacy policies of any third-party web sites or services that you visit.</p>
<h2>Termination</h2>
<p>We may terminate or suspend access to our Service immediately, without prior notice or liability, for any reason whatsoever, including without limitation if you breach the Terms.</p>
<p>All provisions of the Terms which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability.</p>
<p>We may terminate or suspend your account immediately, without prior notice or liability, for any reason whatsoever, including without limitation if you breach the Terms.</p>
<p>Upon termination, your right to use the Service will immediately cease. If you wish to terminate your account, you may simply discontinue using the Service.</p>
<p>All provisions of the Terms which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability.</p>
<h2>Governing Law</h2>
<p>These Terms shall be governed and construed in accordance with the laws of Estonia, without regard to its conflict of law provisions.</p>
<p>Our failure to enforce any right or provision of these Terms will not be considered a waiver of those rights. If any provision of these Terms is held to be invalid or unenforceable by a court, the remaining provisions of these Terms will remain in effect. These Terms constitute the entire agreement between us regarding our Service, and supersede and replace any prior agreements we might have between us regarding the Service.</p>
<h2>Changes</h2>
<p>We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material we will try to provide at least 30 days notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion.</p>
<p>By continuing to access or use our Service after those revisions become effective, you agree to be bound by the revised terms. If you do not agree to the new terms, please stop using the Service.</p>
<h2>Contact Us</h2>
<p>If you have any questions about these Terms, please contact us.</p>

+ 0
- 13
webmail/views/tos.hbs View File

@ -1,13 +0,0 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Terms and Conditions ("Terms")</h1>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">TOS</h3></div>
<div class="panel-body">
{{>tos}}
</div>
</div>

+ 0
- 134
webmail/views/webmail/audit.hbs View File

@ -1,134 +0,0 @@
<h2 class="sub-header" style="display: flex;">
<div style="flex-grow: 1">
<table class="limited">
<tr class="messagerow-{{message.mailbox}}-{{messageData.id}}">
<td class="message-subject-line">
<span>{{messageData.subject}}</span>
</td>
</tr>
</table>
</div>
<div>
<a href="/webmail/{{mailbox.id}}/message/{{messageData.id}}" class="btn btn-default"><span class="glyphicon glyphicon-menu-left" aria-hidden="true"></span></a>
</div>
</h2>
<p>
Below are displayed timeline events related to the selected message. This includes receive info, forwarding and autoreplies
</p>
{{#each events}}
<dl class="dl-horizontal">
{{#if actionDescription}}
{{#if action}}
<dt>Action</dt>
<dd><strong><span class="text-{{actionLabel}}">{{actionDescription}}</span></strong></dd>
{{/if}}
{{else}}
{{#if action}}
<dt>Action</dt>
<dd><strong>{{action}}</strong></dd>
{{/if}}
{{/if}}
<dt>ID</dt>
<dd><strong>{{id}}{{#if seq}}.{{seq}}{{/if}}</strong></dd>
<dt>Time</dt>
<dd><span class="datestring-fixed" title="{{time}}">{{time}}</span></dd>
{{#if messageId}}
<dt>Message-ID</dt>
<dd>{{messageId}}</dd>
{{/if}}
{{#if from}}
<dt>From</dt>
<dd>{{from}}</dd>
{{/if}}
{{#if to}}
<dt>To</dt>
<dd>{{to}}</dd>
{{/if}}
{{#if targetList}}
<dt>{{#if toTitle}}{{toTitle}}{{else}}Forwarding{{/if}}</dt>
<dd>
{{#each targetList}}
<div><strong>{{../id}}.{{seq}}:</strong> {{text}} <code class="response">{{value}}</code></div>
{{/each}}
</dd>
{{/if}}
{{#if origin}}
<dt>Sending host</dt>
<dd>{{origin}}</dd>
{{/if}}
{{#if src}}
<dt>Local address</dt>
<dd>{{src}}</dd>
{{/if}}
{{#if mx}}
<dt>Destination</dt>
<dd>{{mx}}
{{#if dst}}
[{{dst}}]
{{/if}}
</dd>
{{/if}}
{{#if response}}
<dt>Server response</dt>
<dd><code class="response">{{response}}</code></dd>
{{/if}}
{{#if error}}
<dt>Error message</dt>
<dd><code class="response">{{error}}</code></dd>
{{/if}}
</dl>
{{/each}}
<p>
&nbsp;
</p>
<script>
document.addEventListener('DOMContentLoaded', function() {
var stream = new EventSource('/api/events');
stream.onmessage = function(e) {
var data, row, star, redrawTimer;
try {
data = JSON.parse(e.data);
} catch (E) {
return;
}
switch (data.command) {
case 'COUNTERS': {
if (data.mailbox) {
if(FAVICON && data.mailbox === INBOX_ID){
FAVICON.badge(data.unseen);
}
[].slice.call(document.querySelectorAll('.unseen-counter-' + data.mailbox)).forEach(function(row){
if(data.unseen){
row.style.display = 'block';
row.textContent = data.unseen;
}else {
row.style.display = 'none';
row.textContent = 0;
}
});
}
break;
}
}
};
});
</script>

+ 0
- 46
webmail/views/webmail/create.hbs View File

@ -1,46 +0,0 @@
<h2 class="sub-header"><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> Create folder</h2>
<form method="post" action="/webmail/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
{{> mailbox}}
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Create</button>
</div>
</form>
<script>
document.addEventListener('DOMContentLoaded', function() {
var stream = new EventSource('/api/events');
stream.onmessage = function(e) {
var data, row, star, redrawTimer;
try {
data = JSON.parse(e.data);
} catch (E) {
return;
}
switch (data.command) {
case 'COUNTERS': {
if (data.mailbox) {
if(FAVICON && data.mailbox === INBOX_ID){
FAVICON.badge(data.unseen);
}
[].slice.call(document.querySelectorAll('.unseen-counter-' + data.mailbox)).forEach(function(row){
if(data.unseen){
row.style.display = 'block';
row.textContent = data.unseen;
}else {
row.style.display = 'none';
row.textContent = 0;
}
});
}
break;
}
}
};
});
</script>

+ 0
- 754
webmail/views/webmail/index.hbs View File

@ -1,754 +0,0 @@
<h2 class="sub-header">
{{#if mailbox.editable}}
<div class="pull-right">
<a href="/webmail/{{mailbox.id}}/settings" class="btn btn-default"><span class="glyphicon glyphicon-cog" aria-hidden="true"></span> Settings</a>
</div>
{{/if}}
{{#if mailbox.icon}}
<span class="glyphicon glyphicon-{{mailbox.icon}}" aria-hidden="true"></span>
{{else}}
<span class="glyphicon glyphicon-inbox" aria-hidden="true"></span>
{{/if}}
{{mailbox.name}}
</h2>
<div class="toolbar-container">
<div class="toolbar-main">
<div class="pull-left" style="margin-left: 10px; width: 20px;">
<input type="checkbox" class="toggle-all" />
</div>
<fieldset id="action-toolbar" disabled>
<div class="form-group">
<button class="btn btn-default btn-xs bulk-mark-seen">Mark as Seen</button>
<button class="btn btn-default btn-xs bulk-mark-unseen">Mark as Unseen</button>
<span style="display: inline-block; width: 10px;"></span>
<button class="btn btn-default btn-xs bulk-delete" data-toggle="modal" data-target="#deleteModal"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete</button>
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> Move <span class="caret"></span>
</button>
<ul class="dropdown-menu">
{{#each mailboxes}}
{{#if canMoveTo}}
<li><a href="#" class="bulk-move" data-mailbox="{{id}}" data-mailbox-path="{{path}}" data-toggle="modal" data-target="#moveModal">
{{{prefix}}}
{{#if icon}}
<span class="glyphicon glyphicon-{{icon}}" aria-hidden="true"></span>
{{else}}
<span class="glyphicon glyphicon-triangle-right" aria-hidden="true"></span>
{{/if}}
{{formatted}}
{{{suffix}}}</a></li>
{{/if}}
{{/each}}
</ul>
</div>
</div>
</fieldset>
</div>
<div class="toolbar-search">
{{>searchfield}}
</div>
</div>
<div class="clearfix"></div>
{{#if isTrash}}
<div class="alert alert-info" style="padding: 5px 15px;" role="alert">Messages in Trash folder are deleted permanently after 30 days</div>
{{/if}}
{{#if isJunk}}
<div class="alert alert-info" style="padding: 5px 15px;" role="alert">Messages in Junk Mail folder are deleted permanently after 30 days</div>
{{/if}}
<div class="table-responsive">
<table class="messagelist">
<colgroup>
<col class="messagerow-spacer-col" />
<col class="messagerow-checkbox-col" />
<col class="messagerow-star-col" />
<col class="messagerow-from-col" />
<col class="messagerow-subject-col" />
<col class="messagerow-info-col" />
<col class="messagerow-date-col" />
<col class="messagerow-spacer-col" />
</colgroup>
<tbody>
{{#each messages}}
{{>messagerow}}
{{/each}}
</tbody>
</table>
</div>
<nav aria-label="nav">
<ul class="pager">
{{#if previousCursor}}
<li class="previous"><a href="/webmail/{{mailbox.id}}?previous={{previousCursor}}&amp;page={{previousPage}}&amp;query={{query}}"><span aria-hidden="true">&larr;</span> Newer</a></li>
{{else}}
<li class="previous disabled"><a href="#"><span aria-hidden="true">&larr;</span> Newer</a></li>
{{/if}}
<li style="display: inline-block; padding-top: 7px;">
Page <strong>{{page}}</strong> (<strong>{{startStr}}</strong>–<strong>{{endStr}}</strong> out of <strong>{{resultsStr}}</strong> messages)
</li>
{{#if nextCursor}}
<li class="next"><a href="/webmail/{{mailbox.id}}?next={{nextCursor}}&amp;page={{nextPage}}&amp;query={{query}}">Older <span aria-hidden="true">&rarr;</span></a></li>
{{else}}
<li class="next disabled"><a href="#">Older <span aria-hidden="true">&rarr;</span></a></li>
{{/if}}
</ul>
</nav>
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Delete messages</h4>
</div>
<div class="modal-body">
{{#if skipTrash}}
Are you sure you want to permanently delete selected messages?
{{else}}
Are you sure you want to move selected messages to Trash folder?
{{/if}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="button" class="btn btn-danger bulk-delete-confirm" data-loading-text="Deleting..." >Yes, delete</button>
</div>
</div>
</div>
</div>
<div class="modal" id="moveModal" tabindex="-1" role="dialog" aria-labelledby="moveModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="moveModalLabel">Move messages</h4>
</div>
<div class="modal-body">
Are you sure you want to move selected messages to <span class="bulk-move-path">another folder</span>?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="button" class="btn btn-primary bulk-move-confirm" data-loading-text="Moving..." >Yes, move</button>
</div>
</div>
</div>
</div>
<script id="messagerow-template" type="text/x-handlebars-template">
{{{messageRowTemplate}}}
</script>
<input type="hidden" id="_csrf" value="{{csrfToken}}">
<input type="hidden" id="mailbox" value="{{mailbox.id}}">
<input type="hidden" id="cursor-type" value="{{cursorType}}">
<input type="hidden" id="cursor-value" value="{{cursorValue}}">
<input type="hidden" id="page" value="{{page}}">
<input type="hidden" id="mailbox-type" value="{{mailbox.specialUse}}">
<script>
// star toggle
(function(){
var toggleStar = function(e, elm){
e.preventDefault();
e.stopPropagation();
if(!elm || elm.dataset.status === 'pending'){
return;
}
elm.dataset.status = 'pending';
var flagged = elm.classList.contains('flagged');
var done = function(){
elm.dataset.status = 'done';
}
fetch('/api/toggle/flagged', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: elm.dataset.mailbox,
message: elm.dataset.message,
flagged: !flagged // toggle
})
})
.then(function(res) {
return res.json();
})
.then(function(res) {
if(res.error){
console.error(res.error);
return done();
}
if(flagged){
elm.classList.remove('flagged');
elm.classList.add('unflagged');
elm.querySelector('.glyphicon').classList.remove('glyphicon-star');
elm.querySelector('.glyphicon').classList.add('glyphicon-star-empty');
}else{
elm.classList.remove('unflagged');
elm.classList.add('flagged');
elm.querySelector('.glyphicon').classList.remove('glyphicon-star-empty');
elm.querySelector('.glyphicon').classList.add('glyphicon-star');
}
done();
}).catch(function(err){
console.error(err);
done();
});
};
var setupToggling = function(elm){
elm.addEventListener('click', function(e){
toggleStar(e, elm);
}, false);
}
var starElms = document.querySelectorAll('.message-star');
for(var i=0, len = starElms.length; i<len; i++){
setupToggling(starElms[i]);
}
})();
// checkboxes
document.addEventListener('DOMContentLoaded', function() {
var checkboxes = document.querySelectorAll('.message-checkbox');
var toolbarElm = document.querySelector('#action-toolbar');
var mailboxType = document.querySelector('#mailbox-type');
var toggleAllElm = document.querySelector('.toggle-all');
var isInbox = mailboxType === 'INBOX';
var isSent = mailboxType === '\\Sent';
var isTrash= mailboxType === '\\Trash';
var isJunk = mailboxType === '\\Junk';
var skipTrash = ['\\Trash', '\\Junk'].includes(mailboxType);
var getChecked = function(){
var result = [];
for(var i=0, len = checkboxes.length; i<len; i++){
if(checkboxes[i].checked){
result.push({
elm: checkboxes[i],
message: checkboxes[i].dataset.message,
mailbox: checkboxes[i].dataset.mailbox
});
}
}
return result;
}
var toggleToolbar = function(){
var checked = 0;
for(var i=0, len = checkboxes.length; i<len; i++){
if(checkboxes[i].checked){
checked++;
}
}
if(checked){
toolbarElm.disabled = false;
if(checked === checkboxes.length){
toggleAllElm.checked = true;
}
}else{
toolbarElm.disabled = true;
toggleAllElm.checked = false;
}
};
for(var i=0, len = checkboxes.length; i<len; i++){
checkboxes[i].addEventListener('click', toggleToolbar, false);
checkboxes[i].addEventListener('change', toggleToolbar, false);
}
var findRow = function(elm, level){
level = level || 0;
var parent = elm.parentNode;
if(!parent || level > 10){
return false;
}
if(parent.classList.contains('messagerow')){
return parent;
}
return findRow(parent, level+1);
}
var removeRow = function(id){
var row = document.getElementById('msg_' + id);
if(row && row.parentNode){
row.parentNode.removeChild(row);
}
}
var messagerowSource = document.getElementById('messagerow-template').innerHTML;
var messagerowTemplate = Handlebars.compile(messagerowSource);
// function to redraw email listing
var redrawList = function(done){
var checkedMessages = {};
getChecked().forEach(function(checked){
checkedMessages[checked.message] = true;
});
fetch('/api/list', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: document.getElementById('mailbox').value,
cursorType: document.getElementById('cursor-type').value,
cursorValue: document.getElementById('cursor-value').value,
page: document.getElementById('page').value
})
})
.then(function(res) {
return res.json();
})
.then(function(res) {
if(res.error){
console.error(res.error);
return done();
}
var html = res.results.map(function(message){
return messagerowTemplate(message);
}).join('\n');
document.querySelector('.messagelist tbody').innerHTML = html;
// reset page load handlers
checkboxes = document.querySelectorAll('.message-checkbox');
for(var i=0, len = checkboxes.length; i<len; i++){
if(checkedMessages[checkboxes[i].dataset.message]){
checkboxes[i].checked = true;
}
checkboxes[i].addEventListener('click', toggleToolbar, false);
checkboxes[i].addEventListener('change', toggleToolbar, false);
}
toggleToolbar();
$("[rel='tooltip']").tooltip();
updateFixedDatestrings()
done();
}).catch(function(err){
console.error(err);
done();
});
};
var pendingSeen = false;
var toggleSeen = function(seen){
if(pendingSeen){
return false;
}
var checked = getChecked();
if(!checked.length){
return false;
}
pendingSeen = true;
var groupkeys= [];
var groups = {};
checked.forEach(function(entry){
if(!groups[entry.mailbox]){
groups[entry.mailbox] = [];
groupkeys.push(entry.mailbox);
}
groups[entry.mailbox].push(entry.message);
})
var done = function(){
pendingSeen = false;
}
var batchPos = 0;
var processBatch = function(){
if(batchPos >= groupkeys.length){
return done();
}
var mailbox = groupkeys[batchPos++];
var messages = groups[mailbox];
fetch('/api/toggle/seen', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: mailbox,
message: messages.join(','),
seen: !!seen
})
})
.then(function(res) {
return res.json();
})
.then(function(res) {
if(res.error){
console.error(res.error);
return done();
}
checked.forEach(function(checkbox){
var row = findRow(checkbox.elm);
if(row){
row.classList.remove(seen ? 'message-unseen' : 'message-seen');
row.classList.add(seen ? 'message-seen' : 'message-unseen');
}
})
// continue processing
processBatch();
}).catch(function(err){
console.error(err);
done();
});
}
processBatch();
}
var setUnseen = function(){
toggleSeen(false);
};
var setSeen = function(){
toggleSeen(true);
};
document.querySelector('.bulk-mark-unseen').addEventListener('click', setUnseen, false);
document.querySelector('.bulk-mark-unseen').addEventListener('touch', setUnseen, false);
document.querySelector('.bulk-mark-seen').addEventListener('click', setSeen, false);
document.querySelector('.bulk-mark-seen').addEventListener('touch', setSeen, false);
var toggleAll = function(){
var checked = toggleAllElm.checked;
for(var i=0, len = checkboxes.length; i<len; i++){
checkboxes[i].checked = checked;
}
if(checked && checkboxes.length){
toolbarElm.disabled = false;
}
}
document.querySelector('.toggle-all').addEventListener('click', toggleAll, false);
document.querySelector('.toggle-all').addEventListener('change', toggleAll, false);
var pendingDeleted = false;
var deleteMessage = function(){
if(pendingDeleted){
return false;
}
var checked = getChecked();
if(!checked.length){
return false;
}
pendingDeleted = true;
$('#deleteModal .bulk-delete-confirm').button('loading');
var done = function(){
pendingDeleted = false;
$('#deleteModal .bulk-delete-confirm').button('reset');
$('#deleteModal').modal('hide');
}
var groupkeys= [];
var groups = {};
checked.forEach(function(entry){
if(!groups[entry.mailbox]){
groups[entry.mailbox] = [];
groupkeys.push(entry.mailbox);
}
groups[entry.mailbox].push(entry.message);
})
var deleted = 0;
var batchPos = 0;
var processBatch = function(){
if(batchPos >= groupkeys.length){
if(deleted){
return redrawList(done);
}
return done();
}
var mailbox = groupkeys[batchPos++];
var messages = groups[mailbox];
fetch('/api/delete', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: mailbox,
message: messages.join(',')
})
})
.then(function(res) {
return res.json();
})
.then(function(res) {
if(res.error){
console.error(res.error);
return done();
}
var removeRow = function(id){
var row = document.getElementById('msg_' + id);
if(row && row.parentNode){
row.parentNode.removeChild(row);
}
}
if(res.id && res.id.length){
for(var i=0, len = res.id.length; i<len; i++){
if(res.id[i] && res.id[i][0] && res.id[i][1]){
removeRow(res.id[i][0]);
deleted++;
}
}
}
processBatch();
}).catch(function(err){
console.error(err);
done();
});
}
processBatch();
};
document.querySelector('.bulk-delete-confirm').addEventListener('click', deleteMessage, false);
document.querySelector('.bulk-delete-confirm').addEventListener('touch', deleteMessage, false);
var pendingMove = false;
var moveMessage = function(target){
if(pendingMove){
return false;
}
var checked = getChecked();
if(!checked.length){
return false;
}
pendingMove = true;
$('#moveModal .bulk-move-confirm').button('loading');
var done = function(){
pendingMove = false;
$('#moveModal .bulk-move-confirm').button('reset');
$('#moveModal').modal('hide');
}
var targetMailbox = document.querySelector('.bulk-move-confirm').dataset.mailbox;
var groupkeys= [];
var groups = {};
checked.forEach(function(entry){
if(targetMailbox === entry.mailbox){
// skip
return;
}
if(!groups[entry.mailbox]){
groups[entry.mailbox] = [];
groupkeys.push(entry.mailbox);
}
groups[entry.mailbox].push(entry.message);
})
var moved = 0;
var batchPos = 0;
var processBatch = function(){
if(batchPos >= groupkeys.length){
if(moved){
return redrawList(done);
}
return done();
}
var mailbox = groupkeys[batchPos++];
var messages = groups[mailbox];
fetch('/api/move', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: mailbox,
message: messages.join(','),
target: targetMailbox
})
})
.then(function(res) {
return res.json();
})
.then(function(res) {
if(res.error){
console.error(res.error);
return done();
}
if(res.id && res.id.length){
for(var i=0, len = res.id.length; i<len; i++){
if(res.id[i] && res.id[i][0] && res.id[i][1]){
removeRow(res.id[i][0]);
moved++;
}
}
}
processBatch();
}).catch(function(err){
console.error(err);
done();
});
}
processBatch();
};
$('#moveModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var mailbox = button.data('mailbox'); // Extract info from data-* attributes
var path = button.data('mailbox-path');
$('.bulk-move-path').text(path);
document.querySelector('.bulk-move-confirm').dataset.mailbox = mailbox;
});
document.querySelector('.bulk-move-confirm').addEventListener('click', moveMessage, false);
document.querySelector('.bulk-move-confirm').addEventListener('touch', moveMessage, false);
let checkNewMessages = document.getElementById('page').value === '1' && document.getElementById('mailbox').value;
var stream = new EventSource('/api/events');
stream.onmessage = function(e) {
var data, row, star, redrawTimer;
try {
data = JSON.parse(e.data);
} catch (E) {
return;
}
switch (data.command) {
case 'EXISTS':
if(checkNewMessages === data.mailbox){
clearTimeout(redrawTimer);
redrawTimer = setTimeout(function(){
clearTimeout(redrawTimer);
redrawList(function(){});
}, 200);
}
break;
case 'FETCH': {
row = document.querySelector('.messagerow-'+data.mailbox+'-'+data.uid);
if(!row){
return;
}
if (data.flags) {
star = row.querySelector('.message-star');
if (data.flags.indexOf('\\Flagged')>=0) {
star.classList.remove('unflagged');
star.classList.add('flagged');
star.querySelector('.glyphicon').classList.remove('glyphicon-star-empty');
star.querySelector('.glyphicon').classList.add('glyphicon-star');
} else {
star.classList.remove('flagged');
star.classList.add('unflagged');
star.querySelector('.glyphicon').classList.remove('glyphicon-star');
star.querySelector('.glyphicon').classList.add('glyphicon-star-empty');
}
if (data.flags.indexOf('\\Seen')>=0) {
row.classList.remove('message-unseen');
row.classList.add('message-seen');
} else {
row.classList.remove('message-seen');
row.classList.add('message-unseen');
}
}
break;
}
case 'EXPUNGE': {
row = document.querySelector('.messagerow-'+data.mailbox+'-'+data.uid);
if(!row){
return;
}
if(row.parentNode){
row.parentNode.removeChild(row);
clearTimeout(redrawTimer);
redrawTimer = setTimeout(function(){
clearTimeout(redrawTimer);
redrawList(function(){});
}, 200);
}
}
case 'COUNTERS': {
if (data.mailbox) {
if(FAVICON && data.mailbox === INBOX_ID){
FAVICON.badge(data.unseen);
}
[].slice.call(document.querySelectorAll('.unseen-counter-' + data.mailbox)).forEach(function(row){
if(data.unseen){
row.style.display = 'block';
row.textContent = data.unseen;
}else {
row.style.display = 'none';
row.textContent = 0;
}
});
}
break;
}
}
};
toggleToolbar();
}, false);
</script>

+ 0
- 75
webmail/views/webmail/mailbox.hbs View File

@ -1,75 +0,0 @@
<h2 class="sub-header"><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{mailbox.name}}</h2>
<form method="post" action="/webmail/{{mailbox.id}}/settings">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
{{> mailbox}}
{{#unless isInbox}}
<div class="form-group">
{{#unless isSpecial}}
<div class="pull-right">
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#deleteModal"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete</button>
</div>
{{/unless}}
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> Update</button>
</div>
{{/unless}}
</form>
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Delete folder</h4>
</div>
<div class="modal-body">
Are you sure you want to permanently delete <strong>{{mailbox.name}}</strong> and all its contents?
</div>
<div class="modal-footer">
<form method="post" action="/webmail/{{mailbox.id}}/delete">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger">Yes, delete</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var stream = new EventSource('/api/events');
stream.onmessage = function(e) {
var data, row, star, redrawTimer;
try {
data = JSON.parse(e.data);
} catch (E) {
return;
}
switch (data.command) {
case 'COUNTERS': {
if (data.mailbox) {
if(FAVICON && data.mailbox === INBOX_ID){
FAVICON.badge(data.unseen);
}
[].slice.call(document.querySelectorAll('.unseen-counter-' + data.mailbox)).forEach(function(row){
if(data.unseen){
row.style.display = 'block';
row.textContent = data.unseen;
}else {
row.style.display = 'none';
row.textContent = 0;
}
});
}
break;
}
}
};
});
</script>

+ 0
- 630
webmail/views/webmail/message.hbs View File

@ -1,630 +0,0 @@
<input type="hidden" id="mailbox" value="{{mailbox.id}}" />
<input type="hidden" id="message" value="{{message.id}}" />
<input type="hidden" id="_csrf" value="{{csrfToken}}">
<h2 class="sub-header" style="display: flex;">
<div style="flex-grow: 1">
<table class="limited">
<tr class="messagerow-{{message.mailbox}}-{{message.id}}">
<td class="message-subject-line">
<a href="#" class="message-star {{#if message.flagged}}flagged{{else}}unflagged{{/if}}"
data-mailbox="{{mailbox.id}}" data-message="{{message.id}}"><span
class="glyphicon glyphicon-{{#if message.flagged}}star{{else}}star-empty{{/if}}"
aria-hidden="true"></span></a>
<span>{{message.subject}}</span>
</td>
</tr>
</table>
</div>
<div>
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
<span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> Details <span
class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="/webmail/{{mailbox.id}}/raw/{{message.id}}.eml"><span
class="glyphicon glyphicon-download-alt" aria-hidden="true"></span> Original message</a>
</li>
{{#if message.attachments}}
<li role="separator" class="divider"></li>
{{#each message.attachments}}
<li><a href="/webmail/{{../mailbox.id}}/attachment/{{../message.id}}/{{id}}"
download="{{filename}}"><span class="glyphicon glyphicon-paperclip" aria-hidden="true"></span>
{{filename}} [{{sizeKb}}kB]</a></li>
{{/each}}
{{/if}}
</ul>
</div>
</div>
</h2>
<div class="toolbar-container">
<div class="toolbar-main">
<fieldset id="action-toolbar">
<div class="form-group">
<a href="/webmail/send?action=reply&amp;refMailbox={{mailbox.id}}&amp;refMessage={{message.id}}"
class="btn btn-default btn-xs"><span class="glyphicon glyphicon-send" aria-hidden="true"></span>
Reply</a>
<a href="/webmail/send?action=replyAll&amp;refMailbox={{mailbox.id}}&amp;refMessage={{message.id}}"
class="btn btn-default btn-xs"><span class="glyphicon glyphicon-send" aria-hidden="true"></span>
Reply to all</a>
<a href="/webmail/send?action=forward&amp;refMailbox={{mailbox.id}}&amp;refMessage={{message.id}}"
class="btn btn-default btn-xs"><span class="glyphicon glyphicon-share" aria-hidden="true"></span>
Forward</a>
<span style="display: inline-block; width: 10px;"></span>
<button class="btn btn-default btn-xs bulk-mark-unseen">Mark as Unseen</button>
<span style="display: inline-block; width: 10px;"></span>
<button class="btn btn-default btn-xs bulk-delete" data-toggle="modal" data-target="#deleteModal"><span
class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete</button>
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> Move <span
class="caret"></span>
</button>
<ul class="dropdown-menu">
{{#each mailboxes}}
{{#if canMoveTo}}
<li><a href="#" class="bulk-move" data-mailbox="{{id}}" data-mailbox-path="{{path}}"
data-toggle="modal" data-target="#moveModal">
{{{prefix}}}
{{#if icon}}
<span class="glyphicon glyphicon-{{icon}}" aria-hidden="true"></span>
{{else}}
<span class="glyphicon glyphicon-triangle-right" aria-hidden="true"></span>
{{/if}}
{{formatted}}
{{{suffix}}}</a></li>
{{/if}}
{{/each}}
</ul>
</div>
</div>
</fieldset>
</div>
<div class="toolbar-search">
{{>searchfield}}
</div>
</div>
<div class="clearfix"></div>
{{#each message.info}}
<div>
<strong>{{key}}:</strong>
{{#if icon}}
<span class="glyphicon glyphicon-{{icon}}" aria-hidden="true"></span>
{{/if}}
<span {{#if isDate}} class="datestring" title="{{value}}" {{/if}}>
{{#if isHtml}}{{{value}}}{{else}}{{value}}{{/if}}
</span>
{{#if @first}}
{{#if ../message.securityInfo}}
<a id="extraDetails" tabindex="0" role="button" data-toggle="popover" data-trigger="focus"
title="Delivery details"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span></a>
<div id="extraDetailsContent" style="display: none">
{{#each ../message.securityInfo}}
<div {{#if textClass}} class="{{textClass}}" {{/if}}>
<strong>{{key}}:</strong>
{{#if icon}}
<span class="glyphicon glyphicon-{{icon}}" aria-hidden="true"></span>
{{/if}}
<span {{#if isDate}} class="datestring" title="{{value}}" {{/if}}>
{{#if isHtml}}{{{value}}}{{else}}{{value}}{{/if}}
</span>
</div>
{{/each}}
</div>
{{/if}}
{{/if}}
</div>
{{/each}}
{{#if expires}}
<div class="text-muted">
<strong>Message expires:</strong>
<span class="datestring" title="{{expires}}">
{{expires}}
</span>
</div>
{{/if}}
<div style="margin-bottom: 5px;"></div>
{{#if message.encrypted}}
<div id="encrypted-warning" class="alert alert-warning" role="alert">
<span class="glyphicon glyphicon-lock" aria-hidden="true"></span>
This message is encrypted and can not be displayed. Download the original message <a
href="/webmail/{{mailbox.id}}/raw/{{message.id}}.eml" download="{{message.id}}.eml" class="alert-link">from here
</a> to open it in an e-mail client that is able to read encrypted messages.
Alternatively you can install <a href="https://www.mailvelope.com/" class="alert-link">Mailvelope browser
extension</a> to allow decrypting and displaying messages by {{serviceName}}.
</div>
<div id="mailvelope-loading" class="alert alert-info" role="alert" style="display:none">
<span class="glyphicon glyphicon-lock" aria-hidden="true"></span>
Mailvelope detected. Trying to decrpyt encrypted message...
</div>
<div id="message-content" class="mailvelope"></div>
<script>
document.addEventListener('DOMContentLoaded', function () {
if (typeof mailvelope !== 'undefined') {
mailvelopeLoaded();
} else {
window.addEventListener('mailvelope', mailvelopeLoaded, false);
}
var identifier = 'wildduck';
function getKeyring(callback) {
mailvelope.getKeyring(identifier).then(function (keyring) {
return callback(null, keyring);
}).catch(function (err) {
mailvelope.createKeyring(identifier).then(function (keyring) {
return callback(null, keyring);
}).catch(function (err) {
return callback(err);
});
});
}
function mailvelopeLoaded() {
document.getElementById('encrypted-warning').style.display = 'none';
document.getElementById('mailvelope-loading').style.display = 'block';
var selector = '#message-content';
getKeyring(function (err, keyring) {
if (err) {
document.getElementById('encrypted-warning').style.display = 'block';
document.getElementById('mailvelope-loading').style.display = 'none';
alert('Failed to create keyring. ' + err.message);
return;
}
fetch('/webmail/{{mailbox.id}}/raw/{{message.id}}.eml', {
method: 'get',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include'
})
.then(function (res) {
return res.text();
})
.then(function (text) {
mailvelope.createDisplayContainer(selector, text, keyring, { showExternalContent: true }).then(function () {
//$(selector).addClass('mailvelope').find('.message-part, .part-notice').hide();
//setTimeout(function() { $(window).resize(); }, 10);
document.getElementById('mailvelope-loading').style.display = 'none';
}).catch(function (err) {
document.getElementById('encrypted-warning').style.display = 'block';
document.getElementById('mailvelope-loading').style.display = 'none';
console.error(err);
alert('Message decryption failed: ' + err.message, 'error')
});
}).catch(function (err) {
document.getElementById('encrypted-warning').style.display = 'block';
document.getElementById('mailvelope-loading').style.display = 'none';
console.error(err);
alert('Message decryption failed: ' + err.message)
});
});
}
});
</script>
{{else}}
<div id="message-content" class="iframe-box"></div>
{{#if message.attachments}}
<div class="well">
{{#each message.attachments}}
<a class="btn btn-success btn-sm" href="/webmail/{{../mailbox.id}}/attachment/{{../message.id}}/{{id}}"
role="button" download="{{filename}}"><span class="glyphicon glyphicon-cloud-download"
aria-hidden="true"></span> {{filename}}</a>
{{/each}}
</div>
{{/if}}
<p>
&nbsp;
</p>
<script type="text/javascript" src="/components/DOMPurify/dist/purify.min.js"></script>
<script>
var message = {{{ messageJson }}};
document.addEventListener("DOMContentLoaded", function (event) {
if (message.html) {
var clean = DOMPurify.sanitize(message.html.join('\n'), {
ALLOW_UNKNOWN_PROTOCOLS: true,
WHOLE_DOCUMENT: true,
FORBID_TAGS: ['form']
});
clean = clean.replace(/head>/, 'head><link rel="stylesheet" href="/css/mail.css" /><base target="_parent"><script>function resizeIframe(obj) {obj.style.height = obj.contentWindow.document.body.scrollHeight + "px";}</' + 'script>');
var iframe = document.createElement('iframe');
document.getElementById('message-content').appendChild(iframe);
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(clean);
iframe.contentWindow.document.close();
iframe.contentWindow.addEventListener('load', function () {
iframe.contentWindow.resizeIframe(iframe);
});
iframe.contentWindow.document.addEventListener('DOMContentLoaded', function () {
iframe.contentWindow.resizeIframe(iframe);
});
}
}, false);
</script>
{{/if}}
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Delete message</h4>
</div>
<div class="modal-body">
{{#if isTrash}}
Are you sure you want to permanently delete this message?
{{else}}
Are you sure you want to move this message to Trash folder?
{{/if}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="button" class="btn btn-danger bulk-delete-confirm" data-loading-text="Deleting...">Yes,
delete</button>
</div>
</div>
</div>
</div>
<div class="modal" id="moveModal" tabindex="-1" role="dialog" aria-labelledby="moveModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="moveModalLabel">Move message</h4>
</div>
<div class="modal-body">
Are you sure you want to move this message to <span class="bulk-move-path">another folder</span>?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="button" class="btn btn-primary bulk-move-confirm" data-loading-text="Moving...">Yes,
move</button>
</div>
</div>
</div>
</div>
<script>
// star toggle
(function () {
var toggleStar = function (e, elm) {
e.preventDefault();
e.stopPropagation();
if (!elm || elm.dataset.status === 'pending') {
return;
}
elm.dataset.status = 'pending';
var flagged = elm.classList.contains('flagged');
var done = function () {
elm.dataset.status = 'done';
}
fetch('/api/toggle/flagged', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: elm.dataset.mailbox,
message: elm.dataset.message,
flagged: !flagged // toggle
})
})
.then(function (res) {
return res.json();
})
.then(function (res) {
if (res.error) {
console.error(res.error);
return done();
}
if (flagged) {
elm.classList.remove('flagged');
elm.classList.add('unflagged');
elm.querySelector('.glyphicon').classList.remove('glyphicon-star');
elm.querySelector('.glyphicon').classList.add('glyphicon-star-empty');
} else {
elm.classList.remove('unflagged');
elm.classList.add('flagged');
elm.querySelector('.glyphicon').classList.remove('glyphicon-star-empty');
elm.querySelector('.glyphicon').classList.add('glyphicon-star');
}
done();
}).catch(function (err) {
console.error(err);
done();
});
};
var setupToggling = function (elm) {
elm.addEventListener('click', function (e) {
toggleStar(e, elm);
}, false);
}
var starElms = document.querySelectorAll('.message-star');
for (var i = 0, len = starElms.length; i < len; i++) {
setupToggling(starElms[i]);
}
})();
// checkboxes
document.addEventListener('DOMContentLoaded', function () {
var toolbarElm = document.querySelector('#action-toolbar');
var mailbox = document.getElementById('mailbox').value;
var message = document.getElementById('message').value;
var pendingSeen = false;
var toggleSeen = function (seen) {
if (pendingSeen) {
return false;
}
var done = function () {
pendingSeen = false;
}
fetch('/api/toggle/seen', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: mailbox,
message: message,
seen: !!seen
})
})
.then(function (res) {
return res.json();
})
.then(function (res) {
if (res.error) {
console.error(res.error);
return done();
}
window.location.href = '/webmail/' + mailbox;
}).catch(function (err) {
console.error(err);
done();
});
}
var setUnseen = function () {
toggleSeen(false);
};
var setSeen = function () {
toggleSeen(true);
};
document.querySelector('.bulk-mark-unseen').addEventListener('click', setUnseen, false);
document.querySelector('.bulk-mark-unseen').addEventListener('touch', setUnseen, false);
var pendingDeleted = false;
var deleteMessage = function () {
if (pendingDeleted) {
return false;
}
pendingDeleted = true;
$('#deleteModal .bulk-delete-confirm').button('loading');
var done = function () {
pendingDeleted = false;
$('#deleteModal .bulk-delete-confirm').button('reset');
$('#deleteModal').modal('hide');
}
fetch('/api/delete', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: mailbox,
message: message
})
})
.then(function (res) {
return res.json();
})
.then(function (res) {
if (res.error) {
console.error(res.error);
return done();
}
window.location.href = '/webmail/' + mailbox;
}).catch(function (err) {
console.error(err);
done();
});
};
document.querySelector('.bulk-delete-confirm').addEventListener('click', deleteMessage, false);
document.querySelector('.bulk-delete-confirm').addEventListener('touch', deleteMessage, false);
var pendingMove = false;
var moveMessage = function (target) {
if (pendingMove) {
return false;
}
pendingMove = true;
$('#moveModal .bulk-move-confirm').button('loading');
var done = function () {
pendingMove = false;
$('#moveModal .bulk-move-confirm').button('reset');
$('#moveModal').modal('hide');
}
var targetMailbox = document.querySelector('.bulk-move-confirm').dataset.mailbox;
fetch('/api/move', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: mailbox,
message: message,
target: targetMailbox
})
})
.then(function (res) {
return res.json();
})
.then(function (res) {
if (res.error) {
console.error(res.error);
return done();
}
window.location.href = '/webmail/' + targetMailbox;
}).catch(function (err) {
console.error(err);
done();
});
};
$('#moveModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var mailbox = button.data('mailbox'); // Extract info from data-* attributes
var path = button.data('mailbox-path');
$('.bulk-move-path').text(path);
document.querySelector('.bulk-move-confirm').dataset.mailbox = mailbox;
});
document.querySelector('.bulk-move-confirm').addEventListener('click', moveMessage, false);
document.querySelector('.bulk-move-confirm').addEventListener('touch', moveMessage, false);
var stream = new EventSource('/api/events');
stream.onmessage = function (e) {
var data, row, star, redrawTimer;
try {
data = JSON.parse(e.data);
} catch (E) {
return;
}
switch (data.command) {
case 'FETCH': {
row = document.querySelector('.messagerow-' + data.mailbox + '-' + data.uid);
if (!row) {
return;
}
if (data.flags) {
star = row.querySelector('.message-star');
if (data.flags.indexOf('\\Flagged') >= 0) {
star.classList.remove('unflagged');
star.classList.add('flagged');
star.querySelector('.glyphicon').classList.remove('glyphicon-star-empty');
star.querySelector('.glyphicon').classList.add('glyphicon-star');
} else {
star.classList.remove('flagged');
star.classList.add('unflagged');
star.querySelector('.glyphicon').classList.remove('glyphicon-star');
star.querySelector('.glyphicon').classList.add('glyphicon-star-empty');
}
}
break;
}
case 'COUNTERS': {
if (data.mailbox) {
if (FAVICON && data.mailbox === INBOX_ID) {
FAVICON.badge(data.unseen);
}
[].slice.call(document.querySelectorAll('.unseen-counter-' + data.mailbox)).forEach(function (row) {
if (data.unseen) {
row.style.display = 'block';
row.textContent = data.unseen;
} else {
row.style.display = 'none';
row.textContent = 0;
}
});
}
break;
}
}
};
$('#extraDetails').popover({
content: document.getElementById('extraDetailsContent').innerHTML,
html: true
})
}, false);
</script>

+ 0
- 382
webmail/views/webmail/send.hbs View File

@ -1,382 +0,0 @@
<form method="post" id="send-form" class="form-horizontal" action="/webmail/send" enctype="multipart/form-data">
<input type="hidden" id="_csrf" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="action" value="{{values.action}}">
<input type="hidden" name="refMailbox" value="{{values.refMailbox}}">
<input type="hidden" name="refMessage" value="{{values.refMessage}}">
<input type="hidden" id="mailbox" name="draftMailbox" value="{{values.draftMailbox}}">
<input type="hidden" id="message" name="draftMessage" value="{{values.draftMessage}}">
<input type="hidden" name="draft" value="{{values.draft}}">
<div class="toolbar-container">
<div class="toolbar-main">
<fieldset id="action-toolbar">
<div class="form-group">
<div class="col-sm-offset-1 col-sm-11" style="margin-top: 20px;">
<button class="btn btn-primary btn-xs" type="button" data-toggle="modal" data-target="#sendModal"><span class="glyphicon glyphicon-send" aria-hidden="true"></span> Send message</button>
<button class="btn btn-default btn-xs" type="submit" name="userAction" value="save"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span> Save draft</button>
{{#if values.draft}}
<button class="btn btn-default btn-xs" type="button" data-toggle="modal" data-target="#deleteModal"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Discard Draft</button>
{{/if}}
</div>
</div>
</fieldset>
</div>
</div>
<div id="from-field" class="form-group{{#if errors.from}} has-error{{/if}}" {{#unless fromAddress}}style="display:none"{{/unless}}>
<label for="inputFrom" class="col-sm-1 control-label">From</label>
<div class="col-sm-11">
<select class="form-control" name="from" id="inputFrom">
{{#each addresses}}
<option value="{{id}}" {{#if selected}}selected{{/if}}>
{{#if name}}{{name}} – {{/if}} {{address}}
</option>
{{/each}}
</select>
{{#if errors.from}}
<span class="help-block">{{errors.from}}</span>
{{/if}}
</div>
</div>
<div class="form-group{{#if errors.to}} has-error{{/if}}">
<label for="inputTo" class="col-sm-1 control-label">To</label>
<div class="col-sm-11">
<input type="text" class="form-control" name="to" id="inputTo" value="{{values.to}}" placeholder="Recipient">
{{#if errors.to}}
<span class="help-block">{{errors.to}}</span>
{{/if}}
</div>
</div>
<div id="cc-field" class="form-group{{#if errors.cc}} has-error{{/if}}" {{#unless values.cc}}style="display:none"{{/unless}}>
<label for="inputCc" class="col-sm-1 control-label">Cc</label>
<div class="col-sm-11">
<input type="text" class="form-control" name="cc" id="inputCc" value="{{values.cc}}" placeholder="Cc">
{{#if errors.cc}}
<span class="help-block">{{errors.cc}}</span>
{{/if}}
</div>
</div>
<div id="bcc-field" class="form-group{{#if errors.bcc}} has-error{{/if}}" {{#unless values.bcc}}style="display:none"{{/unless}}>
<label for="inputBcc" class="col-sm-1 control-label">Bcc</label>
<div class="col-sm-11">
<input type="text" class="form-control" name="bcc" id="inputBcc" value="{{values.bcc}}" placeholder="Bcc">
{{#if errors.bcc}}
<span class="help-block">{{errors.bcc}}</span>
{{/if}}
</div>
</div>
<div class="text-right" style="margin-top: -10px; margin-bottom: 10px;">
<a href="#" id="link-add-from" {{#if fromAddress}}style="display:none"{{/if}}>From</a>
<a href="#" id="link-add-cc" {{#if values.cc}}style="display:none"{{/if}}>Cc</a>
<a href="#" id="link-add-bcc" {{#if values.bcc}}style="display:none"{{/if}}>Bcc</a>
</div>
<div class="form-group{{#if errors.subject}} has-error{{/if}}">
<label for="inputSubject" class="col-sm-1 control-label">Subject</label>
<div class="col-sm-11">
<input type="text" class="form-control" id="inputSubject" name="subject" value="{{values.subject}}" placeholder="Message subject">
{{#if errors.subject}}
<span class="help-block">{{errors.subject}}</span>
{{/if}}
</div>
</div>
<div class="form-group{{#if errors.editordata}} has-error{{/if}}">
<div class="col-sm-12">
<textarea id="summernote" name="editordata"></textarea>
{{#if errors.editordata}}
<span class="help-block">{{errors.editordata}}</span>
{{/if}}
</div>
</div>
<div id="attachment-field" class="form-group{{#if errors.attachment}} has-error{{/if}}">
<div class="col-sm-12">
<label for="inputAttachment">Attachments</label>
<input id="input-attachment" name="attachment" class="form-control file" type="file" multiple>
{{#if errors.attachment}}
<span class="help-block">{{errors.attachment}}</span>
{{/if}}
</div>
</div>
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Delete draft</h4>
</div>
<div class="modal-body">
Are you sure you want to permanently delete this draft?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="button" class="btn btn-danger bulk-delete-confirm" data-loading-text="Deleting..." >Yes, delete</button>
</div>
</div>
</div>
</div>
<div class="modal" id="sendModal" tabindex="-1" role="dialog" aria-labelledby="sendModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="sendModalLabel">Send message</h4>
</div>
<div class="modal-body">
Are you sure you want to send this message?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" name="userAction" value="send" class="btn btn-primary bulk-send-confirm" data-loading-text="Sending..." >Yes, send</button>
</div>
</div>
</div>
</div>
</form>
<script type="text/javascript" src="/components/DOMPurify/dist/purify.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
var messageHtml = {{{messageHtml}}};
if (messageHtml && messageHtml.length && /^\s*$/.test(document.getElementById('summernote').value)) {
// make sure that server timestamps get converted to browser time strings
// {%DATE ... %}
messageHtml = messageHtml.map(function(html){
return html.replace(/\{&DATE ([^&]+)&\}/g, function(m, d){
return moment(d.trim()).format('LLLL');
});
});
var clean = DOMPurify.sanitize(messageHtml.join('\n'), {
ALLOW_UNKNOWN_PROTOCOLS: true,
WHOLE_DOCUMENT: false,
FORBID_TAGS: ['form', 'style']
});
{{#unless keepHtmlAsIs}}
clean = '<br/><br/>\n<blockquote>' + clean + '</blockquote>';
{{/unless}}
document.getElementById('summernote').value = clean;
}
$('#summernote').summernote({
toolbar: [
// [groupName, [list of button]]
['style', ['bold', 'italic', 'underline', 'clear']],
['fontsize', ['fontsize']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']]
],
height: 300
});
var linkAddFrom = document.getElementById('link-add-from');
var linkAddCc = document.getElementById('link-add-cc');
var linkAddBcc = document.getElementById('link-add-bcc');
var showFrom = function(){
document.getElementById('from-field').style.display = 'block';
linkAddFrom.style.display = 'none';
document.getElementById('inputFrom').focus();
};
var showCc = function(){
document.getElementById('cc-field').style.display = 'block';
linkAddCc.style.display = 'none';
document.getElementById('inputCc').focus();
};
var showBcc = function(){
document.getElementById('bcc-field').style.display = 'block';
linkAddBcc.style.display = 'none';
document.getElementById('inputBcc').focus();
};
linkAddFrom.addEventListener('click', showFrom, false);
linkAddFrom.addEventListener('touch', showFrom, false);
linkAddCc.addEventListener('click', showCc, false);
linkAddCc.addEventListener('touch', showCc, false);
linkAddBcc.addEventListener('click', showBcc, false);
linkAddBcc.addEventListener('touch', showBcc, false);
}, false);
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var stream = new EventSource('/api/events');
stream.onmessage = function(e) {
var data, row, star, redrawTimer;
try {
data = JSON.parse(e.data);
} catch (E) {
return;
}
switch (data.command) {
case 'COUNTERS': {
if (data.mailbox) {
if(FAVICON && data.mailbox === INBOX_ID){
FAVICON.badge(data.unseen);
}
[].slice.call(document.querySelectorAll('.unseen-counter-' + data.mailbox)).forEach(function(row){
if(data.unseen){
row.style.display = 'block';
row.textContent = data.unseen;
}else {
row.style.display = 'none';
row.textContent = 0;
}
});
}
break;
}
}
};
});
</script>
<script>
// star toggle
(function(){
var toggleStar = function(e, elm){
e.preventDefault();
e.stopPropagation();
if(!elm || elm.dataset.status === 'pending'){
return;
}
elm.dataset.status = 'pending';
var flagged = elm.classList.contains('flagged');
var done = function(){
elm.dataset.status = 'done';
}
fetch('/api/toggle/flagged', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: elm.dataset.mailbox,
message: elm.dataset.message,
flagged: !flagged // toggle
})
})
.then(function(res) {
return res.json();
})
.then(function(res) {
if(res.error){
console.error(res.error);
return done();
}
if(flagged){
elm.classList.remove('flagged');
elm.classList.add('unflagged');
elm.querySelector('.glyphicon').classList.remove('glyphicon-star');
elm.querySelector('.glyphicon').classList.add('glyphicon-star-empty');
}else{
elm.classList.remove('unflagged');
elm.classList.add('flagged');
elm.querySelector('.glyphicon').classList.remove('glyphicon-star-empty');
elm.querySelector('.glyphicon').classList.add('glyphicon-star');
}
done();
}).catch(function(err){
console.error(err);
done();
});
};
var setupToggling = function(elm){
elm.addEventListener('click', function(e){
toggleStar(e, elm);
}, false);
}
var starElms = document.querySelectorAll('.message-star');
for(var i=0, len = starElms.length; i<len; i++){
setupToggling(starElms[i]);
}
})();
// toolbar buttons
document.addEventListener('DOMContentLoaded', function() {
var mailbox = document.getElementById('mailbox').value;
var message = document.getElementById('message').value;
var pendingDeleted = false;
var deleteMessage = function(){
if(pendingDeleted){
return false;
}
pendingDeleted = true;
$('#deleteModal .bulk-delete-confirm').button('loading');
var done = function(){
pendingDeleted = false;
$('#deleteModal .bulk-delete-confirm').button('reset');
$('#deleteModal').modal('hide');
}
fetch('/api/delete', {
method: 'post',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
_csrf: document.getElementById('_csrf').value,
mailbox: mailbox,
message: message
})
})
.then(function(res) {
return res.json();
})
.then(function(res) {
if(res.error){
console.error(res.error);
return done();
}
window.location.href = '/webmail/' + mailbox;
}).catch(function(err){
console.error(err);
done();
});
};
document.querySelector('.bulk-delete-confirm').addEventListener('click', deleteMessage, false);
document.querySelector('.bulk-delete-confirm').addEventListener('touch', deleteMessage, false);
}, false);
</script>

Loading…
Cancel
Save