diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 449b278f8..c2c2f5e82 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -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)'; diff --git a/skins/larry/images/zen-form-sprites.png b/skins/larry/images/zen-form-sprites.png new file mode 100755 index 000000000..8cd36efe0 Binary files /dev/null and b/skins/larry/images/zen-form-sprites.png differ diff --git a/skins/larry/includes/footer.html b/skins/larry/includes/footer.html index 6cd3e62d1..0f00d1fae 100644 --- a/skins/larry/includes/footer.html +++ b/skins/larry/includes/footer.html @@ -7,6 +7,8 @@ var UI = new rcube_mail_ui(); $(document).ready(function(){ UI.set('errortitle', ''); UI.set('toggleoptions', ''); + UI.set('exitfullscreen', ''); + UI.set('switchtheme', ''); UI.init(); }); diff --git a/skins/larry/mail.css b/skins/larry/mail.css index 7eb324254..a78bfe052 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -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; diff --git a/skins/larry/templates/compose.html b/skins/larry/templates/compose.html index 04a987f89..dfac0e4ca 100644 --- a/skins/larry/templates/compose.html +++ b/skins/larry/templates/compose.html @@ -3,6 +3,7 @@ <roundcube:object name="pagetitle" /> + @@ -169,6 +170,7 @@
+

@@ -217,6 +219,8 @@
+ + diff --git a/skins/larry/ui.js b/skins/larry/ui.js index 153abdc21..463e95611 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -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); + $('

').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')](); } diff --git a/skins/larry/zen-form.css b/skins/larry/zen-form.css new file mode 100755 index 000000000..3e8b8f6e4 --- /dev/null +++ b/skins/larry/zen-form.css @@ -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; +} diff --git a/skins/larry/zen-form.js b/skins/larry/zen-form.js new file mode 100755 index 000000000..b75f15ec1 --- /dev/null +++ b/skins/larry/zen-form.js @@ -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('
'); + } + + }, // 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 = $('
', { + 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: '
' + }).children(); + + $select.append($options.clone()); + + $.each($options, function (i, option) { + + App.env.createObject('a', { + href: '#', + html: '' + $(option).text() + '' , + '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: ' Exit Zen Mode' + }, function () { + App.env.destroy($elements); + }); + + // Add theme switch button + App.env.addObject('a', { + class: 'zen-forms-theme-switch', + html: ' 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);