diff --git a/Gruntfile.js b/Gruntfile.js index de688b51d..cd5387b35 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -248,7 +248,7 @@ module.exports = function (grunt) { "dev/Common/Knockout.js", "dev/Common/LinkBuilder.js", "dev/Common/Plugins.js", - "dev/Common/HtmlEditor.js", + "dev/Common/HtmlEditorWrapper.js", "dev/Common/Selector.js", "dev/Storages/LocalStorages/CookieDriver.js", @@ -467,10 +467,12 @@ module.exports = function (grunt) { grunt.registerTask('default', ['less', 'concat', 'cssmin', 'jshint', 'rlmin']); grunt.registerTask('build', ['default', 'rlmin', 'rainloop', 'compress:build', 'md5:build', 'rainloop-clear']); + grunt.registerTask('fast', ['less', 'concat']); // aliases grunt.registerTask('u', ['uglify']); grunt.registerTask('h', ['jshint']); grunt.registerTask('b', ['build']); + grunt.registerTask('f', ['fast']); grunt.registerTask('w', ['default', 'watch']); }; diff --git a/dev/Boots/RainLoopApp.js b/dev/Boots/RainLoopApp.js index 2385bb020..852e8b40c 100644 --- a/dev/Boots/RainLoopApp.js +++ b/dev/Boots/RainLoopApp.js @@ -809,11 +809,6 @@ RainLoopApp.prototype.bootstart = function () kn.startScreens([MailBoxScreen, SettingsScreen]); - // setup maito protocol - $document.on('mousedown', '#rl-center a', function (oEvent) { - return !(oEvent && 3 !== oEvent['which'] && RL.mailToHelper($(this).attr('href'))); - }); - if (bGoogle || bFacebook || bTwitter) { RL.socialUsers(true); diff --git a/dev/Common/HtmlEditor.js b/dev/Common/HtmlEditor.js deleted file mode 100644 index a3a67bfc1..000000000 --- a/dev/Common/HtmlEditor.js +++ /dev/null @@ -1,897 +0,0 @@ -/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */ - -/** - * @constructor - */ -function HtmlEditor(oTextAreaElem, oHtmlAreaElem, oToolBarElement, oOptions) -{ - var - oDefOptions = { - 'DisableHtml': false, - 'onSwitch': false, - 'LangSwitcherConferm': 'EDITOR_TEXT_SWITCHER_CONFIRM', - 'LangSwitcherTextLabel': 'EDITOR_SWITCHER_TEXT_LABEL', - 'LangSwitcherHtmlLabel': 'EDITOR_SWITCHER_HTML_LABEL' - } - ; - - this.bIe = !!/msie/.test(navigator.userAgent.toLowerCase()); - - oOptions = $.extend(oDefOptions, Utils.isUnd(oOptions) ? {} : oOptions); - - this.oOptions = oOptions; - this.bOnlyPlain = !!this.oOptions.DisableHtml; - this.fOnSwitch = this.oOptions.onSwitch; - - this.textarea = $(oTextAreaElem).empty().addClass('editorTextArea'); - this.htmlarea = $(oHtmlAreaElem).empty().addClass('editorHtmlArea').prop('contentEditable', 'true'); - this.toolbar = $(oToolBarElement).empty().addClass('editorToolbar'); - - HtmlEditor.htmlInitEditor.apply(this); - HtmlEditor.htmlInitToolbar.apply(this); - HtmlEditor.htmlAttachEditorEvents.apply(this); - - if (this.bOnlyPlain) - { - this.toolbar.hide(); -// this.switchToPlain(false); - } -} - -/** - * @param {string} mA - * @param {string} mB - * @param {string} mC - */ -HtmlEditor.prototype.initLanguage = function (mA, mB, mC) -{ - this.oOptions.LangSwitcherConferm = mA; - this.oOptions.LangSwitcherTextLabel = mB; - this.oOptions.LangSwitcherHtmlLabel = mC; -}; - -/** - * @param {string} mA - * @param {boolean=} mB - * @param {string=} mC - */ -HtmlEditor.prototype.execCom = function (mA, mB, mC) -{ - if (window.document) - { - window.document.execCommand(mA, mB || false, mC || null); - this.updateTextArea(); - } -}; - -HtmlEditor.prototype.getEditorSelection = function () { - var mSelection = null; - if (window.getSelection) - { - mSelection = window.getSelection(); - } - else if (window.document.getSelection) - { - mSelection = window.document.getSelection(); - } - else if (window.document.selection) - { - mSelection = window.document.selection; - } - return mSelection; -}; - -HtmlEditor.prototype.getEditorRange = function () { - var oSelection = this.getEditorSelection(); - if (!oSelection || 0 === oSelection.rangeCount) - { - return null; - } - return (oSelection.getRangeAt) ? oSelection.getRangeAt(0) : oSelection.createRange(); -}; - -/** - * @param {string} mA - * @param {boolean=} mB - * @param {string=} mC - */ -HtmlEditor.prototype.ec = function (mA, mB, mC) -{ - this.execCom(mA, mB, mC); -}; - -/** - * @param {number} iHeading - */ -HtmlEditor.prototype.heading = function (iHeading) -{ - this.ec('formatblock', false, (this.bIe) ? 'Heading ' + iHeading : 'h' + iHeading); -}; - -/** - * @param {string} sSrc - */ -HtmlEditor.prototype.insertImage = function (sSrc) -{ - if (this.isHtml() && !this.bOnlyPlain) - { - this.htmlarea.focus(); - this.ec('insertImage', false, sSrc); - } -}; - -HtmlEditor.prototype.focus = function () -{ - if (this.isHtml() && !this.bOnlyPlain) - { - this.htmlarea.focus(); - } - else - { - this.textarea.focus(); - } -}; - -/** - * @param {string} sType - * @param {string} sColor - */ -HtmlEditor.prototype.setcolor = function (sType, sColor) -{ - var - oRange = null, - sCmd = '' - ; - - if (this.bIe && !document['addEventListener']) - { - oRange = this.getEditorRange(); - if (oRange) - { - oRange.execCommand(('forecolor' === sType) ? 'ForeColor' : 'BackColor', false, sColor); - } - } - else - { - if (this.bIe) - { - sCmd = ('forecolor' === sType) ? 'ForeColor' : 'BackColor'; - } - else - { - sCmd = ('forecolor' === sType) ? 'foreColor' : 'backColor'; - } - - this.ec(sCmd, false, sColor); - } -}; - -/** - * @return {boolean} - */ -HtmlEditor.prototype.isHtml = function () -{ - return (true === this.bOnlyPlain) ? false : this.textarea.is(':hidden'); -}; - -/** - * @return {string} - */ -HtmlEditor.prototype.toHtmlString = function () -{ - return this.editor.innerHTML; -}; - -/** - * @return {string} - */ -HtmlEditor.prototype.toString = function () -{ - return this.editor.innerText; -}; - -HtmlEditor.prototype.updateTextArea = function () -{ - this.textarea.val(this.toHtmlString()); -}; - -HtmlEditor.prototype.updateHtmlArea = function () -{ - this.editor.innerHTML = this.textarea.val(); -}; - -/** - * @param {string} sInnerText - * @param {boolean} bIsHtml - */ -HtmlEditor.prototype.setRawText = function (sInnerText, bIsHtml) -{ - if (bIsHtml && !this.bOnlyPlain) - { - if (!this.isHtml()) - { - this.textarea.val(''); - this.switchToHtml(); - } - - this.textarea.val(sInnerText.toString()); - this.updateHtmlArea(); - } - else - { - this.textarea.val(sInnerText.toString()); - this.updateHtmlArea(); - this.switchToPlain(false); - } -}; - -HtmlEditor.prototype.clear = function () -{ - this.textarea.val(''); - this.editor.innerHTML = ''; - - if (this.bOnlyPlain) - { - this.toolbar.hide(); - this.switchToPlain(false); - } - else - { - this.switchToHtml(); - } -}; - -/** - * @return {string} - */ -HtmlEditor.prototype.getTextForRequest = function () -{ - if (this.isHtml()) - { - this.updateTextArea(); - return this.textarea.val(); - } - - return this.textarea.val(); -}; - -/** - * @param {boolean=} bWrap - * @return {string} - */ -HtmlEditor.prototype.getTextFromHtml = function (bWrap) -{ - var - sText = '', - sQuoteChar = '> ', - - convertBlockquote = function () { - if (arguments && 1 < arguments.length) - { - var sText = Utils.trim(arguments[1]) - .replace(/__bq__start__([\s\S\n\r]*)__bq__end__/gm, convertBlockquote) - ; - - sText = '\n' + sQuoteChar + Utils.trim(sText).replace(/\n/gm, '\n' + sQuoteChar) + '\n>\n'; - - return sText.replace(/\n([> ]+)/gm, function () { - return (arguments && 1 < arguments.length) ? '\n' + Utils.trim(arguments[1].replace(/[\s]/, '')) + ' ' : ''; - }); - } - - return ''; - }, - - convertDivs = function () { - if (arguments && 1 < arguments.length) - { - var sText = Utils.trim(arguments[1]); - if (0 < sText.length) - { - sText = sText.replace(/]*>([\s\S]*)<\/div>/gmi, convertDivs); - sText = '\n' + Utils.trim(sText) + '\n'; - } - return sText; - } - return ''; - }, - - convertLinks = function () { - if (arguments && 1 < arguments.length) - { - var - sName = Utils.trim(arguments[1]) -// sHref = Utils.trim(arguments[0].replace(//gmi, '$1')) - ; - - return sName; -// sName = (0 === Utils.trim(sName).length) ? '' : sName; -// sHref = ('mailto:' === sHref.substr(0, 7)) ? '' : sHref; -// sHref = ('http' === sHref.substr(0, 4)) ? sHref : ''; -// sHref = (sName === sHref) ? '' : sHref; -// sHref = (0 < sHref.length) ? ' (' + sHref + ') ' : ''; -// return (0 < sName.length) ? sName + sHref : sName; - } - return ''; - } - ; - - bWrap = Utils.isUnd(bWrap) ? true : !!bWrap; - - sText = this.toHtmlString() - .replace(/[\s]+/gm, ' ') - .replace(//gmi, '\n') - .replace(/<\/h\d>/gi, '\n') - .replace(/<\/p>/gi, '\n\n') - .replace(/<\/li>/gi, '\n') - .replace(/<\/td>/gi, '\n') - .replace(/<\/tr>/gi, '\n') - .replace(/]*>/gmi, '\n_______________________________\n\n') - .replace(/]*>/gmi, '') - .replace(/]*>([\s\S]*)<\/div>/gmi, convertDivs) - .replace(/]*>/gmi, '\n__bq__start__\n') - .replace(/<\/blockquote>/gmi, '\n__bq__end__\n') - .replace(/]*>([\s\S]*?)<\/a>/gmi, convertLinks) - .replace(/ /gi, ' ') - .replace(/<[^>]*>/gm, '') - .replace(/>/gi, '>') - .replace(/</gi, '<') - .replace(/&/gi, '&') - .replace(/&\w{2,6};/gi, '') - ; - - return (bWrap ? Utils.splitPlainText(sText) : sText) - .replace(/\n[ \t]+/gm, '\n') - .replace(/[\n]{3,}/gm, '\n\n') - .replace(/__bq__start__([\s\S]*)__bq__end__/gm, convertBlockquote) - .replace(/__bq__start__/gm, '') - .replace(/__bq__end__/gm, '') - ; -}; - -/** - * @return {string} - */ -HtmlEditor.prototype.getHtmlFromText = function () -{ - return Utils.convertPlainTextToHtml(this.textarea.val()); -}; - -HtmlEditor.prototype.switchToggle = function () -{ - if (this.isHtml()) - { - this.switchToPlain(); - } - else - { - this.switchToHtml(); - } -}; - -/** -* @param {boolean=} bWithConfirm -*/ -HtmlEditor.prototype.switchToPlain = function (bWithConfirm) -{ - bWithConfirm = Utils.isUnd(bWithConfirm) ? true : bWithConfirm; - - var - sText = this.getTextFromHtml(), - fSwitch = _.bind(function (bValue) { - - if (bValue) - { - this.toolbar.addClass('editorHideToolbar'); - $('.editorSwitcher', this.toolbar).text(this.switcherLinkText(false)); - - this.textarea.val(sText); - this.textarea.show(); -// this.textarea.css({'display': ''}); - this.htmlarea.hide(); - if (this.fOnSwitch) - { - this.fOnSwitch(false); - } - } - - }, this) - ; - - if (!bWithConfirm || 0 === Utils.trim(sText).length) - { - fSwitch(true); - } - else - { - fSwitch(window.confirm(this.oOptions.LangSwitcherConferm)); -// Utils.dialogConfirm(this.oOptions.LangSwitcherConferm, fSwitch); - } -}; - -/** - * @param {boolean} bIsPlain - * @return {string} - */ -HtmlEditor.prototype.switcherLinkText = function (bIsPlain) -{ - return (bIsPlain) ? this.oOptions.LangSwitcherTextLabel : this.oOptions.LangSwitcherHtmlLabel; -// return (bIsPlain) ? '« ' + 'EDITOR_TEXT_SWITCHER_PLAINT_TEXT' : 'EDITOR_TEXT_SWITCHER_RICH_FORMATTING' + ' »'; -}; - -HtmlEditor.prototype.switchToHtml = function () -{ - this.toolbar.removeClass('editorHideToolbar'); - $('.editorSwitcher', this.toolbar).text(this.switcherLinkText(true)); - - this.textarea.val(this.getHtmlFromText()); - this.updateHtmlArea(); - this.textarea.hide(); - this.htmlarea.show(); -// this.htmlarea.css({'display': ''}); - - if (this.fOnSwitch) - { - this.fOnSwitch(true); - } -}; - -/** - * @param {string} sName - * @param {string} sTitle - */ -HtmlEditor.prototype.addButton = function (sName, sTitle) -{ - var self = this; - - $('
').addClass('editorToolbarButtom').append($('').addClass(sName)).attr('title', sTitle).click(function (oEvent) { - if (!Utils.isUnd(HtmlEditor.htmlFunctions[sName])) - { - HtmlEditor.htmlFunctions[sName].apply(self, [$(this), oEvent]); - } - else - { - window.alert(sName); - } - }).appendTo(this.toolbar); -}; - -HtmlEditor.htmlInitToolbar = function () -{ - if (!this.bOnlyPlain) - { -// this.addButton('fontname', 'Select font'); - this.addButton('bold', 'Bold'); - this.addButton('italic', 'Italic'); - this.addButton('underline', 'Underline'); - this.addButton('strikethrough', 'Strikethrough'); - - this.addButton('removeformat', 'removeformat'); - - this.addButton('justifyleft', 'justifyleft'); - this.addButton('justifycenter', 'justifycenter'); - this.addButton('justifyright', 'justifyright'); - - this.addButton('horizontalrule', 'horizontalrule'); - - this.addButton('orderedlist', 'orderedlist'); - this.addButton('unorderedlist', 'unorderedlist'); - - this.addButton('indent', 'indent'); - this.addButton('outdent', 'outdent'); - - this.addButton('forecolor', 'forecolor'); -// this.addButton('backcolor', 'backcolor'); - - (function ($, that) { - - $('').addClass('editorSwitcher').text(that.switcherLinkText(true)).click(function () { - that.switchToggle(); - }).appendTo(that.toolbar); - - }($, this)); - } -}; - -HtmlEditor.htmlInitEditor = function () -{ - this.editor = this.htmlarea[0]; - this.editor.innerHTML = this.textarea.val(); -}; - -HtmlEditor.htmlAttachEditorEvents = function () -{ - var - self = this, - - fIsImage = function (oItem) { - return oItem && oItem.type && 0 === oItem.type.indexOf('image/'); - }, - -// fUpdateHtmlArea = function () { -// self.updateHtmlArea(); -// }, -// -// fUpdateTextArea = function () { -// self.updateTextArea(); -// }, - - fHandleFileSelect = function (oEvent) { - - oEvent = (oEvent && oEvent.originalEvent ? - oEvent.originalEvent : oEvent) || window.event; - - if (oEvent) - { - oEvent.stopPropagation(); - oEvent.preventDefault(); - - var - oReader = null, - oFile = null, - aFiles = (oEvent.files || (oEvent.dataTransfer ? oEvent.dataTransfer.files : null)) - ; - - if (aFiles && 1 === aFiles.length && fIsImage(aFiles[0])) - { - oFile = aFiles[0]; - - oReader = new window.FileReader(); - oReader.onload = (function (oLocalFile) { - return function (oEvent) { - self.insertImage(oEvent.target.result, oLocalFile.name); - }; - }(oFile)); - - oReader.readAsDataURL(oFile); - } - } - - self.htmlarea.removeClass('editorDragOver'); - }, - - fHandleDragLeave = function () { - self.htmlarea.removeClass('editorDragOver'); - }, - - fHandleDragOver = function (oEvent) { - oEvent.stopPropagation(); - oEvent.preventDefault(); - - self.htmlarea.addClass('editorDragOver'); - }, - - fHandlePaste = function (oEvent) { - - var oClipboardData = oEvent && oEvent.clipboardData ? oEvent.clipboardData : - (oEvent && oEvent.originalEvent && oEvent.originalEvent.clipboardData ? oEvent.originalEvent.clipboardData : null); - - if (oClipboardData && oClipboardData.items) - { - _.each(oClipboardData.items, function (oItem) { - if (fIsImage(oItem) && oItem['getAsFile']) { - var oReader = null, oFile = oItem['getAsFile'](); - if (oFile) - { - oReader = new window.FileReader(); - oReader.onload = (function (oLocalFile) { - return function (oEvent) { - self.insertImage(oEvent.target.result, oLocalFile.name); - }; - }(oFile)); - - oReader.readAsDataURL(oFile); - } - } - }); - } - } - ; - - if (!this.bOnlyPlain) - { -// this.textarea.on('click keyup keydown mousedown blur', fUpdateHtmlArea); -// this.htmlarea.on('click keyup keydown mousedown blur', fUpdateTextArea); -// - if (window.File && window.FileReader && window.FileList) - { - this.htmlarea.bind('dragover', fHandleDragOver); - this.htmlarea.bind('dragleave', fHandleDragLeave); - this.htmlarea.bind('drop', fHandleFileSelect); - this.htmlarea.bind('paste', fHandlePaste); - } - } -}; - -HtmlEditor.htmlColorPickerColors = (function () { - - var - aMaps = [], - aColors = [], - iIndex = 0, - iIndexSub = 0, - iIndexSubSub = 0, - iLen = 0, - sMap = '' - ; - - for (iIndex = 0; iIndex < 256; iIndex += 85) - { - sMap = iIndex.toString(16); - aMaps.push(1 === sMap.length ? '0' + sMap : sMap); - } - - iLen = aMaps.length; - for (iIndex = 0; iIndex < iLen; iIndex++) - { - for (iIndexSub = 0; iIndexSub < iLen; iIndexSub++) - { - for (iIndexSubSub = 0; iIndexSubSub < iLen; iIndexSubSub++) - { - aColors.push('#' + aMaps[iIndex] + '' + aMaps[iIndexSub] + '' + aMaps[iIndexSubSub]); - } - } - } - - return aColors; - -}()); - -HtmlEditor.htmlFontPicker = (function () { - - var - jqDoc = $(window.document), - bIsAppented = false, - oFontPickerHolder = $('
'), - oFonts = oFontPickerHolder.find('.editorFpFonts'), - fCurrentFunc = function () {} - ; - - $.each(['Arial', 'Arial Black', 'Courier New', 'Tahoma', 'Times New Roman', 'Verdana'], function (iIndex, sFont) { - oFonts.append( - $('' + sFont + '').click(function () { - fCurrentFunc(sFont); - }) - ); - oFonts.append('
'); - }); - - oFontPickerHolder.hide(); - - return function (oClickObject, fSelectFunc, oTollbar) { - - if (!bIsAppented) - { - oFontPickerHolder.appendTo(oTollbar); - bIsAppented = true; - } - - fCurrentFunc = fSelectFunc; - - jqDoc.unbind('click.fpNamespace'); - window.setTimeout(function () { - jqDoc.one('click.fpNamespace', function () { - oFontPickerHolder.hide(); - }); - }, 500); - - var oPos = $(oClickObject).position(); - oFontPickerHolder - .css('top', (5 + oPos.top + $(oClickObject).height()) + 'px') - .css('left', oPos.left + 'px') - .show(); - }; - -}()); - -HtmlEditor.htmlColorPicker = (function () { - - var - jqDoc = $(window.document), - bIsAppented = false, - oColorPickerHolder = $('
'), - oColors = oColorPickerHolder.find('.editorCpColors'), - fCurrentFunc = function () {} - ; - - $.each(HtmlEditor.htmlColorPickerColors, function (iIndex, sColor) { - oColors.append(''); - }); - - oColorPickerHolder.hide(); - - $('.editorCpColor', oColors).click(function (oEvent) { - - var - iIndex = 1, - sSelectedColor = '#000000', - sRgbString = $(oEvent.target).css('background-color'), - aParts = sRgbString.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/) - ; - - if (aParts !== null) - { - delete (aParts[0]); - for (; iIndex <= 3; ++iIndex) - { - aParts[iIndex] = Utils.pInt(aParts[iIndex]).toString(16); - if (1 === aParts[iIndex].length) - { - aParts[iIndex] = '0' + aParts[iIndex]; - } - } - - sSelectedColor = '#' + aParts.join(''); - } - else - { - sSelectedColor = sRgbString; - } - - fCurrentFunc(sSelectedColor); - }); - - return function (oClickObject, fSelectFunc, oTollbar) { - - if (!bIsAppented) - { - oColorPickerHolder.appendTo(oTollbar); - bIsAppented = true; - } - - var oPos = $(oClickObject).position(); - fCurrentFunc = fSelectFunc; - - jqDoc.unbind('click.cpNamespace'); - window.setTimeout(function () { - jqDoc.one('click.cpNamespace', function () { - oColorPickerHolder.hide(); - }); - }, 100); - - oColorPickerHolder - .css('top', (5 + oPos.top + $(oClickObject).height()) + 'px') - .css('left', oPos.left + 'px') - .show(); - }; - -}()); - -/* ----------- */ - -HtmlEditor.htmlFunctions = { - - /** - * @this {HtmlEditor} - */ - 'bold': function () - { - this.ec('bold'); - }, - - /** - * @this {HtmlEditor} - */ - 'italic': function () - { - this.ec('italic'); - }, - - /** - * @this {HtmlEditor} - */ - 'underline': function () - { - this.ec('underline'); - }, - - /** - * @this {HtmlEditor} - */ - 'strikethrough': function () - { - this.ec('strikethrough'); - }, - - /** - * @this {HtmlEditor} - */ - 'indent': function () - { - this.ec('indent'); - }, - - /** - * @this {HtmlEditor} - */ - 'outdent': function () - { - this.ec('outdent'); - }, - - /** - * @this {HtmlEditor} - */ - 'justifyleft': function () - { - this.ec('justifyLeft'); - }, - - /** - * @this {HtmlEditor} - */ - 'justifycenter': function () - { - this.ec('justifyCenter'); - }, - - /** - * @this {HtmlEditor} - */ - 'justifyright': function () - { - this.ec('justifyRight'); - }, - - /** - * @this {HtmlEditor} - */ - 'horizontalrule': function () - { - this.ec('insertHorizontalRule', false, 'ht'); - }, - - /** - * @this {HtmlEditor} - */ - 'removeformat': function () - { - this.ec('removeFormat'); - }, - - /** - * @this {HtmlEditor} - */ - 'orderedlist': function () - { - this.ec('insertorderedlist'); - }, - - /** - * @this {HtmlEditor} - */ - 'unorderedlist': function () - { - this.ec('insertunorderedlist'); - }, - - /** - * @this {HtmlEditor} - */ - 'forecolor': function (oClickObject) - { - HtmlEditor.htmlColorPicker(oClickObject, _.bind(function (sValue) { - this.setcolor('forecolor', sValue); - }, this), this.toolbar); - }, - - /** - * @this {HtmlEditor} - */ - 'backcolor': function (oClickObject) - { - HtmlEditor.htmlColorPicker(oClickObject, _.bind(function (sValue) { - this.setcolor('backcolor', sValue); - }, this), this.toolbar); - }, - - /** - * @this {HtmlEditor} - */ - 'fontname': function (oClickObject) - { - HtmlEditor.htmlFontPicker(oClickObject, _.bind(function (sValue) { - this.ec('fontname', false, sValue); - }, this), this.toolbar); - } -}; diff --git a/dev/Common/HtmlEditorWrapper.js b/dev/Common/HtmlEditorWrapper.js new file mode 100644 index 000000000..f30ba1685 --- /dev/null +++ b/dev/Common/HtmlEditorWrapper.js @@ -0,0 +1,402 @@ + +/** + * @constructor + * @param {Object} oElement + * @param {Function=} fOnBlur + * @param {Function=} fOnReady + */ +function HtmlEditorWrapper(oElement, fOnBlur, fOnReady) +{ + var self = this; + + self.editor = null; + self.bHtml = true; + self.bPlainDirty = false; + self.iBlurTimer = 0; + + self.fOnBlur = fOnBlur || null; + self.fOnReady = fOnReady || null; + + self.$element = $(oElement); + + self.LANG = { + 'HTML': 'Rich formatting', + 'PLAIN': 'Plain text', + 'FULL': 'Fullscreen' + }; + + self.init(); +} + +HtmlEditorWrapper.prototype.addInputFormatStyle = function () +{ + if (this.$plain) + { + this.$plain.addClass('styled'); + } + + if (this.$html) + { + this.$html.addClass('styled'); + } +}; + +HtmlEditorWrapper.prototype.setupLang = function (sHtml, sPlain, sFullscreen) +{ + this.LANG = { + 'HTML': sHtml || this.LANG.HTML, + 'PLAIN': sPlain || this.LANG.PLAIN, + 'FULL': sFullscreen || this.LANG.FULL + }; + + this.setModeButtonText(); +}; + +HtmlEditorWrapper.prototype.blurTrigger = function () +{ + if (this.fOnBlur) + { + var self = this; + window.clearTimeout(self.iBlurTimer); + self.iBlurTimer = window.setTimeout(function () { + self.fOnBlur(); + }, 200); + } +}; + +HtmlEditorWrapper.prototype.focusTrigger = function () +{ + if (this.fOnBlur) + { + window.clearTimeout(this.iBlurTimer); + } +}; + +/** + * @param {string} sHtml + * @return {string} + */ +HtmlEditorWrapper.prototype.htmlToPlain = function (sHtml) +{ + var + sText = '', + sQuoteChar = '> ', + + convertBlockquote = function () { + if (arguments && 1 < arguments.length) + { + var sText = $.trim(arguments[1]) + .replace(/__bq__start__([\s\S\n\r]*)__bq__end__/gm, convertBlockquote) + ; + + sText = '\n' + sQuoteChar + $.trim(sText).replace(/\n/gm, '\n' + sQuoteChar) + '\n>\n'; + + return sText.replace(/\n([> ]+)/gm, function () { + return (arguments && 1 < arguments.length) ? '\n' + $.trim(arguments[1].replace(/[\s]/, '')) + ' ' : ''; + }); + } + + return ''; + }, + + convertDivs = function () { + if (arguments && 1 < arguments.length) + { + var sText = $.trim(arguments[1]); + if (0 < sText.length) + { + sText = sText.replace(/]*>([\s\S]*)<\/div>/gmi, convertDivs); + sText = '\n' + $.trim(sText) + '\n'; + } + return sText; + } + return ''; + }, + + convertLinks = function () { + if (arguments && 1 < arguments.length) + { + var + sName = $.trim(arguments[1]) +// sHref = $.trim(arguments[0].replace(//gmi, '$1')) + ; + + return sName; +// sName = (0 === trim(sName).length) ? '' : sName; +// sHref = ('mailto:' === sHref.substr(0, 7)) ? '' : sHref; +// sHref = ('http' === sHref.substr(0, 4)) ? sHref : ''; +// sHref = (sName === sHref) ? '' : sHref; +// sHref = (0 < sHref.length) ? ' (' + sHref + ') ' : ''; +// return (0 < sName.length) ? sName + sHref : sName; + } + return ''; + } + ; + + sText = sHtml + .replace(/[\s]+/gm, ' ') + .replace(//gmi, '\n') + .replace(/<\/h\d>/gi, '\n') + .replace(/<\/p>/gi, '\n\n') + .replace(/<\/li>/gi, '\n') + .replace(/<\/td>/gi, '\n') + .replace(/<\/tr>/gi, '\n') + .replace(/]*>/gmi, '\n_______________________________\n\n') + .replace(/]*>/gmi, '') + .replace(/]*>([\s\S]*)<\/div>/gmi, convertDivs) + .replace(/]*>/gmi, '\n__bq__start__\n') + .replace(/<\/blockquote>/gmi, '\n__bq__end__\n') + .replace(/]*>([\s\S]*?)<\/a>/gmi, convertLinks) + .replace(/ /gi, ' ') + .replace(/<[^>]*>/gm, '') + .replace(/>/gi, '>') + .replace(/</gi, '<') + .replace(/&/gi, '&') + .replace(/&\w{2,6};/gi, '') + ; + + return sText + .replace(/\n[ \t]+/gm, '\n') + .replace(/[\n]{3,}/gm, '\n\n') + .replace(/__bq__start__([\s\S]*)__bq__end__/gm, convertBlockquote) + .replace(/__bq__start__/gm, '') + .replace(/__bq__end__/gm, '') + ; +}; + +/** + * @param {string} sPlain + * @return {string} + */ +HtmlEditorWrapper.prototype.plainToHtml = function (sPlain) +{ + return sPlain.toString() + .replace(/&/g, '&').replace(/>/g, '>').replace(/'); +}; + +HtmlEditorWrapper.prototype.fullScreenToggle = function () +{ + $('html').toggleClass('html-editor-wrapper-fullscreen'); + $('body').toggleClass('html-editor-wrapper-fullscreen'); + + this.focus(); +}; + +HtmlEditorWrapper.prototype.initToolbar = function () +{ + var self = this; + +// self.$fullscreen = $('fullscreen') +// .addClass('html-editor-wrapper-fullscreen-button') +// .on('click', function () { +// self.fullScreenToggle(); +// }) +// ; + + self.$mode = $('html') + .addClass('html-editor-wrapper-mode-button') + .on('click', function () { + self.modeToggle(true); + }) + ; + + self.$toolbar + .append(self.$mode) +// .append(self.$fullscreen) + ; +}; + +/** + * @return {boolean} + */ +HtmlEditorWrapper.prototype.isHtml = function () +{ + return this.bHtml; +}; + +/** + * @return {boolean} + */ +HtmlEditorWrapper.prototype.checkDirty = function () +{ + return this.bHtml && this.editor ? this.editor.checkDirty() : this.bPlainDirty; +}; + +HtmlEditorWrapper.prototype.resetDirty = function () +{ + if (this.editor) + { + this.editor.resetDirty(); + } + + this.bPlainDirty = false; +}; + +/** + * @return {string} + */ +HtmlEditorWrapper.prototype.getData = function () +{ + if (this.bHtml && this.editor) + { + return this.editor.getData(); + } + else + { + return this.$plain.val(); + } +}; + +HtmlEditorWrapper.prototype.setHtml = function (sHtml, bFocus) +{ + if (this.editor) + { + if (!this.bHtml) + { + this.modeToggle(bFocus); + } + + this.editor.setData(sHtml); + } +}; + +HtmlEditorWrapper.prototype.setPlain = function (sPlain, bFocus) +{ + if (this.bHtml) + { + this.modeToggle(bFocus); + } + + this.$plain.val(sPlain); +}; + +HtmlEditorWrapper.prototype.init = function () +{ + if (this.$element && this.$element[0]) + { + var self = this; + + self.$toolbar = $('
') + .addClass('html-editor-wrapper-toolbar') + ; + + self.$plain = $('') + .addClass('html-editor-wrapper-plain') + .on('change', function () { + self.bPlainDirty = true; + }) + .on('blur', function() { + self.blurTrigger(); + }) + .on('focus', function() { + self.focusTrigger(); + }) + .hide() + ; + + self.$html = $('
') + .addClass('html-editor-wrapper-html') + .attr('contenteditable', 'true') + .on('blur', function() { + self.blurTrigger(); + }) + .on('focus', function() { + self.focusTrigger(); + }) + .hide() + ; + + if (self.bHtml) { + self.$html.show(); + } else { + self.$plain.show(); + } + + self.$element + .addClass('html-editor-wrapper') + .append(self.$toolbar) + .append(self.$plain) + .append(self.$html) + ; + + self.editor = window.CKEDITOR.inline(self.$html[0]); + if (self.fOnReady) + { + self.editor.on('instanceReady', function () { + self.fOnReady(); + }); + } + + self.initToolbar(); + self.setModeButtonText(); + } +}; + +HtmlEditorWrapper.prototype.focus = function () +{ + if (this.bHtml) { + this.$html.focus(); + } else { + this.$plain.focus(); + } +}; + +HtmlEditorWrapper.prototype.blur = function () +{ + if (this.bHtml) { + this.$html.blur(); + } else { + this.$plain.blur(); + } +}; + +HtmlEditorWrapper.prototype.setModeButtonText = function () +{ + this.$mode.text(this.bHtml ? this.LANG.PLAIN : this.LANG.HTML); +}; + +HtmlEditorWrapper.prototype.clear = function (bFocus) +{ + this.setHtml('', bFocus); +}; + +HtmlEditorWrapper.prototype.modeToggle = function (bFocus) +{ + bFocus = Utils.isUnd(bFocus) ? true : !!bFocus; + if (bFocus) + { + this.blur(); + } + + if (this.bHtml) { + this.$html.hide(); + + this.$plain + .val(this.htmlToPlain(this.$html.html())) + .show() + ; + + this.bHtml = false; + this.bPlainDirty = true; + } else { + this.$plain.hide(); + + this.$html + .html(this.plainToHtml(this.$plain.val())) + .show() + ; + + this.bHtml = true; + this.bPlainDirty = true; + } + + this.setModeButtonText(); + + if (bFocus) + { + this.focus(); + } + + this.blurTrigger(); +}; diff --git a/dev/Common/LinkBuilder.js b/dev/Common/LinkBuilder.js index ff75c332f..a3b68047e 100644 --- a/dev/Common/LinkBuilder.js +++ b/dev/Common/LinkBuilder.js @@ -274,6 +274,24 @@ LinkBuilder.prototype.openPgpJs = function () this.sVersion + '/static/js/openpgp.js'; }; +/** + * @return {string} + */ +LinkBuilder.prototype.ckeditorPath = function () +{ + return ('' === this.sCdnStaticDomain ? 'rainloop/v/' : this.sCdnStaticDomain) + + this.sVersion + '/static/ckeditor/'; +}; + +/** + * @return {string} + */ +LinkBuilder.prototype.ckeditorJs = function () +{ + return ('' === this.sCdnStaticDomain ? 'rainloop/v/' : this.sCdnStaticDomain) + + this.sVersion + '/static/ckeditor/ckeditor.js'; +}; + /** * @return {string} */ diff --git a/dev/Common/Utils.js b/dev/Common/Utils.js index 39feb04db..af8a08a1c 100644 --- a/dev/Common/Utils.js +++ b/dev/Common/Utils.js @@ -644,12 +644,23 @@ Utils.getUploadErrorDescByCode = function (mCode) * @param {?} oObject * @param {string} sMethodName * @param {Array=} aParameters + * @param {number=} nDelay */ -Utils.delegateRun = function (oObject, sMethodName, aParameters) +Utils.delegateRun = function (oObject, sMethodName, aParameters, nDelay) { if (oObject && oObject[sMethodName]) { - oObject[sMethodName].apply(oObject, Utils.isArray(aParameters) ? aParameters : []); + nDelay = Utils.pInt(nDelay); + if (0 >= nDelay) + { + oObject[sMethodName].apply(oObject, Utils.isArray(aParameters) ? aParameters : []); + } + else + { + _.delay(function () { + oObject[sMethodName].apply(oObject, Utils.isArray(aParameters) ? aParameters : []); + }, nDelay); + } } }; diff --git a/dev/Knoin/Knoin.js b/dev/Knoin/Knoin.js index 50c821da7..e81537a47 100644 --- a/dev/Knoin/Knoin.js +++ b/dev/Knoin/Knoin.js @@ -177,9 +177,7 @@ Knoin.prototype.showScreenPopup = function (ViewModelClassToShow, aParameters) Plugins.runHook('view-model-on-show', [ViewModelClassToShow.__name, ViewModelClassToShow.__vm, aParameters || []]); - _.delay(function () { - Utils.delegateRun(ViewModelClassToShow.__vm, 'onFocus'); - }, 500); + Utils.delegateRun(ViewModelClassToShow.__vm, 'onFocus', [], 500); } } }; @@ -259,27 +257,27 @@ Knoin.prototype.screenOnRoute = function (sScreenName, sSubPart) // show screen if (self.oCurrentScreen) { + Utils.delegateRun(self.oCurrentScreen, 'onShow'); - Utils.delegateRun(self.oCurrentScreen, 'onShow'); + Plugins.runHook('screen-on-show', [self.oCurrentScreen.screenName(), self.oCurrentScreen]); - Plugins.runHook('screen-on-show', [self.oCurrentScreen.screenName(), self.oCurrentScreen]); + if (Utils.isNonEmptyArray(self.oCurrentScreen.viewModels())) + { + _.each(self.oCurrentScreen.viewModels(), function (ViewModelClass) { - if (Utils.isNonEmptyArray(self.oCurrentScreen.viewModels())) - { - _.each(self.oCurrentScreen.viewModels(), function (ViewModelClass) { + if (ViewModelClass.__vm && ViewModelClass.__dom && + 'Popups' !== ViewModelClass.__vm.viewModelPosition()) + { + ViewModelClass.__dom.show(); + ViewModelClass.__vm.viewModelVisibility(true); + Utils.delegateRun(ViewModelClass.__vm, 'onShow'); + Utils.delegateRun(ViewModelClass.__vm, 'onFocus', [], 200); - if (ViewModelClass.__vm && ViewModelClass.__dom && - 'Popups' !== ViewModelClass.__vm.viewModelPosition()) - { - ViewModelClass.__dom.show(); - ViewModelClass.__vm.viewModelVisibility(true); - Utils.delegateRun(ViewModelClass.__vm, 'onShow'); + Plugins.runHook('view-model-on-show', [ViewModelClass.__name, ViewModelClass.__vm]); + } - Plugins.runHook('view-model-on-show', [ViewModelClass.__name, ViewModelClass.__vm]); - } - - }, self); - } + }, self); + } } // -- diff --git a/dev/Screens/AbstractSettings.js b/dev/Screens/AbstractSettings.js index 6566e5306..a78d24e9b 100644 --- a/dev/Screens/AbstractSettings.js +++ b/dev/Screens/AbstractSettings.js @@ -104,6 +104,7 @@ AbstractSettings.prototype.onRoute = function (sSubName) { self.oCurrentSubScreen.viewModelDom.show(); Utils.delegateRun(self.oCurrentSubScreen, 'onShow'); + Utils.delegateRun(self.oCurrentSubScreen, 'onFocus', [], 200); _.each(self.menu(), function (oItem) { oItem.selected(oSettingsScreen && oSettingsScreen.__rlSettingsData && oItem.route === oSettingsScreen.__rlSettingsData.Route); diff --git a/dev/Settings/Identities.js b/dev/Settings/Identities.js index 7f5561712..289a9485b 100644 --- a/dev/Settings/Identities.js +++ b/dev/Settings/Identities.js @@ -12,6 +12,8 @@ function SettingsIdentities() this.signatureToAll = oData.signatureToAll; this.replyTo = oData.replyTo; + this.signatureDom = ko.observable(null); + this.displayNameTrigger = ko.observable(Enums.SaveSettingsStep.Idle); this.replyTrigger = ko.observable(Enums.SaveSettingsStep.Idle); this.signatureTrigger = ko.observable(Enums.SaveSettingsStep.Idle); @@ -79,6 +81,41 @@ SettingsIdentities.prototype.deleteIdentity = function (oIdentityToRemove) } }; +SettingsIdentities.prototype.onFocus = function () +{ + if (!this.editor && this.signatureDom()) + { + var + self = this, + sSignature = RL.data().signature() + ; + + this.editor = new HtmlEditorWrapper(self.signatureDom(), function () { + RL.data().signature( + (self.editor.isHtml() ? ':HTML:' : '') + self.editor.getData() + ); + }, function () { + if (':HTML:' === sSignature.substr(0, 6)) + { + self.editor.setHtml(sSignature.substr(6), false); + } + else + { + self.editor.setPlain(sSignature, false); + } + }); + + this.editor.addInputFormatStyle(); + + Utils.initOnStartOrLangChange(function () { + self.editor.setupLang( + Utils.i18n('EDITOR/TEXT_SWITCHER_RICH_FORMATTING'), + Utils.i18n('EDITOR/TEXT_SWITCHER_PLAINT_TEXT') + ); + }); + } +}; + SettingsIdentities.prototype.onBuild = function (oDom) { var self = this; @@ -91,7 +128,7 @@ SettingsIdentities.prototype.onBuild = function (oDom) self.editIdentity(oIdentityItem); } }) - ; + ; _.delay(function () { diff --git a/dev/Settings/Identity.js b/dev/Settings/Identity.js index cdbda0cc7..af2da307c 100644 --- a/dev/Settings/Identity.js +++ b/dev/Settings/Identity.js @@ -12,6 +12,8 @@ function SettingsIdentity() this.signatureToAll = oData.signatureToAll; this.replyTo = oData.replyTo; + this.signatureDom = ko.observable(null); + this.displayNameTrigger = ko.observable(Enums.SaveSettingsStep.Idle); this.replyTrigger = ko.observable(Enums.SaveSettingsStep.Idle); this.signatureTrigger = ko.observable(Enums.SaveSettingsStep.Idle); @@ -19,6 +21,41 @@ function SettingsIdentity() Utils.addSettingsViewModel(SettingsIdentity, 'SettingsIdentity', 'SETTINGS_LABELS/LABEL_IDENTITY_NAME', 'identity'); +SettingsIdentity.prototype.onFocus = function () +{ + if (!this.editor && this.signatureDom()) + { + var + self = this, + sSignature = RL.data().signature() + ; + + this.editor = new HtmlEditorWrapper(self.signatureDom(), function () { + RL.data().signature( + (self.editor.isHtml() ? ':HTML:' : '') + self.editor.getData() + ); + }, function () { + if (':HTML:' === sSignature.substr(0, 6)) + { + self.editor.setHtml(sSignature.substr(6), false); + } + else + { + self.editor.setPlain(sSignature, false); + } + }); + + this.editor.addInputFormatStyle(); + + Utils.initOnStartOrLangChange(function () { + self.editor.setupLang( + Utils.i18n('EDITOR/TEXT_SWITCHER_RICH_FORMATTING'), + Utils.i18n('EDITOR/TEXT_SWITCHER_PLAINT_TEXT') + ); + }); + } +}; + SettingsIdentity.prototype.onBuild = function () { var self = this; diff --git a/dev/Styles/@Main.less b/dev/Styles/@Main.less index b591d92a1..73c088fbd 100644 --- a/dev/Styles/@Main.less +++ b/dev/Styles/@Main.less @@ -71,5 +71,5 @@ @import "SettingsThemes.less"; @import "Animations.less"; -@import "Editor.less"; +@import "HtmlEditorWrapper.less"; @import "_End.less"; diff --git a/dev/Styles/Editor.less b/dev/Styles/Editor.less deleted file mode 100644 index c147b07ab..000000000 --- a/dev/Styles/Editor.less +++ /dev/null @@ -1,184 +0,0 @@ -.editorToolbar { - - position: relative; - height: 20px; - margin-top: 10px; - line-height: 19px; - - &.editorHideToolbar .editorToolbarButtom { - display: none; - } - - .editorSwitcher { - display: inline-block; - vertical-align: middle; - } - - .editorToolbarButtom { - - display: inline-block; - width: 16px; - height: 16px; - padding: 3px; - - a { - display: inline-block; - border: 0px; - margin: 0px; - padding: 0px; - width: 16px; - height: 16px; - - cursor: default; - background: url('@{rlEditorSprite}'); - - &.bold { background-position: 0 0; } - &.italic { background-position: -16px 0; } - &.underline { background-position: -32px 0; } - &.strikethrough { background-position: -48px 0; } - &.link { background-position: -64px 0; } - &.unlink { background-position: -80px 0; } - &.orderedlist { background-position: -96px 0; } - &.unorderedlist { background-position: -112px 0; } - &.image { background-position: -128px 0; } - - &.h1 { background-position: 0 -16px;} - &.h2 { background-position: -16px -16px;} - &.h3 { background-position: -32px -16px;} - &.h4 { background-position: -48px -16px;} - &.h5 { background-position: -64px -16px;} - &.h6 { background-position: -80px -16px;} - - &.subscript { background-position: -96px -16px;} - &.superscript { background-position: -112px -16px;} - &.indent { background-position: -128px -16px;} - &.outdent { background-position: -144px -16px;} - &.horizontalrule { background-position: -160px -16px;} - &.p { background-position: -176px -16px;} - - &.justifyleft { background-position: 0 -32px;} - &.justifycenter { background-position: -16px -32px;} - &.justifyright { background-position: -32px -32px;} - &.increasefontsize { background-position: -48px -32px;} - &.decreasefontsize { background-position: -64px -32px;} - &.forecolor { background-position: -80px -32px;} - &.backcolor { background-position: -80px -32px;} - &.removeformat { background-position: -144px 0;} - } - } -} - -.textAreaParent { - - padding: 0px; - - .editorHtmlArea { - - .box-sizing(border-box); - - background: #fff; - color: #000; - - border: 0px !important; - overflow: auto; - overflow-y: scroll; - font-family: Arial, Verdana, Geneva, sans-serif; - font-size: 13px; - line-height: 15px; - margin: 0px; - padding: 8px; - - ul { - - padding-left: 40px; - - li { - list-style-type: disc !important; - } - } - - ol { - padding-left: 40px; - li { - list-style-type: decimal !important; - } - } - - blockquote { - border: 0; - border-left: solid 2px #444; - margin-left: 5px; - margin: 5px 0; - padding-left: 5px; - } - - img { - vertical-align: bottom; - } - - &.editorDragOver { - background: #ffffef; - } - } - - .editorTextArea { - .box-sizing(border-box); - - background: #fff; - color: #000; - - display: block; - border: 0px !important; - width: 100%; - margin: 0px; - padding: 8px; - /*font-family: Monaco, Menlo, Consolas, 'Courier New', monospace;*/ - font-family: Arial, Verdana, Geneva, sans-serif; - font-size: 13px; - line-height: 15px; - - overflow: auto; - overflow-y: scroll; - } -} - -.editorColorPicker { - - .editorCpColors { - - float: left; - margin: 0; - clear: both; - width: 128px; - border: 1px solid #000; - backgroud: #000; - - .editorCpColor { - border: 1px solid #fff; - float: left; - width: 14px; - height: 14px; - } - } -} - -.editorSwitcher { - .g-ui-link; - .pull-right; - - padding-bottom: 6px; -} - -.editorFontStylePicker { - - .editorFpFonts { - - padding: 5px; - border: 1px solid #000; - background-color: #fff; - - .editorFpFont { - padding: 5px; - } - } -} diff --git a/dev/Styles/HtmlEditorWrapper.less b/dev/Styles/HtmlEditorWrapper.less new file mode 100644 index 000000000..32c3541ea --- /dev/null +++ b/dev/Styles/HtmlEditorWrapper.less @@ -0,0 +1,152 @@ +.html-editor-wrapper { + position: relative; + /*overflow: hidden;*/ +} + +.html-editor-wrapper-fullscreen { + border: 0; + padding: 0; + margin: 0; + overflow: hidden; + background: #fff; + height: 100%; +} + +html.html-editor-wrapper-fullscreen .html-editor-wrapper { + position: absolute; + z-index: 1000; + top: 0; + left: 0; + right: 0; + bottom: 0; + height: auto !important; + width: auto !important; +} + +.html-editor-wrapper-html, .html-editor-wrapper-plain { + background-color: #fff; + outline: none; + overflow: auto; + z-index: 1; + margin: 0; + border: 0; + + padding: 10px; + font-family: arial,sans-serif; + font-size: 12px; + line-height: 16px; + color: #333; + + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +.html-editor-wrapper-html:focus, .html-editor-wrapper-plain:focus { + border: 0; +} + +.html-editor-wrapper-html.styled, .html-editor-wrapper-plain.styled { + border: 1px solid #cccccc; + + -webkit-transition: border linear .2s, box-shadow linear .2s; + -moz-transition: border linear .2s, box-shadow linear .2s; + -o-transition: border linear .2s, box-shadow linear .2s; + transition: border linear .2s, box-shadow linear .2s; + + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +html.html-editor-wrapper-fullscreen .html-editor-wrapper-html.styled, html.html-editor-wrapper-fullscreen .html-editor-wrapper-plain.styled { + + border: 0; + + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.html-editor-wrapper-html.styled:focus, .html-editor-wrapper-plain.styled:focus { + border: 1px solid #999999; + + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +.html-editor-wrapper-html { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} + +.html-editor-wrapper-plain { + position: relative; + width: 100%; + height: 100%; + resize: none; + + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; +} + +.html-editor-wrapper-mode-button, .html-editor-wrapper-fullscreen-button { + position: absolute; + top: 5px; + right: 25px; + z-index: 100; + color: #555; + text-decoration: underline; + font-family: Arial,Helvetica,Tahoma,Verdana,Sans-Serif; + font-size: 12px; +} + +.html-editor-wrapper-mode-button:hover, .html-editor-wrapper-fullscreen-button:hover { + text-decoration: underline; + color: #555; +} + +.html-editor-wrapper-fullscreen-button { + bottom: 30px; +} + +.html-editor-wrapper-html ul { + padding-left: 40px; +} + +.html-editor-wrapper-html ul li { + list-style-type: disc !important; +} + +.html-editor-wrapper-html ol { + padding-left: 40px; +} +.html-editor-wrapper-html ol li { + list-style-type: decimal !important; +} + +.html-editor-wrapper-html blockquote { + border: 0; + border-left: solid 2px #444; + margin: 5px 0 5px 5px; + padding-left: 5px; +} + +.html-editor-wrapper-html img { + vertical-align: bottom; +} diff --git a/dev/Styles/SettingsIdentities.less b/dev/Styles/SettingsIdentities.less index 3dd6ac35a..e85123fa2 100644 --- a/dev/Styles/SettingsIdentities.less +++ b/dev/Styles/SettingsIdentities.less @@ -7,6 +7,12 @@ padding: 14px 0; } + .e-signature-place { + display: inline-block; + width: 600px; + height: 200px; + } + .list-table { width: 600px; diff --git a/dev/Styles/SettingsIdentity.less b/dev/Styles/SettingsIdentity.less index 4791de8e2..8172611c8 100644 --- a/dev/Styles/SettingsIdentity.less +++ b/dev/Styles/SettingsIdentity.less @@ -1,4 +1,10 @@ .b-settings-identity { + + .e-signature-place { + display: inline-block; + width: 600px; + height: 200px; + } } diff --git a/dev/ViewModels/MailBoxMessageViewViewModel.js b/dev/ViewModels/MailBoxMessageViewViewModel.js index 4df3a0d89..f4162562c 100644 --- a/dev/ViewModels/MailBoxMessageViewViewModel.js +++ b/dev/ViewModels/MailBoxMessageViewViewModel.js @@ -291,6 +291,10 @@ MailBoxMessageViewViewModel.prototype.onBuild = function (oDom) }); oDom + .on('mousedown', 'a', function (oEvent) { + // setup maito protocol + return !(oEvent && 3 !== oEvent['which'] && RL.mailToHelper($(this).attr('href'))); + }) .on('click', '.attachmentsPlace .attachmentPreview', function (oEvent) { if (oEvent && oEvent.stopPropagation) { diff --git a/dev/ViewModels/PopupsComposeViewModel.js b/dev/ViewModels/PopupsComposeViewModel.js index 673551ccf..e7f0f89be 100644 --- a/dev/ViewModels/PopupsComposeViewModel.js +++ b/dev/ViewModels/PopupsComposeViewModel.js @@ -83,9 +83,7 @@ function PopupsComposeViewModel() this.attacheMultipleAllowed = ko.observable(false); this.addAttachmentEnabled = ko.observable(false); - this.composeEditorTextArea = ko.observable(null); - this.composeEditorHtmlArea = ko.observable(null); - this.composeEditorToolbar = ko.observable(null); + this.composeEditorArea = ko.observable(null); this.identities = RL.data().identities; @@ -278,8 +276,8 @@ function PopupsComposeViewModel() this.cc(), this.bcc(), this.subject(), - this.oEditor.isHtml(), - this.oEditor.getTextForRequest(), + this.oEditor ? this.oEditor.isHtml() : false, + this.oEditor ? this.oEditor.getData() : '', this.prepearAttachmentsForSendOrSave(), this.aDraftInfo, this.sInReplyTo, @@ -314,8 +312,8 @@ function PopupsComposeViewModel() this.cc(), this.bcc(), this.subject(), - this.oEditor.isHtml(), - this.oEditor.getTextForRequest(), + this.oEditor ? this.oEditor.isHtml() : false, + this.oEditor ? this.oEditor.getData() : '', this.prepearAttachmentsForSendOrSave(), this.aDraftInfo, this.sInReplyTo, @@ -333,17 +331,6 @@ function PopupsComposeViewModel() } }, this); - Utils.initOnStartOrLangChange(null, this, function () { - if (this.oEditor) - { - this.oEditor.initLanguage( - Utils.i18n('EDITOR/TEXT_SWITCHER_CONFIRM'), - Utils.i18n('EDITOR/TEXT_SWITCHER_PLAINT_TEXT'), - Utils.i18n('EDITOR/TEXT_SWITCHER_RICH_FORMATTING') - ); - } - }); - this.showCcAndBcc.subscribe(function () { this.triggerForResize(); }, this); @@ -581,6 +568,13 @@ PopupsComposeViewModel.prototype.convertSignature = function (sSignature, sFrom) { if ('' !== sSignature) { + var bHtml = false; + if (':HTML:' === sSignature.substr(0, 6)) + { + bHtml = true; + sSignature = sSignature.substr(6); + } + sSignature = sSignature.replace(/[\r]/, ''); sFrom = Utils.pString(sFrom); @@ -594,11 +588,46 @@ PopupsComposeViewModel.prototype.convertSignature = function (sSignature, sFrom) } sSignature = sSignature.replace(/{{FROM}}[\n]?/, '').replace(/{{IF:FROM}}[\n]?/, '').replace(/{{\/IF:FROM}}[\n]?/, ''); + + if (!bHtml) + { + sSignature = Utils.convertPlainTextToHtml(sSignature); + } } return sSignature; }; +PopupsComposeViewModel.prototype.editor = function (fOnInit) +{ + if (fOnInit) + { + var self = this; + if (!this.oEditor && this.composeEditorArea()) + { + _.delay(function () { + self.oEditor = new HtmlEditorWrapper(self.composeEditorArea(), null, function () { + fOnInit(self.oEditor); + }); + + Utils.initOnStartOrLangChange(null, self, function () { + if (self.oEditor) + { + self.oEditor.setupLang( + Utils.i18n('EDITOR/TEXT_SWITCHER_RICH_FORMATTING'), + Utils.i18n('EDITOR/TEXT_SWITCHER_PLAINT_TEXT') + ); + } + }); + }, 400); + } + else if (this.oEditor) + { + fOnInit(this.oEditor); + } + } +}; + /** * @param {string=} sType = Enums.ComposeType.Empty * @param {?MessageModel|Array=} oMessageOrArray = null @@ -747,54 +776,62 @@ PopupsComposeViewModel.prototype.onShow = function (sType, oMessageOrArray, aToE break; } - if (this.oEditor) + switch (sComposeType) { - switch (sComposeType) - { - case Enums.ComposeType.Reply: - case Enums.ComposeType.ReplyAll: - sFrom = oMessage.fromToLine(false, true); - sReplyTitle = Utils.i18n('COMPOSE/REPLY_MESSAGE_TITLE', { - 'DATETIME': sDate, - 'EMAIL': sFrom - }); + case Enums.ComposeType.Reply: + case Enums.ComposeType.ReplyAll: + sFrom = oMessage.fromToLine(false, true); + sReplyTitle = Utils.i18n('COMPOSE/REPLY_MESSAGE_TITLE', { + 'DATETIME': sDate, + 'EMAIL': sFrom + }); - sText = '

' + sReplyTitle + ':' + - '

' + sText + '
'; + sText = '

' + sReplyTitle + ':' + + '

' + sText + '
'; - break; + break; - case Enums.ComposeType.Forward: - sFrom = oMessage.fromToLine(false, true); - sTo = oMessage.toToLine(false, true); - sCc = oMessage.ccToLine(false, true); - sText = '


' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_TITLE') + - '
' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_FROM') + ': ' + sFrom + - '
' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_TO') + ': ' + sTo + - (0 < sCc.length ? '
' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_CC') + ': ' + sCc : '') + - '
' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_SENT') + ': ' + Utils.encodeHtml(sDate) + - '
' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_SUBJECT') + ': ' + Utils.encodeHtml(sSubject) + - '

' + sText; - break; - case Enums.ComposeType.ForwardAsAttachment: - sText = ''; - break; - } - - if (bSignatureToAll && '' !== sSignature && - Enums.ComposeType.EditAsNew !== sComposeType && Enums.ComposeType.Draft !== sComposeType) - { - sText = Utils.convertPlainTextToHtml(this.convertSignature(sSignature, - fEmailArrayToStringLineHelper(oMessage.from, true))) + '
' + sText; - } - - this.oEditor.setRawText(sText, oMessage.isHtml()); + case Enums.ComposeType.Forward: + sFrom = oMessage.fromToLine(false, true); + sTo = oMessage.toToLine(false, true); + sCc = oMessage.ccToLine(false, true); + sText = '


' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_TITLE') + + '
' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_FROM') + ': ' + sFrom + + '
' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_TO') + ': ' + sTo + + (0 < sCc.length ? '
' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_CC') + ': ' + sCc : '') + + '
' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_SENT') + ': ' + Utils.encodeHtml(sDate) + + '
' + Utils.i18n('COMPOSE/FORWARD_MESSAGE_TOP_SUBJECT') + ': ' + Utils.encodeHtml(sSubject) + + '

' + sText; + break; + case Enums.ComposeType.ForwardAsAttachment: + sText = ''; + break; } + + if (bSignatureToAll && '' !== sSignature && + Enums.ComposeType.EditAsNew !== sComposeType && Enums.ComposeType.Draft !== sComposeType) + { + sText = this.convertSignature(sSignature, fEmailArrayToStringLineHelper(oMessage.from, true)) + '
' + sText; + } + + this.editor(function (oEditor) { + oEditor.setHtml(sText, false); + if (!oMessage.isHtml()) + { + oEditor.modeToggle(false); + } + }); } - else if (this.oEditor && Enums.ComposeType.Empty === sComposeType) + else if (Enums.ComposeType.Empty === sComposeType) { - this.oEditor.setRawText(Utils.convertPlainTextToHtml(this.convertSignature(sSignature)), - Enums.EditorDefaultType.Html === RL.data().editorDefaultType()); + sText = this.convertSignature(sSignature); + this.editor(function (oEditor) { + oEditor.setHtml(sText, false); + if (Enums.EditorDefaultType.Html !== RL.data().editorDefaultType()) + { + oEditor.modeToggle(false); + } + }); } else if (Utils.isNonEmptyArray(oMessageOrArray)) { @@ -868,7 +905,6 @@ PopupsComposeViewModel.prototype.tryToClosePopup = function () PopupsComposeViewModel.prototype.onBuild = function () { - this.initEditor(); this.initUploader(); var @@ -974,28 +1010,6 @@ PopupsComposeViewModel.prototype.getAttachmentById = function (sId) return null; }; -PopupsComposeViewModel.prototype.initEditor = function () -{ - if (this.composeEditorTextArea() && this.composeEditorHtmlArea() && this.composeEditorToolbar()) - { - var self = this; - this.oEditor = new HtmlEditor(this.composeEditorTextArea(), this.composeEditorHtmlArea(), this.composeEditorToolbar(), { - 'onSwitch': function (bHtml) { - if (!bHtml) - { - self.removeLinkedAttachments(); - } - } - }); - - this.oEditor.initLanguage( - Utils.i18n('EDITOR/TEXT_SWITCHER_CONFIRM'), - Utils.i18n('EDITOR/TEXT_SWITCHER_PLAINT_TEXT'), - Utils.i18n('EDITOR/TEXT_SWITCHER_RICH_FORMATTING') - ); - } -}; - PopupsComposeViewModel.prototype.initUploader = function () { if (this.composeUploaderButton()) @@ -1400,7 +1414,7 @@ PopupsComposeViewModel.prototype.isEmptyForm = function (bIncludeAttachmentInPro 0 === this.bcc().length && 0 === this.subject().length && bAttach && - '' === this.oEditor.getTextForRequest() + (!this.oEditor || '' === this.oEditor.getData()) ; }; @@ -1440,7 +1454,7 @@ PopupsComposeViewModel.prototype.reset = function () if (this.oEditor) { - this.oEditor.clear(); + this.oEditor.clear(false); } }; diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php index a41892c6e..56be78f88 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php @@ -190,6 +190,7 @@ class Service '{{BaseAppMainCssLink}}' => $aData['AppCssLink'], '{{BaseAppBootScriptSource}}' => $sJsBoot, '{{BaseAppLibsScriptLink}}' => $aData['LibJsLink'], + '{{BaseAppEditorScriptLink}}' => $aData['EditorJsLink'], '{{BaseAppMainScriptLink}}' => $aData['AppJsLink'], '{{BaseAppLoadingDescription}}' => \htmlspecialchars($aData['LoadingDescription'], ENT_QUOTES|ENT_IGNORE, 'UTF-8'), '{{BaseDir}}' => \in_array($aData['Language'], array('ar', 'he', 'ur')) ? 'rtl' : 'ltr' @@ -275,6 +276,7 @@ class Service 'AppleTouchLink' => $sStaticPrefix.'apple-touch-icon.png', 'AppCssLink' => $sStaticPrefix.'css/app'.($bAppCssDebug ? '' : '.min').'.css', 'LibJsLink' => $sStaticPrefix.'js/libs.js', + 'EditorJsLink' => $sStaticPrefix.'ckeditor/ckeditor.js', 'AppJsLink' => $sStaticPrefix.'js/'.($bAdmin ? 'admin' : 'app').($bAppJsDebug ? '' : '.min').'.js' ); } diff --git a/rainloop/v/0.0.0/app/templates/Index.html b/rainloop/v/0.0.0/app/templates/Index.html index 5e36a474c..f421ed9ce 100644 --- a/rainloop/v/0.0.0/app/templates/Index.html +++ b/rainloop/v/0.0.0/app/templates/Index.html @@ -72,6 +72,7 @@
+