Add Zen mode to message compose body (#1489198) using the Idered/zen-form jQuery plugin

dev-zenmode
Thomas Bruederli 10 years ago
parent 6459944860
commit 29f7b272a5

@ -300,6 +300,11 @@ $labels['addbcc'] = 'Add Bcc';
$labels['addreplyto'] = 'Add Reply-To';
$labels['addfollowupto'] = 'Add Followup-To';
// zen mode labels
$labels['editfullscreen'] = 'Edit in fullscreen';
$labels['exitfullscreen'] = 'Exit fullscreen mode';
$labels['switchtheme'] = 'Switch theme';
// mdn
$labels['mdnrequest'] = 'The sender of this message has asked to be notified when you read this message. Do you wish to notify the sender?';
$labels['receiptread'] = 'Return Receipt (read)';

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

@ -7,6 +7,8 @@ var UI = new rcube_mail_ui();
$(document).ready(function(){
UI.set('errortitle', '<roundcube:label name="errortitle" quoting="javascript" />');
UI.set('toggleoptions', '<roundcube:label name="toggleadvancedoptions" quoting="javascript" />');
UI.set('exitfullscreen', '<roundcube:label name="exitfullscreen" quoting="javascript" />');
UI.set('switchtheme', '<roundcube:label name="switchtheme" quoting="javascript" />');
UI.init();
});

@ -1298,6 +1298,21 @@ div.message-partheaders .headers-table td.header {
bottom: 42px;
}
#composebodycontainer .go-zen {
position: absolute;
top: 6px;
right: 4px;
}
#composebodycontainer .icon-go-zen {
display: inline-block;
width: 16px;
height: 14px;
background: url('data:image/gif;base64,R0lGODlhDgAMAPABAHl5ef///yH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAQAsAAAAAA4ADAAAAh+EEaln6t+YRHGegHB2bW3PfUk3itqoWdSqns8rwUcBADs=') 1px 1px no-repeat;
text-indent: -5000px;
overflow: hidden;
}
#composebody {
position: absolute;
top: 0;

@ -3,6 +3,7 @@
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/zen-form.css" />
<roundcube:if condition="config:enable_spellcheck" />
<link rel="stylesheet" type="text/css" href="/googiespell.css" />
<roundcube:endif />
@ -169,6 +170,7 @@
<div id="composebodycontainer">
<label for="composebody" class="voice"><roundcube:label name="arialabelmessagebody" /></label>
<roundcube:object name="composeBody" id="composebody" form="form" cols="70" rows="20" tabindex="1" />
<a href="#" class="go-zen" title="<roundcube:label name='editfullscreen' />"><span class="icon-go-zen"><roundcube:label name="editfullscreen" /></span></a>
</div>
<div id="compose-attachments" class="rightcol" role="region" aria-labelledby="aria-label-composeattachments">
<h2 id="aria-label-composeattachments" class="voice"><roundcube:label name="attachments" /></h2>
@ -217,6 +219,8 @@
</ul>
</div>
<script type="text/javascript" src="/zen-form.js"></script>
<roundcube:include file="/includes/footer.html" />
</body>

@ -212,6 +212,25 @@ function rcube_mail_ui()
new rcube_splitter({ id:'composesplitterv', p1:'#composeview-left', p2:'#composeview-right',
orientation:'v', relative:true, start:206, min:170, size:12, render:layout_composeview }).init();
// enable zen-mode for message body
if ($.fn.zenForm) {
$('#composebody').zenForm({ theme: 'light' })
.on('zf-initialized', function(event, zenbox) {
var subject = $('#compose-subject').val(),
$zenbox = $(zenbox);
$('<h2>').addClass('zen-forms-subject')
.text(subject ? subject : rcmail.gettext('nosubject'))
.insertBefore('.zen-forms-close-button', zenbox);
$zenbox.find('.zen-forms-close-button').attr('title', env.exitfullscreen)
$zenbox.find('.zen-forms-theme-switch').attr('title', env.switchtheme)
$zenbox.find('.input').first().focus();
})
.on('zf-destroyed', function(event) {
$('#composebody').focus();
});
}
}
else if (rcmail.env.action == 'list' || !rcmail.env.action) {
var previewframe = $('#mailpreviewframe').is(':visible');
@ -497,7 +516,7 @@ function rcube_mail_ui()
form.css('overflow', ovflw > 0 ? 'auto' : 'hidden');
w = body.parent().width() - 5;
h = body.parent().height() - 8;
h = body.parent().height() - 16;
body.width(w).height(h);
$('#composebodycontainer > div').width(w+8);
@ -508,6 +527,9 @@ function rcube_mail_ui()
var abooks = $('#directorylist');
$('#compose-contacts .scroller').css('top', abooks.position().top + abooks.outerHeight());
// hide zen-mode switch in HTML mode
$('a.go-zen')[($('#composebody_ifr').is(':visible') ? 'hide' : 'show')]();
}

@ -0,0 +1,340 @@
/* ================================== *\
* Zen Form
* ================================== */
.zen-forms {
font: normal 18px/1.2 "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif;
position: fixed;
z-index: 99999;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 20px 40px 20px 20px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.zen-forms-body-wrap {
width: 0;
height: 0;
overflow: hidden;
}
body {
max-width: 100%;
}
.zen-forms-input-wrap {
max-width: 800px;
position: relative;
margin: 0 auto;
height: 90%;
}
/**
* Buttons style
*/
.zen-forms-header {
max-width: 800px;
margin: 0 auto 16px auto;
}
.zen-forms-header .zen-forms-subject {
float: left;
display: inline;
margin: 0;
color: #808080;
font-size: 16px;
}
.zen-forms-close-button,
.zen-forms-theme-switch {
float: right;
position: relative;
display: inline-block;
cursor: pointer;
font-size: .8em;
padding: 0 .5em;
vertical-align: top;
width: 18px;
height: 18px;
text-indent: -5000px;
overflow: hidden;
white-space: nowrap;
}
.zen-icon {
background-image: url(images/zen-form-sprites.png);
display: inline-block;
line-height: 1;
position: absolute;
zoom: 1;
width: 16px;
height: 16px;
top: 1px;
left: 1px;
}
.zen-icon:before {
content: "";
display: block;
width: 0;
height: 100%;
}
.light-theme .zen-forms-close-button:hover .zen-icon,
.zen-icon--close {
background-position: -16px 0;
}
.light-theme .zen-forms-theme-switch:hover .zen-icon,
.zen-icon--theme {
background-position: 0 0;
}
.light-theme .zen-icon--close,
.zen-forms-close-button:hover .zen-icon {
background-position: -16px -16px;
}
.light-theme .zen-icon--theme,
.zen-forms-theme-switch:hover .zen-icon {
background-position: 0 -16px;
}
/**
* Inputs basic style
*/
.zen-forms-header:after,
.zen-forms-input-wrap:after {
clear: both;
content: '';
display: table;
margin-bottom: 2px;
}
.zen-forms .input {
box-shadow: none;
background: none;
text-shadow: none;
padding: 7px 0;
width: 100%;
height: 100%;
border-radius: 0;
border: none;
margin: 0;
cursor: pointer;
font: inherit;
}
.zen-forms .input:focus {
cursor: text;
}
.zen-forms .select {
display: none;
}
.zen-forms .custom-select-wrap + label {
padding: 8px 3px;
position: static;
display: inline-block;
width: auto;
float: left;
}
.zen-forms .custom-select-wrap {
display: inline-block;
vertical-align: top;
max-width: 100%;
}
.zen-forms .custom-select {
border-radius: 5px;
top: 0;
right: 0;
left: 0;
overflow: auto;
max-height: 300px;
}
.zen-forms .custom-select a {
display: inline-block;
display: block;
text-decoration: none;
padding: 6px 10px;
display: none;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.zen-forms .custom-select span {
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.zen-forms .custom-select.is-open a,
.zen-forms .custom-select .selected {
position: static;
display: block;
}
.zen-forms textarea {
resize: none;
}
.zen-forms label {
cursor: pointer;
padding: 8px 8px 8px 0;
display: inline-block;
vertical-align: top;
font: inherit;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.zen-forms .input + label {
position: absolute;
display: none;
top: 0;
width: 100%;
}
.zen-forms .empty + label {
display: block;
}
.zen-forms .input:focus + label {
display: none;
}
@media (min-width: 1024px) {
.zen-forms .input:focus + label {
display: block;
background: #000;
font-size: .8em;
padding: 0 .5em;
line-height: 2;
border-radius: 3px;
margin: 5px 15px 0 0;
right: 100%;
width: auto;
white-space: nowrap;
color: #fff;
opacity: .5;
}
.zen-forms .input:focus + label:after {
content: '';
top: 50%;
left: 100%;
width: 0px;
height: 0px;
margin-top: -4px;
position: absolute;
border-style: solid;
border-width: 4px 0 4px 4px;
border-color: transparent transparent transparent #000000;
}
}
.zen-forms :focus {
outline-color: transparent;
outline-style: none;
}
/**
* Dark theme
*/
.zen-forms {
background: #151a1c;
}
.zen-forms .zen-forms-close-button,
.zen-forms .zen-forms-theme-switch {
color: #768991;
}
.zen-forms .zen-forms-close-button:hover,
.zen-forms .zen-forms-theme-switch:hover {
color: #707071;
}
.zen-forms label {
color: #415056;
}
.zen-forms .input {
color: #768991;
}
.zen-forms .custom-select {
background-color: #151a1c;
border: 2px solid #0f1314;
}
.zen-forms .custom-select a {
color: #768991;
}
.zen-forms .custom-select.is-open a:hover {
background-color: #181e20;
}
.zen-forms .custom-select.is-open .selected {
background-color: #0f1314;
}
/**
* Light theme
*/
.zen-forms.light-theme {
background: #fefefe;
}
.zen-forms.light-theme .zen-forms-close-button,
.zen-forms.light-theme .zen-forms-theme-switch {
color: #707071;
}
.zen-forms.light-theme .zen-forms-close-button:hover,
.zen-forms.light-theme .zen-forms-theme-switch:hover {
color: #768991;
}
.zen-forms.light-theme label {
color: #959697;
}
.zen-forms.light-theme .input {
color: #707071;
}
.zen-forms.light-theme .custom-select {
background-color: #fefefe;
border: 2px solid #e5e5e5;
}
.zen-forms.light-theme .custom-select a {
color: #707071;
}
.zen-forms.light-theme .custom-select.is-open a:hover {
background-color: #f5f5f5;
}
.zen-forms.light-theme .custom-select.is-open .selected {
background-color: #e5e5e5;
}

@ -0,0 +1,392 @@
/** Zen Forms 1.0.3 | MIT License | git.io/zen-form */
(function ($) {
$.fn.zenForm = function (settings) {
settings = $.extend({
trigger: '.go-zen',
theme: 'dark'
}, settings);
/**
* Helper functions
*/
var Utils = {
/**
* (Un)Wrap body content to hide overflow
*/
bodyWrap: function () {
var $body = $('body'),
$wrap = $body.children('.zen-forms-body-wrap');
if ($wrap.length) {
$wrap.children().unwrap();
} else {
$body.wrapInner('<div class="zen-forms-body-wrap"/>');
}
}, // bodyWrap
/**
* Watch inputs and add "empty" class if needed
*/
watchEmpty: function () {
App.Environment.find('input, textarea, select').each(function () {
$(this).on('change', function () {
$(this)[$(this).val() ? 'removeClass' : 'addClass']('empty');
}).trigger('change');
});
},
/**
* Custom styled selects
*/
customSelect: function ($select, $customSelect) {
var $selected;
$customSelect.on('click', function (event) {
event.stopPropagation();
$selected = $customSelect.find('.selected');
$customSelect.toggleClass('is-open');
if ($customSelect.hasClass('is-open')) {
$customSelect.scrollTop(
$selected.position().top - $selected.outerHeight()
);
}
}).find('a').on('click', function () {
$(this).addClass('selected').siblings().removeClass('selected');
$select.val($(this).data('value'));
});
}, // customSelect
/**
* Hide any elements(mostly selects) when clicked outside them
*/
manageSelects: function () {
$(document).on('click', function () {
$('.is-open').removeClass('is-open');
});
}, // manageSelects
/**
* Hide any elements(mostly selects) when clicked outside them
*/
focusFirst: function () {
var $first = App.Environment.find('input').first();
// we need to re-set value to remove focus selection
$first.focus().val($first.val());
} // focusFirst
}, // Utils
/**
* Core functionality
*/
App = {
/**
* Orginal form element
*/
Form: null,
/**
* Wrapper element
*/
Environment: null,
/**
* Functions to create and manipulate environment
*/
env: {
/**
* Object where elements created with App.env.addObject are appended
*/
wrapper: null,
create: function () {
// Callback: zf-initialize
App.Form.trigger('zf-initialize');
Utils.bodyWrap();
App.Environment = $('<div>', {
class: 'zen-forms' + (settings.theme == 'dark' ? '' : ' light-theme')
}).hide().appendTo('body').fadeIn(200);
// ESC to exit. Thanks @ktmud
$('body').on('keydown', function (event) {
if (event.which == 27)
App.env.destroy($elements);
});
return App.Environment;
}, // create
/**
* Update orginal inputs with new values and destroy Environment
*/
destroy: function ($elements) {
// Callback: zf-destroy
App.Form.trigger('zf-destroy', App.Environment);
$('body').off('keydown');
// Update orginal inputs with new values
$elements.each(function (i) {
var $el = $('#zen-forms-input' + i);
if ($el.length) {
$(this).val($el.val());
}
});
Utils.bodyWrap();
// Hide and remove Environment
App.Environment.fadeOut(200, function () {
App.env.wrapper = null;
App.Environment.remove();
});
// Callback: zf-destroyed
App.Form.trigger('zf-destroyed');
}, // destroy
/**
* Append inputs, textareas to Environment
*/
add: function ($elements) {
var $el, $label, value, id, ID, label;
$elements.each(function (i) {
App.env.wrapper = App.env.createObject('div', {
class: 'zen-forms-input-wrap'
}).appendTo(App.Environment);
$el = $(this);
value = $el.val();
id = $el.attr('id');
ID = 'zen-forms-input' + i;
label = $el.data('label') || $("label[for=" + id + "]").text() || $el.attr('placeholder') || '';
// Exclude specified elements
if ($.inArray( $el.attr('type'), ['checkbox', 'radio', 'submit']) == -1) {
if ($el.is('input') )
App.env.addInput($el, ID, value);
else if ($el.is('select') )
App.env.addSelect($el, ID);
else
App.env.addTextarea($el, ID, value);
$label = App.env.addObject('label', {
for: ID,
text: label
});
if ($el.is('select') )
$label.prependTo(App.env.wrapper);
}
});
// Callback: zf-initialized
App.Form.trigger('zf-initialized', App.Environment);
}, // add
addInput: function ($input, ID, value) {
return App.env.addObject('input', {
id: ID,
value: value,
class: 'input',
type: $input.attr('type')
});
}, // addInput
addTextarea: function ($textarea, ID, value) {
return App.env.addObject('textarea', {
id: ID,
text: value,
rows: 5,
class: 'input'
});
}, // addTextarea
addSelect: function ($orginalSelect, ID) {
var $select = App.env.addObject('select', {
id: ID,
class: 'select'
}),
$options = $orginalSelect.find('option'),
$customSelect = App.env.addObject('div', {
class: 'custom-select-wrap',
html: '<div class="custom-select"></div>'
}).children();
$select.append($options.clone());
$.each($options, function (i, option) {
App.env.createObject('a', {
href: '#',
html: '<span>' + $(option).text() + '</span>' ,
'data-value': $(option).attr('value'),
class: $(option).prop('selected') ? 'selected' : ''
}).appendTo($customSelect);
});
$select.val($orginalSelect.val());
Utils.customSelect($select, $customSelect);
return $customSelect;
}, // addSelect
/**
* Wrapper for creating jQuery objects
*/
createObject: function (type, params, fn, fnMethod) {
return $('<'+type+'>', params).on(fnMethod || 'click', fn);
}, // createObject
/**
* Wrapper for adding jQuery objects to wrapper
*/
addObject: function (type, params, fn, fnMethod) {
return App.env.createObject(type, params, fn, fnMethod).appendTo(App.env.wrapper || App.Environment);
}, // addObject
switchTheme: function () {
App.Environment.toggleClass('light-theme');
} // switchTheme
}, // env
zen: function ($elements) {
// Create environment
App.env.create();
// Add wrapper div for close and theme buttons
App.env.wrapper = App.env.createObject('div', {
class: 'zen-forms-header'
}).appendTo(App.Environment);
// Add close button
App.env.addObject('a', {
class: 'zen-forms-close-button',
html: '<i class="zen-icon zen-icon--close"></i> Exit Zen Mode'
}, function () {
App.env.destroy($elements);
});
// Add theme switch button
App.env.addObject('a', {
class: 'zen-forms-theme-switch',
html: '<i class="zen-icon zen-icon--theme"></i> Switch theme'
}, function () {
App.env.switchTheme();
});
// Add inputs and textareas from form
App.env.add($elements);
// Additional select functionality
Utils.manageSelects();
// Select first input
Utils.focusFirst();
// Add .empty class for empty inputs
Utils.watchEmpty();
} // zen
}; // App
App.Form = $(this);
var $elements = App.Form.is('form') ? App.Form.find('input, textarea, select') : App.Form;
$(settings.trigger).on('click', function (event) {
event.preventDefault();
App.zen($elements);
});
// Command: destroy
App.Form.on('destroy', function () {
App.env.destroy($elements);
});
// Command: init
App.Form.on('init', function () {
App.zen($elements);
});
return this;
};
})(jQuery);
Loading…
Cancel
Save